Commit 0112e3c0 0112e3c07a2b8fcfb642ace4b880575f86fcf933 by Sergey Poznyakoff

Add preauth.c

1 parent d2ff5151
2007-12-03 Sergey Poznyakoff <gray@gnu.org.ua>
* 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)
......
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.
......
/* 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)-1)
#define pstep(x,step) (((x)&smask(step))^(((x)>>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;
}