/* GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2007, 2008, 2009, 2010, 2011 Free Software Foundation,
Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library; If not, see
. */
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct _mu_acl_entry
{
mu_acl_action_t action;
void *arg;
unsigned netmask;
int salen;
struct sockaddr sa[1];
};
struct _mu_acl
{
mu_list_t aclist;
};
static void
_destroy_acl_entry (void *item)
{
struct _mu_acl_entry *p = item;
free (p);
/* FIXME: free arg? */
}
static size_t
mu_acl_entry_size (int salen)
{
return sizeof (struct _mu_acl_entry) + salen - sizeof (struct sockaddr);
}
static int
prepare_sa (struct sockaddr *sa)
{
switch (sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in *s_in = (struct sockaddr_in *)sa;
s_in->sin_addr.s_addr = ntohl (s_in->sin_addr.s_addr);
break;
}
case AF_UNIX:
break;
default:
return 1;
}
return 0;
}
int
mu_acl_entry_create (struct _mu_acl_entry **pent,
mu_acl_action_t action, void *data,
struct sockaddr *sa, int salen, unsigned long netmask)
{
struct _mu_acl_entry *p = malloc (mu_acl_entry_size (salen));
if (!p)
return EINVAL;
p->action = action;
p->arg = data;
p->netmask = ntohl (netmask);
p->salen = salen;
memcpy (p->sa, sa, salen);
if (prepare_sa (p->sa))
{
free (p);
return EINVAL;
}
*pent = p;
return 0;
}
int
mu_acl_create (mu_acl_t *pacl)
{
int rc;
mu_acl_t acl;
acl = calloc (1, sizeof (*acl));
if (!acl)
return errno;
rc = mu_list_create (&acl->aclist);
if (rc)
free (acl);
else
*pacl = acl;
mu_list_set_destroy_item (acl->aclist, _destroy_acl_entry);
return rc;
}
int
mu_acl_count (mu_acl_t acl, size_t *pcount)
{
if (!acl)
return EINVAL;
return mu_list_count (acl->aclist, pcount);
}
int
mu_acl_destroy (mu_acl_t *pacl)
{
mu_acl_t acl;
if (!pacl || !*pacl)
return EINVAL;
acl = *pacl;
mu_list_destroy (&acl->aclist);
free (acl);
*pacl = acl;
return 0;
}
int
mu_acl_get_iterator (mu_acl_t acl, mu_iterator_t *pitr)
{
if (!acl)
return EINVAL;
return mu_list_get_iterator (acl->aclist, pitr);
}
int
mu_acl_append (mu_acl_t acl, mu_acl_action_t act,
void *data, struct sockaddr *sa, int salen,
unsigned long netmask)
{
int rc;
struct _mu_acl_entry *ent;
if (!acl)
return EINVAL;
rc = mu_acl_entry_create (&ent, act, data, sa, salen, netmask);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot allocate ACL entry: %s", mu_strerror (rc)));
return ENOMEM;
}
rc = mu_list_append (acl->aclist, ent);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot append ACL entry: %s", mu_strerror (rc)));
free (ent);
}
return rc;
}
int
mu_acl_prepend (mu_acl_t acl, mu_acl_action_t act, void *data,
struct sockaddr *sa, int salen, unsigned long netmask)
{
int rc;
struct _mu_acl_entry *ent;
if (!acl)
return EINVAL;
rc = mu_acl_entry_create (&ent, act, data, sa, salen, netmask);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot allocate ACL entry: %s", mu_strerror (rc)));
return ENOMEM;
}
rc = mu_list_prepend (acl->aclist, ent);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot prepend ACL entry: %s", mu_strerror (rc)));
free (ent);
}
return rc;
}
int
mu_acl_insert (mu_acl_t acl, size_t pos, int before,
mu_acl_action_t act, void *data,
struct sockaddr *sa, int salen, unsigned long netmask)
{
int rc;
void *ptr;
struct _mu_acl_entry *ent;
if (!acl)
return EINVAL;
rc = mu_list_get (acl->aclist, pos, &ptr);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("No such entry %lu", (unsigned long) pos));
return rc;
}
rc = mu_acl_entry_create (&ent, act, data, sa, salen, netmask);
if (!ent)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot allocate ACL entry: %s", mu_strerror (rc)));
return ENOMEM;
}
rc = mu_list_insert (acl->aclist, ptr, ent, before);
if (rc)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot insert ACL entry: %s", mu_strerror (rc)));
free (ent);
}
return rc;
}
static mu_kwd_t action_tab[] = {
{ "accept", mu_acl_accept },
{ "deny", mu_acl_deny },
{ "log", mu_acl_log },
{ "exec", mu_acl_exec },
{ "ifexec", mu_acl_ifexec },
{ NULL }
};
int
mu_acl_action_to_string (mu_acl_action_t act, const char **pstr)
{
return mu_kwd_xlat_tok (action_tab, act, pstr);
}
int
mu_acl_string_to_action (const char *str, mu_acl_action_t *pres)
{
int x;
int rc = mu_kwd_xlat_name (action_tab, str, &x);
if (rc == 0)
*pres = x;
return rc;
}
#define MU_S_UN_NAME(sa, salen) \
((salen < mu_offsetof (struct sockaddr_un,sun_path)) ? "" : (sa)->sun_path)
static void
debug_sockaddr (struct sockaddr *sa, int salen)
{
switch (sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in s_in = *(struct sockaddr_in *)sa;
s_in.sin_addr.s_addr = htonl (s_in.sin_addr.s_addr);
mu_debug_log_cont ("{AF_INET %s:%d}",
inet_ntoa (s_in.sin_addr), ntohs (s_in.sin_port));
break;
}
case AF_UNIX:
{
struct sockaddr_un *s_un = (struct sockaddr_un *)sa;
if (MU_S_UN_NAME(s_un, salen)[0] == 0)
mu_debug_log_cont ("{AF_UNIX}");
else
mu_debug_log_cont ("{AF_UNIX %s}", s_un->sun_path);
break;
}
default:
mu_debug_log_cont ("{Unsupported family: %d}", sa->sa_family);
}
}
size_t
mu_stpcpy (char **pbuf, size_t *psize, const char *src)
{
size_t slen = strlen (src);
if (pbuf == NULL || *pbuf == NULL)
return slen;
else
{
char *buf = *pbuf;
size_t size = *psize;
if (size > slen)
size = slen;
memcpy (buf, src, size);
*psize -= size;
*pbuf += size;
if (*psize)
**pbuf = 0;
else
(*pbuf)[-1] = 0;
return size;
}
}
void
mu_sockaddr_to_str (const struct sockaddr *sa, int salen,
char *bufptr, size_t buflen,
size_t *plen)
{
char *nbuf;
size_t len = 0;
switch (sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in s_in = *(struct sockaddr_in *)sa;
len += mu_stpcpy (&bufptr, &buflen, inet_ntoa (s_in.sin_addr));
len += mu_stpcpy (&bufptr, &buflen, ":");
if (mu_asprintf (&nbuf, "%hu", ntohs (s_in.sin_port)) == 0)
{
len += mu_stpcpy (&bufptr, &buflen, nbuf);
free (nbuf);
}
break;
}
case AF_UNIX:
{
struct sockaddr_un *s_un = (struct sockaddr_un *)sa;
if (MU_S_UN_NAME(s_un, salen)[0] == 0)
len += mu_stpcpy (&bufptr, &buflen, "anonymous socket");
else
{
len += mu_stpcpy (&bufptr, &buflen, "socket ");
len += mu_stpcpy (&bufptr, &buflen, s_un->sun_path);
}
break;
}
default:
len += mu_stpcpy (&bufptr, &buflen, "{Unsupported family");
if (mu_asprintf (&nbuf, ": %d", sa->sa_family) == 0)
{
len += mu_stpcpy (&bufptr, &buflen, nbuf);
free (nbuf);
}
len += mu_stpcpy (&bufptr, &buflen, "}");
}
if (plen)
*plen = len + 1;
}
char *
mu_sockaddr_to_astr (const struct sockaddr *sa, int salen)
{
size_t size;
char *p;
mu_sockaddr_to_str (sa, salen, NULL, 0, &size);
p = malloc (size);
if (p)
mu_sockaddr_to_str (sa, salen, p, size, NULL);
return p;
}
int
_acl_match (struct _mu_acl_entry *ent, struct sockaddr *sa, int salen)
{
#define RESMATCH(word) \
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9)) \
mu_debug_log_end ("%s; ", word);
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9))
{
struct in_addr a;
mu_debug_log_begin ("Does ");
debug_sockaddr (sa, salen);
mu_debug_log_cont (" match ");
debug_sockaddr (ent->sa, salen);
a.s_addr = ent->netmask;
a.s_addr = htonl (a.s_addr);
mu_debug_log_cont (" netmask %s? ", inet_ntoa (a));
}
if (ent->sa->sa_family != sa->sa_family)
{
RESMATCH ("no");
return 1;
}
switch (ent->sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in *sin_ent = (struct sockaddr_in *)ent->sa;
struct sockaddr_in *sin_item = (struct sockaddr_in *)sa;
if (sin_ent->sin_addr.s_addr !=
(sin_item->sin_addr.s_addr & ent->netmask))
{
RESMATCH ("no (address differs)");
return 1;
}
if (sin_ent->sin_port && sin_item->sin_port
&& sin_ent->sin_port != sin_item->sin_port)
{
RESMATCH ("no (port differs)");
return 1;
}
break;
}
case AF_UNIX:
{
struct sockaddr_un *sun_ent = (struct sockaddr_un *)ent->sa;
struct sockaddr_un *sun_item = (struct sockaddr_un *)sa;
if (MU_S_UN_NAME (sun_ent, ent->salen)[0]
&& MU_S_UN_NAME (sun_item, salen)[0]
&& strcmp (sun_ent->sun_path, sun_item->sun_path))
{
RESMATCH ("no");
return 1;
}
break;
}
}
RESMATCH ("yes");
return 0;
}
struct run_closure
{
unsigned idx;
struct sockaddr *sa;
char *numbuf;
char *portbuf;
int salen;
mu_acl_result_t *result;
};
#define SEQ(s, n, l) \
(((l) == (sizeof(s) - 1)) && memcmp (s, n, l) == 0)
static const char *
acl_getvar (const char *name, size_t nlen, void *data)
{
struct run_closure *rp = data;
if (SEQ ("aclno", name, nlen))
{
if (!rp->numbuf && mu_asprintf (&rp->numbuf, "%u", rp->idx))
return NULL;
return rp->numbuf;
}
switch (rp->sa->sa_family)
{
case AF_INET:
{
struct sockaddr_in *s_in = (struct sockaddr_in *)rp->sa;
if (SEQ ("address", name, nlen))
{
struct in_addr addr = s_in->sin_addr;
addr.s_addr = htonl (addr.s_addr);
return inet_ntoa (addr);
}
if (SEQ ("port", name, nlen))
{
if (!rp->portbuf &&
mu_asprintf (&rp->portbuf, "%hu", ntohs (s_in->sin_port)))
return NULL;
return rp->portbuf;
}
break;
case AF_UNIX:
if (SEQ ("address", name, nlen))
{
struct sockaddr_un *s_un = (struct sockaddr_un *)rp->sa;
if (rp->salen == sizeof (s_un->sun_family))
return NULL;
else
return s_un->sun_path;
}
}
break;
}
return NULL;
}
static int
expand_arg (const char *cmdline, struct run_closure *rp, char **s)
{
int rc;
struct mu_wordsplit ws;
const char *env[3];
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_TRACE, ("Expanding \"%s\"", cmdline));
env[0] = "family";
switch (rp->sa->sa_family)
{
case AF_INET:
env[1] = "AF_INET";
break;
case AF_UNIX:
env[1] = "AF_UNIX";
break;
}
env[2] = NULL;
ws.ws_env = env;
ws.ws_getvar = acl_getvar;
ws.ws_closure = rp;
rc = mu_wordsplit (cmdline, &ws,
MU_WRDSF_NOSPLIT | MU_WRDSF_NOCMD |
MU_WRDSF_ENV | MU_WRDSF_ENV_KV |
MU_WRDSF_GETVAR | MU_WRDSF_CLOSURE);
if (rc == 0)
{
*s = strdup (ws.ws_wordv[0]);
mu_wordsplit_free (&ws);
if (!*s)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("failed: not enough memory."));
return ENOMEM;
}
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_TRACE, ("Expansion: \"%s\". ", *s));
}
else
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("failed: %s", mu_wordsplit_strerror (&ws)));
rc = errno;
}
return rc;
}
static int
spawn_prog (const char *cmdline, int *pstatus, struct run_closure *rp)
{
char *s;
pid_t pid;
if (expand_arg (cmdline, rp, &s))
s = strdup (cmdline);
pid = fork ();
if (pid == 0)
{
int i;
struct mu_wordsplit ws;
if (mu_wordsplit (s, &ws, MU_WRDSF_DEFFLAGS))
{
mu_error (_("cannot split line `%s': %s"), s,
mu_wordsplit_strerror (&ws));
_exit (127);
}
for (i = mu_getmaxfd (); i > 2; i--)
close (i);
execvp (ws.ws_wordv[0], ws.ws_wordv);
_exit (127);
}
free (s);
if (pid == (pid_t)-1)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("cannot fork: %s", mu_strerror (errno)));
return errno;
}
if (pstatus)
{
int status;
waitpid (pid, &status, 0);
if (WIFEXITED (status))
{
status = WEXITSTATUS (status);
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_TRACE,
("Program finished with code %d.", status));
*pstatus = status;
}
else if (WIFSIGNALED (status))
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Program terminated on signal %d.",
WTERMSIG (status)));
return MU_ERR_PROCESS_SIGNALED;
}
else
return MU_ERR_PROCESS_UNKNOWN_FAILURE;
}
return 0;
}
int
_run_entry (void *item, void *data)
{
int status = 0;
struct _mu_acl_entry *ent = item;
struct run_closure *rp = data;
rp->idx++;
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9))
{
const char *s = "undefined";
mu_acl_action_to_string (ent->action, &s);
mu_debug_log_begin ("%d:%s: ", rp->idx, s);
}
if (_acl_match (ent, rp->sa, rp->salen) == 0)
{
switch (ent->action)
{
case mu_acl_accept:
*rp->result = mu_acl_result_accept;
status = 1;
break;
case mu_acl_deny:
*rp->result = mu_acl_result_deny;
status = 1;
break;
case mu_acl_log:
{
char *s;
if (ent->arg && expand_arg (ent->arg, rp, &s) == 0)
{
mu_diag_output (MU_DIAG_INFO, "%s", s);
free (s);
}
else
{
debug_sockaddr (rp->sa, rp->salen);
mu_debug_log_nl ();
}
}
break;
case mu_acl_exec:
spawn_prog (ent->arg, NULL, rp);
break;
case mu_acl_ifexec:
{
int prog_status;
int rc = spawn_prog (ent->arg, &prog_status, rp);
if (rc == 0)
{
switch (prog_status)
{
case 0:
*rp->result = mu_acl_result_accept;
status = 1;
break;
case 1:
*rp->result = mu_acl_result_deny;
status = 1;
}
}
}
break;
}
}
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9))
mu_debug_log_nl ();
return status;
}
int
mu_acl_check_sockaddr (mu_acl_t acl, const struct sockaddr *sa, int salen,
mu_acl_result_t *pres)
{
struct run_closure r;
if (!acl)
return EINVAL;
r.sa = malloc (salen);
if (!r.sa)
return ENOMEM;
memcpy (r.sa, sa, salen);
if (prepare_sa (r.sa))
{
free (r.sa);
return EINVAL;
}
r.salen = salen;
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9))
{
mu_debug_log_begin ("Checking sockaddr ");
debug_sockaddr (r.sa, r.salen);
mu_debug_log_nl ();
}
r.idx = 0;
r.result = pres;
*r.result = mu_acl_result_undefined;
r.numbuf = r.portbuf = NULL;
mu_list_do (acl->aclist, _run_entry, &r);
free (r.numbuf);
free (r.portbuf);
free (r.sa);
return 0;
}
int
mu_acl_check_inaddr (mu_acl_t acl, const struct in_addr *inp,
mu_acl_result_t *pres)
{
struct sockaddr_in cs;
int len = sizeof cs;
cs.sin_family = AF_INET;
cs.sin_addr = *inp;
cs.sin_addr.s_addr = ntohl (cs.sin_addr.s_addr);
return mu_acl_check_sockaddr (acl, (struct sockaddr *) &cs, len, pres);
}
int
mu_acl_check_ipv4 (mu_acl_t acl, unsigned int addr, mu_acl_result_t *pres)
{
struct in_addr in;
in.s_addr = addr;
return mu_acl_check_inaddr (acl, &in, pres);
}
int
mu_acl_check_fd (mu_acl_t acl, int fd, mu_acl_result_t *pres)
{
struct sockaddr_in cs;
socklen_t len = sizeof cs;
if (getpeername (fd, (struct sockaddr *) &cs, &len) < 0)
{
mu_debug (MU_DEBCAT_ACL, MU_DEBUG_ERROR,
("Cannot obtain IP address of client: %s",
mu_strerror (errno)));
return MU_ERR_FAILURE;
}
return mu_acl_check_sockaddr (acl, (struct sockaddr *) &cs, len, pres);
}