From 0112e3c07a2b8fcfb642ace4b880575f86fcf933 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Mon, 3 Dec 2007 16:29:02 +0000 Subject: Add preauth.c --- ChangeLog | 2 + NEWS | 29 +++- imap4d/preauth.c | 490 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 520 insertions(+), 1 deletion(-) create mode 100644 imap4d/preauth.c diff --git a/ChangeLog b/ChangeLog index 9958c8ea3..d57d533e1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,9 @@ 2007-12-03 Sergey Poznyakoff + * NEWS: Update. * gnulib.modules: Add des. Sort lines. * imap4d/Makefile.am (imap4d_SOURCES): Add preauth.c + * imap4d/preauth.c: New file. * imap4d/authenticate.c (imap4d_authenticate): Use imap4d_session_setup. * imap4d/imap4d.c (imap4d_session_setup) diff --git a/NEWS b/NEWS index b1b53bb95..9c753c1d7 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -GNU mailutils NEWS -- history of user-visible changes. 2007-11-30 +GNU mailutils NEWS -- history of user-visible changes. 2007-12-03 Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. See the end of file for copying conditions. @@ -146,6 +146,33 @@ Previous versions incorrectly understood such an URL as `a/b' * Fixed APOP handling. +* imap4d supports PREAUTH mode. + +Three mechanisms are provided for authentifying the connection in +PREAUTH mode: + + 1. stdio - PREAUTH mode is enabled automatically if imap4d is started + from command line in interactive mode (-i command line + option). The current login name is used as the user name. + + 2. ident - The remote machine is asked about the requester identity + using the identification protocol (RFC 1413). Both plaintext and + DES encrypted replies are understood. + + 3. prog - Imap4d invokes an external program to authenticate the + connection. Four arguments are supplied to the program: + + 1) Remote IP address in dotted-quad notation; + 2) Remote port number; + 3) Local IP address (currently "0.0.0.0"); + 4) Local port number. + + If the connection is authenticated, the program should print the + user name, followed by a newline character, on its standard + output and exit with code 0. + + Otherwise, it shoud exit with a non-zero exit code. + * Remove v0.6 compatibility layer. diff --git a/imap4d/preauth.c b/imap4d/preauth.c new file mode 100644 index 000000000..790d2bc79 --- /dev/null +++ b/imap4d/preauth.c @@ -0,0 +1,490 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2001, 2002, 2003, 2004, + 2005, 2006, 2007 Free Software Foundation, Inc. + + GNU Mailutils is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GNU Mailutils is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GNU Mailutils; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1301 USA */ + +/* Preauth support for imap4d */ + +#include "imap4d.h" +#include "des.h" + + +/* Stdio preauth */ + +static char * +do_preauth_stdio (struct sockaddr_in *pcs) +{ + struct passwd *pw = getpwuid (getuid ()); + return pw ? strdup (pw->pw_name) : NULL; +} + + +/* IDENT (AUTH, RFC1413) preauth */ +#define USERNAME_C "USERID :" + +/* If the reply matches sscanf expression + + "%*[^:]: USERID :%*[^:]:%s" + + returns a malloced copy of the %s part. Otherwise, return NULL. */ + +static char * +ident_extract_username (char *reply) +{ + char *p; + + p = strchr (reply, ':'); + if (!p) + return NULL; + if (p[1] != ' ' || strncmp (p + 2, USERNAME_C, sizeof (USERNAME_C) - 1)) + return NULL; + p += 2 + sizeof (USERNAME_C) - 1; + p = strchr (p, ':'); + if (!p) + return NULL; + do + p++; + while (*p == ' '); + return p; +} + +static int +trimcrlf (char *buf) +{ + int len = strlen (buf); + if (len == 0) + return 0; + if (buf[len-1] == '\n') + { + len--; + if (buf[len-1] == '\r') + len--; + buf[len] = 0; + } + return len; +} + +static int +is_des_p (const char *name) +{ + int len = strlen (name); + return len > 1 && name[0] == '[' && name[len-1] == ']'; +} + +#define smask(step) ((1<>step)&smask(step))) +#define parity_char(x) pstep(pstep(pstep((x),4),2),1) + +static void +des_fixup_key_parity (unsigned char key[8]) +{ + int i; + for (i = 0; i < 8; i++) + { + key[i] &= 0xfe; + key[i] |= 1 ^ parity_char (key[i]); + } +} + +static void +des_cbc_cksum (gl_des_ctx *ctx, unsigned char *buf, size_t bufsize, + unsigned char out[8], unsigned char key[8]) +{ + while (bufsize > 0) + { + if (bufsize >= 8) + { + unsigned char *p = key; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + *p++ ^= *buf++; + bufsize -= 8; + } + else + { + unsigned char *p = key + bufsize; + buf += bufsize; + switch (bufsize) { + case 7: + *--p ^= *--buf; + case 6: + *--p ^= *--buf; + case 5: + *--p ^= *--buf; + case 4: + *--p ^= *--buf; + case 3: + *--p ^= *--buf; + case 2: + *--p ^= *--buf; + case 1: + *--p ^= *--buf; + } + bufsize = 0; + } + gl_des_ecb_crypt (ctx, key, key, 0); + } +} + +static void +des_string_to_key (char *buf, size_t bufsize, unsigned char key[8]) +{ + size_t i; + int j; + unsigned temp; + unsigned char *p; + char *p_char; + char k_char[64]; + gl_des_ctx context; + char *pstr; + int forward = 1; + p_char = k_char; + memset (k_char, 0, sizeof (k_char)); + + /* get next 8 bytes, strip parity, xor */ + pstr = buf; + for (i = 1; i <= bufsize; i++) + { + /* get next input key byte */ + temp = (unsigned int) *pstr++; + /* loop through bits within byte, ignore parity */ + for (j = 0; j <= 6; j++) + { + if (forward) + *p_char++ ^= (int) temp & 01; + else + *--p_char ^= (int) temp & 01; + temp = temp >> 1; + } + while (--j > 0); + + /* check and flip direction */ + if ((i%8) == 0) + forward = !forward; + } + + p_char = k_char; + p = (unsigned char *) key; + + for (i = 0; i <= 7; i++) + { + temp = 0; + for (j = 0; j <= 6; j++) + temp |= *p_char++ << (1 + j); + *p++ = (unsigned char) temp; + } + + des_fixup_key_parity (key); + gl_des_setkey (&context, key); + des_cbc_cksum (&context, buf, bufsize, key, key); + memset (&context, 0, sizeof context); + des_fixup_key_parity (key); +} + +static int +decode64_buf (const char *name, unsigned char **pbuf, size_t *psize) +{ + mu_stream_t str = NULL, flt = NULL; + size_t namelen; + unsigned char buf[512]; + size_t size; + + name++; + namelen = strlen (name) - 1; + mu_memory_stream_create (&str, NULL, MU_STREAM_NO_CHECK); + mu_filter_create (&flt, str, "base64", MU_FILTER_DECODE, + MU_STREAM_READ | MU_STREAM_NO_CHECK); + mu_stream_open (str); + mu_stream_sequential_write (str, name, namelen); + mu_stream_read (flt, buf, sizeof buf, 0, &size); + mu_stream_destroy (&flt, NULL); + mu_stream_destroy (&str, NULL); + *pbuf = malloc (size); + if (!*pbuf) + return 1; + memcpy (*pbuf, buf, size); + *psize = size; + return 0; +} + +struct ident_info +{ + uint32_t checksum; + uint16_t random; + uint16_t uid; + uint32_t date; + uint32_t ip_local; + uint32_t ip_remote; + uint16_t port_local; + uint16_t port_remote; +}; + +union ident_data +{ + struct ident_info fields; + unsigned long longs[6]; + unsigned char chars[24]; +}; + +char * +ident_decrypt (const char *file, const char *name) +{ + unsigned char *buf = NULL; + size_t size = 0; + int fd; + char keybuf[1024]; + union ident_data id; + + if (decode64_buf (name, &buf, &size)) + return NULL; + + if (size != 24) + { + mu_diag_output (MU_DIAG_ERROR, + _("Incorrect length of IDENT DES packet")); + free (buf); + return NULL; + } + + fd = open (file, O_RDONLY); + if (fd < 0) + { + mu_diag_output (MU_DIAG_ERROR, + _("Cannot open file %s: %s"), + file, mu_strerror (errno)); + return NULL; + } + + while (read (fd, keybuf, sizeof (keybuf)) == sizeof (keybuf)) + { + int i; + unsigned char key[8]; + gl_des_ctx ctx; + + des_string_to_key (keybuf, sizeof (keybuf), key); + gl_des_setkey (&ctx, key); + + memcpy (id.chars, buf, size); + + gl_des_ecb_decrypt (&ctx, (char *)&id.longs[4], (char *)&id.longs[4]); + id.longs[4] ^= id.longs[2]; + id.longs[5] ^= id.longs[3]; + + gl_des_ecb_decrypt (&ctx, (char *)&id.longs[2], (char *)&id.longs[2]); + id.longs[2] ^= id.longs[0]; + id.longs[3] ^= id.longs[1]; + + gl_des_ecb_decrypt (&ctx, (char *)&id.longs[0], (char *)&id.longs[0]); + + for (i = 1; i < 6; i++) + id.longs[0] ^= id.longs[i]; + + if (id.fields.checksum == 0) + break; + } + close (fd); + free (buf); + + if (id.fields.checksum == 0) + { + uid_t uid = ntohs (id.fields.uid); + auth_data = mu_get_auth_by_uid (uid); + if (!auth_data) + { + mu_diag_output (MU_DIAG_ERROR, _("No user with UID %u"), uid); + return NULL; + } + return auth_data->name; + } + else + mu_diag_output (MU_DIAG_ERROR, _("Failed to decrypt IDENT reply")); + return NULL; +} + +static char * +do_preauth_ident (struct sockaddr_in *pcs) +{ + mu_stream_t stream; + char hostaddr[16]; + char *p = inet_ntoa (pcs->sin_addr); + int rc; + char *buf = NULL; + size_t size = 0; + char *name = NULL; + + memcpy (hostaddr, p, 15); + hostaddr[15] = 0; + rc = mu_tcp_stream_create (&stream, hostaddr, ident_port, + MU_STREAM_RDWR | MU_STREAM_NO_CHECK); + if (rc) + { + mu_diag_output (MU_DIAG_INFO, _("Cannot create TCP stream: %s"), + mu_strerror (rc)); + return NULL; + } + + rc = mu_stream_open (stream); + if (rc) + { + mu_diag_output (MU_DIAG_INFO, _("Cannot open TCP stream to %s:%d: %s"), + hostaddr, ident_port, mu_strerror (rc)); + return NULL; + } + + mu_stream_sequential_printf (stream, "%u , %u\r\n", ntohs (pcs->sin_port), + mu_gocs_daemon.port); + mu_stream_shutdown (stream, MU_STREAM_WRITE); + + rc = mu_stream_sequential_getline (stream, &buf, &size, NULL); + mu_stream_close (stream); + mu_stream_destroy (&stream, NULL); + if (rc) + { + mu_diag_output (MU_DIAG_INFO, _("Cannot read answer from %s:%d: %s"), + hostaddr, ident_port, mu_strerror (rc)); + return NULL; + } + mu_diag_output (MU_DIAG_INFO, "Got %s", buf); + trimcrlf (buf); + name = ident_extract_username (buf); + if (!name) + mu_diag_output (MU_DIAG_INFO, + _("Malformed IDENT response: `%s', from %s:%d"), + buf, hostaddr, ident_port); + else if (is_des_p (name)) + { + if (!ident_keyfile) + { + mu_diag_output (MU_DIAG_ERROR, + _("Keydile not specified in config; " + "use `ident-keyfile FILE'")); + name = NULL; + } + else + name = ident_decrypt (ident_keyfile, name); + } + else if (ident_encrypt_only) + { + mu_diag_output (MU_DIAG_ERROR, + _("Refusing unencrypted ident reply from %s:%d"), + hostaddr, ident_port); + name = NULL; + } + else + { + mu_diag_output (MU_DIAG_INFO, "USERNAME %s", name); + name = strdup (name); + } + + free (buf); + return name; +} + + +/* External (program) preauth */ +static char * +do_preauth_program (struct sockaddr_in *pcs) +{ + FILE *fp; + char *p = inet_ntoa (pcs->sin_addr); + char *cmd = 0; + char *buf = NULL; + size_t size; + ssize_t rc; + + asprintf (&cmd, "%s %s %u %s %u", + preauth_program, + p, + ntohs (pcs->sin_port), + "0.0.0.0", /* FIXME */ + mu_gocs_daemon.port); + fp = popen (cmd, "r"); + free (cmd); + rc = getline (&buf, &size, fp); + pclose (fp); + if (rc > 0) + { + if (trimcrlf (buf) == 0) + { + free (buf); + return NULL; + } + return buf; + } + return NULL; +} + +int +imap4d_preauth_setup (int fd) +{ + struct sockaddr_in cs; + int len = sizeof cs; + char *username = NULL; + + mu_diag_output (MU_DIAG_INFO, _("Incoming connection opened")); + if (getpeername (fd, (struct sockaddr *) &cs, &len) < 0) + mu_diag_output (MU_DIAG_ERROR, + _("Cannot obtain IP address of client: %s"), + strerror (errno)); + else + mu_diag_output (MU_DIAG_INFO, _("Connect from %s"), + inet_ntoa (cs.sin_addr)); + + auth_data = NULL; + switch (preauth_mode) + { + case preauth_none: + return 0; + + case preauth_stdio: + username = do_preauth_stdio (&cs); + break; + + case preauth_ident: + username = do_preauth_ident (&cs); + break; + + case preauth_prog: + username = do_preauth_program (&cs); + break; + } + + if (username) + { + int rc; + + if (auth_data) + rc = imap4d_session_setup0 (); + else + { + rc = imap4d_session_setup (username); + free (username); + } + if (rc == 0) + { + state = STATE_AUTH; + return 0; + } + } + + return preauth_only; +} -- cgit v1.2.1