/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 1999, 2001-2012, 2014-2016 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, see . */
/* Preauth support for imap4d */
#include "imap4d.h"
#include "des.h"
/* Stdio preauth */
static char *
do_preauth_stdio ()
{
struct passwd *pw = getpwuid (getuid ());
return pw ? mu_strdup (pw->pw_name) : NULL;
}
/* IDENT (AUTH, RFC1413) preauth */
#define USERNAME_C "USERID :"
/* If the reply matches sscanf expression
"%*[^:]: USERID :%*[^:]:%s"
return a pointer to 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
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, (char*) key, (char*) 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, (char*) key);
des_cbc_cksum (&context, (unsigned char*) 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_static_memory_stream_create (&str, name, namelen);
mu_filter_create (&flt, str, "base64", MU_FILTER_DECODE, MU_STREAM_READ);
mu_stream_unref (str);
mu_stream_read (flt, buf, sizeof buf, &size);
mu_stream_destroy (&flt);
*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, (char*) 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 *clt_sa, struct sockaddr *srv_sa)
{
mu_stream_t stream;
char hostaddr[16];
int rc;
char *buf = NULL;
size_t size = 0;
char *name = NULL;
struct sockaddr_in *srv_addr, *clt_addr;
char *p;
if (!srv_sa || !clt_sa)
{
mu_diag_output (MU_DIAG_ERROR, _("not enough data for IDENT preauth"));
return NULL;
}
if (srv_sa->sa_family != AF_INET)
{
mu_diag_output (MU_DIAG_ERROR,
_("invalid address family (%d) for IDENT preauth"),
srv_sa->sa_family);
return NULL;
}
srv_addr = (struct sockaddr_in *) srv_sa;
clt_addr = (struct sockaddr_in *) clt_sa;
p = inet_ntoa (clt_addr->sin_addr);
memcpy (hostaddr, p, 15);
hostaddr[15] = 0;
rc = mu_tcp_stream_create (&stream, hostaddr, ident_port, MU_STREAM_RDWR);
if (rc)
{
mu_diag_output (MU_DIAG_INFO, _("cannot create TCP stream: %s"),
mu_strerror (rc));
return NULL;
}
mu_stream_printf (stream, "%u , %u\n",
ntohs (clt_addr->sin_port),
ntohs (srv_addr->sin_port));
mu_stream_shutdown (stream, MU_STREAM_WRITE);
rc = mu_stream_getline (stream, &buf, &size, NULL);
mu_stream_close (stream);
mu_stream_destroy (&stream);
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);
name = ident_extract_username (mu_str_stripws (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,
_("keyfile 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 = mu_strdup (name);
}
free (buf);
return name;
}
#define SEQ(s, n, l) \
(((l) == (sizeof(s) - 1)) && memcmp (s, n, l) == 0)
struct preauth_closure
{
struct sockaddr_in *s_clt, *s_srv;
};
static int
preauth_getvar (char **ret, const char *name, size_t nlen, void *data)
{
struct preauth_closure *clos = data;
const char *s = NULL;
if (SEQ ("client_address", name, nlen))
{
if (clos->s_clt && clos->s_clt->sin_family == AF_INET)
s = inet_ntoa (clos->s_clt->sin_addr);
}
else if (SEQ ("client_port", name, nlen))
{
if (clos->s_clt && clos->s_clt->sin_family == AF_INET)
s = mu_umaxtostr (0, ntohs (clos->s_clt->sin_port));
}
else if (SEQ ("server_address", name, nlen))
{
if (clos->s_srv && clos->s_srv->sin_family == AF_INET)
s = inet_ntoa (clos->s_srv->sin_addr);
}
else if (SEQ ("server_port", name, nlen))
{
if (clos->s_srv && clos->s_srv->sin_family == AF_INET)
s = mu_umaxtostr (0, ntohs (clos->s_srv->sin_port));
}
if (s)
{
*ret = strdup (s);
if (!*ret)
return MU_WRDSE_NOSPACE;
return MU_WRDSE_OK;
}
return MU_WRDSE_UNDEF;
}
/* External (program) preauth */
static char *
do_preauth_program (struct sockaddr *pcs, struct sockaddr *sa)
{
int rc;
mu_stream_t str;
char *buf = NULL;
size_t size = 0, n;
struct mu_wordsplit ws;
struct preauth_closure clos;
clos.s_clt = (struct sockaddr_in *) pcs;
clos.s_srv = (struct sockaddr_in *) sa;
ws.ws_getvar = preauth_getvar;
ws.ws_closure = &clos;
if (mu_wordsplit (preauth_program, &ws,
MU_WRDSF_NOSPLIT | MU_WRDSF_NOCMD |
MU_WRDSF_GETVAR | MU_WRDSF_CLOSURE))
{
mu_error (_("cannot expand line `%s': %s"), preauth_program,
mu_wordsplit_strerror (&ws));
return NULL;
}
else if (ws.ws_wordc == 0)
{
mu_wordsplit_free (&ws);
mu_error (_("`%s' expands to an empty line"), preauth_program);
return NULL;
}
rc = mu_command_stream_create (&str, ws.ws_wordv[0], MU_STREAM_READ);
mu_wordsplit_free (&ws);
if (rc)
{
mu_error (_("cannot open input pipe from %s"), preauth_program);
return NULL;
}
rc = mu_stream_getline (str, &buf, &size, &n);
mu_stream_destroy (&str);
if (rc)
{
mu_error (_("read from `%s' failed"), preauth_program);
}
else
{
mu_rtrim_class (buf, MU_CTYPE_ENDLN);
return buf;
}
return NULL;
}
int
imap4d_preauth_setup (int fd)
{
struct sockaddr clt_sa, *pclt_sa;
socklen_t clt_len = sizeof clt_sa;
struct sockaddr srv_sa, *psrv_sa;
socklen_t srv_len = sizeof srv_sa;
char *username = NULL;
if (getsockname (fd, &srv_sa, &srv_len) == -1)
{
psrv_sa = NULL;
srv_len = 0;
}
else
psrv_sa = &srv_sa;
if (test_mode)
{
pclt_sa = NULL;
clt_len = 0;
}
else if (getpeername (fd, (struct sockaddr *) &clt_sa, &clt_len) == -1)
{
mu_diag_output (MU_DIAG_ERROR,
_("cannot obtain IP address of client: %s"),
strerror (errno));
pclt_sa = NULL;
clt_len = 0;
}
else
pclt_sa = &clt_sa;
auth_data = NULL;
switch (preauth_mode)
{
case preauth_none:
return 0;
case preauth_stdio:
username = do_preauth_stdio ();
break;
case preauth_ident:
username = do_preauth_ident (pclt_sa, psrv_sa);
break;
case preauth_prog:
username = do_preauth_program (pclt_sa, psrv_sa);
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;
}