/* 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
#include
#include
#include
#include
#ifndef MU_INADDR_BYTES
#define MU_INADDR_BYTES 16
#endif
struct _mu_acl_entry
{
mu_acl_action_t action;
void *arg;
struct mu_cidr cidr;
};
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? */
}
int
mu_acl_entry_create (struct _mu_acl_entry **pent,
mu_acl_action_t action, void *data,
struct mu_cidr *cidr)
{
struct _mu_acl_entry *p = malloc (sizeof (*p));
if (!p)
return EINVAL;
p->action = action;
p->arg = data;
memcpy (&p->cidr, cidr, sizeof (p->cidr));
*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 mu_cidr *cidr)
{
int rc;
struct _mu_acl_entry *ent;
if (!acl)
return EINVAL;
rc = mu_acl_entry_create (&ent, act, data, cidr);
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 mu_cidr *cidr)
{
int rc;
struct _mu_acl_entry *ent;
if (!acl)
return EINVAL;
rc = mu_acl_entry_create (&ent, act, data, cidr);
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 mu_cidr *cidr)
{
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, cidr);
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;
}
struct run_closure
{
unsigned idx;
struct mu_cidr addr;
char ipstr[40];
char *addrstr;
char *numbuf;
mu_acl_result_t *result;
};
int
_acl_match (struct _mu_acl_entry *ent, struct run_closure *rp)
{
#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))
{
char *s;
if (ent->cidr.len == 0)
s = strdup ("any");
mu_cidr_format (&ent->cidr, 0, &s);
if (!rp->addrstr)
mu_cidr_format (&rp->addr, MU_CIDR_FMT_ADDRONLY, &rp->addrstr);
mu_debug_log_begin ("Does %s match %s? ", s, rp->addrstr);
free (s);
}
if (ent->cidr.len > 0 && mu_cidr_match (&ent->cidr, &rp->addr))
{
RESMATCH ("no");
return 1;
}
RESMATCH ("yes");
return 0;
}
#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;
}
if (SEQ ("address", name, nlen))
{
if (!rp->addrstr)
mu_cidr_format (&rp->addr, MU_CIDR_FMT_ADDRONLY, &rp->addrstr);
return rp->addrstr;
}
#if 0
/* FIXME?: */
if (SEQ ("port", name, nlen))
{
if (!rp->portbuf &&
mu_asprintf (&rp->portbuf, "%hu", ntohs (s_in->sin_port)))
return NULL;
return rp->portbuf;
}
#endif
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->addr.family)
{
case AF_INET:
env[1] = "AF_INET";
break;
#ifdef MAILUTILS_IPV6
case AF_INET6:
env[1] = "AF_INET6";
break;
#endif
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) == 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
{
if (!rp->addrstr)
mu_cidr_format (&rp->addr, MU_CIDR_FMT_ADDRONLY,
&rp->addrstr);
mu_diag_output (MU_DIAG_INFO, "%s", rp->addrstr);
}
}
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_stream_flush (mu_strerr);
return status;
}
int
mu_acl_check_sockaddr (mu_acl_t acl, const struct sockaddr *sa, int salen,
mu_acl_result_t *pres)
{
int rc;
struct run_closure r;
if (!acl)
return EINVAL;
memset (&r, 0, sizeof (r));
if (sa->sa_family == AF_UNIX)
{
*pres = mu_acl_result_accept;
return 0;
}
rc = mu_cidr_from_sockaddr (&r.addr, sa);
if (rc)
return rc;
if (mu_debug_level_p (MU_DEBCAT_ACL, MU_DEBUG_TRACE9))
{
mu_cidr_format (&r.addr, MU_CIDR_FMT_ADDRONLY, &r.addrstr);
mu_debug_log_begin ("Checking sockaddr %s", r.addrstr);
mu_debug_log_nl ();
}
r.idx = 0;
r.result = pres;
*r.result = mu_acl_result_undefined;
r.numbuf = NULL;
mu_list_do (acl->aclist, _run_entry, &r);
free (r.numbuf);
free (r.addrstr);
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)
{
union
{
struct sockaddr sa;
struct sockaddr_in in;
#ifdef MAILUTILS_IPV6
struct sockaddr_in6 in6;
#endif
} addr;
socklen_t len = sizeof addr;
if (getpeername (fd, &addr.sa, &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, &addr.sa, len, pres);
}