/* This file is part of Smap.
Copyright (C) 2006-2010, 2014, 2016 Sergey Poznyakoff
Smap 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.
Smap 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 Smap. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
static char *dfl_positive_reply = "OK";
static char *dfl_negative_reply = "NOTFOUND";
static char *dfl_onerror_reply = "NOTFOUND";
static size_t dbgid;
typedef int (*query_fun_t)(smap_database_t dbp,
smap_stream_t ostr,
const char *map, const char *key,
struct smap_conninfo const *conninfo);
struct _mu_smap_db {
const char *id;
char *positive_reply;
char *negative_reply;
char *onerror_reply;
char *config_file;
int config_flags;
query_fun_t qfn;
};
struct _mu_smap_result {
const char *db;
const char *map;
const char *key;
struct mu_auth_data *auth;
mu_url_t url;
mu_off_t mbsize;
size_t msgsize;
char *diag;
};
static void
_mu_smap_db_free(struct _mu_smap_db *db)
{
free(db->positive_reply);
free(db->negative_reply);
free(db->onerror_reply);
free(db);
}
static void
free_env(char **env)
{
int i;
for (i = 0; env[i]; i++)
free(env[i]);
}
static char *
mkvar(const char *name, const char *val)
{
char *ptr = malloc(strlen(name) + strlen(val) + 2);
if (ptr) {
strcpy(ptr, name);
strcat(ptr, "=");
strcat(ptr, val);
}
return ptr;
}
static int
mkenv(char **env, struct _mu_smap_result *res)
{
int i = 0;
struct mu_auth_data *auth = res->auth;
char buf[512];
#define MKVAR(n, v) \
do { \
if (!(env[i++] = mkvar(n, v))) \
return 1; \
} while (0)
MKVAR("db", res->db);
MKVAR("key", res->key);
MKVAR("map", res->map);
if (auth) {
MKVAR(MU_AUTH_NAME, auth->name);
MKVAR(MU_AUTH_PASSWD, auth->passwd);
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->uid);
MKVAR(MU_AUTH_UID, buf);
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->gid);
MKVAR(MU_AUTH_GID, buf);
MKVAR(MU_AUTH_GECOS, auth->gecos);
MKVAR(MU_AUTH_DIR, auth->dir);
MKVAR(MU_AUTH_SHELL, auth->shell);
MKVAR(MU_AUTH_MAILBOX,
auth->mailbox ? auth->mailbox :
res->url ? mu_url_to_string(res->url) : "");
snprintf(buf, sizeof buf, "%lu", (unsigned long) auth->quota);
MKVAR(MU_AUTH_QUOTA, buf);
snprintf(buf, sizeof buf, "%lu", (unsigned long) res->mbsize);
MKVAR("mbsize", buf);
snprintf(buf, sizeof buf, "%lu", (unsigned long) res->msgsize);
MKVAR("msgsize", buf);
}
if (res->diag)
MKVAR("diag", res->diag);
env[i] = NULL;
return 0;
}
static int
expand_reply_text(const char *arg, struct _mu_smap_result *res, char **repl)
{
int rc;
char *env[16];
struct wordsplit ws;
if (mkenv(env, res)) {
mu_error("not enough memory");
free_env(env);
return 1;
}
ws.ws_env = (const char **) env;
ws.ws_error = smap_error;
rc = wordsplit(arg, &ws,
WRDSF_NOSPLIT |
WRDSF_NOCMD |
WRDSF_ENV |
WRDSF_ERROR |
WRDSF_SHOWERR);
free_env(env);
if (rc)
return 1;
*repl = ws.ws_wordv[0];
ws.ws_wordv[0] = NULL;
wordsplit_free(&ws);
return 0;
}
static int
_mu_auth_query(smap_database_t dbp,
smap_stream_t ostr,
const char *map, const char *key,
struct smap_conninfo const *conninfo)
{
struct _mu_smap_db *mdb = (struct _mu_smap_db *)dbp;
struct mu_auth_data *auth = mu_get_auth_by_name(key);
struct _mu_smap_result res;
char *reply;
int rc;
res.db = mdb->id;
res.map = map;
res.key = key;
res.auth = auth;
res.mbsize = 0;
res.msgsize = 0;
res.diag = NULL;
res.url = NULL;
if (!auth)
rc = expand_reply_text(mdb->negative_reply, &res, &reply);
else {
rc = expand_reply_text(mdb->positive_reply, &res, &reply);
mu_auth_data_free(auth);
}
if (rc == 0) {
smap_stream_printf(ostr, "%s\n", reply);
free(reply);
}
return rc;
}
static int
mod_mailutils_query(smap_database_t dbp,
smap_stream_t ostr,
const char *map, const char *key,
struct smap_conninfo const *conninfo)
{
struct _mu_smap_db *mdb = (struct _mu_smap_db *)dbp;
return mdb->qfn(dbp, ostr, map, key, conninfo);
}
static int
switch_user_id(struct mu_auth_data *auth, int user)
{
int rc;
uid_t uid;
if (!auth || auth->change_uid == 0)
return 0;
if (user)
uid = auth->uid;
else
uid = 0;
rc = setreuid(0, uid);
if (rc < 0)
mu_error("setreuid(0, %d): %s (r=%d, e=%d)",
uid, strerror(errno), getuid(), geteuid());
return rc;
}
static int
checksize_user(struct _mu_smap_db *mdb, smap_stream_t ostr,
struct _mu_smap_result *res,
char **preply)
{
int status, rc = 0;
struct mu_auth_data *auth = res->auth;
mu_mailbox_t mbox;
status = mu_mailbox_create_default(&mbox, auth->mailbox);
if (status) {
res->diag = "local system error";
mu_error("could not create mailbox `%s': %s",
auth->mailbox, mu_strerror(status));
return 0;
}
mu_mailbox_get_url(mbox, &res->url);
status = mu_mailbox_open(mbox, MU_STREAM_READ);
if (status) {
res->diag = "local system error";
mu_error("could not open mailbox `%s': %s",
auth->mailbox, mu_strerror(status));
} else {
mu_off_t size;
status = mu_mailbox_get_size(mbox, &size);
if (status) {
res->diag = "local system error";
mu_error("could not get size for `%s': %s",
auth->mailbox, mu_strerror(status));
} else {
char *stat;
res->mbsize = size;
if (!auth->quota) {
stat = mdb->positive_reply;
res->diag = "NOQUOTA";
} else if (size >= auth->quota) {
stat = mdb->negative_reply;
res->diag = "mailbox quota exceeded "
"for this recipient";
} else if (res->msgsize
&& size + res->msgsize >= auth->quota) {
stat = mdb->negative_reply;
res->diag = "message would exceed "
"maximum mailbox size "
"for this recipient";
} else {
stat = mdb->positive_reply;
res->diag = "QUOTAOK";
}
rc = expand_reply_text(stat, res, preply);
}
mu_mailbox_close(mbox);
}
mu_mailbox_destroy(&mbox);
return rc;
}
static int
checksize(struct _mu_smap_db *mdb, smap_stream_t ostr,
const char *user, struct _mu_smap_result *res,
char **preply)
{
struct mu_auth_data *auth;
int status;
*preply = NULL;
auth = mu_get_auth_by_name(user);
if (!auth) {
res->diag = "user not found";
smap_debug(dbgid, 1, ("%s: user not found", user));
return 0;
}
if (switch_user_id(auth, 1)) {
res->diag = "local system error";
return 0;
}
res->auth = auth;
status = checksize_user(mdb, ostr, res, preply);
switch_user_id(auth, 0);
mu_auth_data_free(auth);
res->auth = NULL;
return status;
}
static int
_mu_mbq_query(smap_database_t dbp,
smap_stream_t ostr,
const char *map, const char *key,
struct smap_conninfo const *conninfo)
{
struct _mu_smap_db *mdb = (struct _mu_smap_db *)dbp;
char *user = strdup(key);
char *p;
size_t len;
struct _mu_smap_result res;
char *reply;
int rc;
memset(&res, 0, sizeof(res));
res.db = mdb->id;
res.map = map;
res.key = key;
len = strcspn(user, " \t");
if (user[len]) {
char *q;
unsigned long n;
user[len++] = 0;
p = user + len;
if (strncmp(p, "SIZE=", 5) == 0)
p += 5;
n = strtoul(p, &q, 10);
if (*q == 0)
res.msgsize = n;
else
smap_debug(dbgid, 1,
("ignoring junk after %s", user + len));
}
rc = checksize(mdb, ostr, user, &res, &reply);
if (!rc && !reply)
rc = expand_reply_text(mdb->onerror_reply, &res, &reply);
if (rc == 0) {
smap_stream_printf(ostr, "%s\n", reply);
free(reply);
}
free(user);
return rc;
}
static const char *capa[] = {
"auth",
"common",
"debug",
"mailbox",
"logging",
NULL
};
static struct mu_cfg_param cfg_param[] = {
{ "positive-reply", mu_c_string, &dfl_positive_reply, 0, NULL,
"set default positive reply text" },
{ "negative-reply", mu_c_string, &dfl_negative_reply, 0, NULL,
"set default negative reply text" },
{ "onerror-reply", mu_c_string, &dfl_onerror_reply, 0, NULL,
"set default error reply text" },
{ NULL }
};
struct cap_buf {
int err;
char **capa;
size_t numcapa;
size_t maxcapa;
};
static int
cap_buf_init(struct cap_buf *bp)
{
bp->err = 0;
bp->numcapa = 0;
bp->maxcapa = 2;
bp->capa = calloc(bp->maxcapa, sizeof bp->capa[0]);
if (!bp->capa) {
mu_error ("%s", mu_strerror(errno));
bp->err = 1;
return 1;
}
bp->capa[0] = NULL;
return 0;
}
static int
cap_buf_add(struct cap_buf *bp, char *str)
{
if (bp->err)
return 1;
if (bp->numcapa == bp->maxcapa) {
size_t n = bp->maxcapa * 2;
char **p = realloc(bp->capa, n * sizeof bp->capa[0]);
if (!p) {
mu_error("%s", mu_strerror(errno));
bp->err = 1;
return 1;
}
bp->capa = p;
bp->maxcapa = n;
}
bp->capa[bp->numcapa] = str;
if (str)
bp->numcapa++;
return 0;
}
static void
cap_buf_free(struct cap_buf *bp)
{
free(bp->capa);
}
static int
_reg_action(void *item, void *data)
{
struct cap_buf *bp = data;
return cap_buf_add(bp, item);
}
static int
init_cfg_capa()
{
int i;
struct cap_buf cb;
for (i = 0; capa[i]; i++)
mu_gocs_register_std(capa[i]);
if (cap_buf_init(&cb) == 0) {
mu_gocs_enumerate(_reg_action, &cb);
cap_buf_add (&cb, NULL);
}
if (cb.err)
return 1;
mu_libcfg_init(cb.capa);
cap_buf_free(&cb);
return 0;
}
struct _smap_log_stream
{
struct _mu_stream base;
smap_stream_t transport;
};
static int
_smap_log_stream_write (struct _mu_stream *stream, const char *buf,
size_t size, size_t *pret)
{
struct _smap_log_stream *sp = (struct _smap_log_stream *)stream;
return smap_stream_write(sp->transport, buf, size, pret);
}
static int
_smap_log_flush (struct _mu_stream *stream)
{
struct _smap_log_stream *sp = (struct _smap_log_stream *)stream;
return smap_stream_flush(sp->transport);
}
static int
_smap_log_stream_ioctl (struct _mu_stream *stream, int code, int opcode,
void *arg)
{
struct _smap_log_stream *sp = (struct _smap_log_stream *)stream;
switch (code) {
case MU_IOCTL_LOGSTREAM:
switch (opcode) {
case MU_IOCTL_LOGSTREAM_SET_SEVERITY:
if (!arg)
return EINVAL;
smap_stream_flush(sp->transport);
if (*(unsigned*) arg == MU_LOG_DEBUG)
sp->transport = smap_debug_str;
else
sp->transport = smap_error_str;
return 0;
}
}
return ENOSYS;
}
static int
_smap_log_stream_create (mu_stream_t *pstr)
{
struct _smap_log_stream *sp;
sp = (struct _smap_log_stream *)
_mu_stream_create(sizeof(*sp), MU_STREAM_WRITE);
if (!sp)
return ENOMEM;
sp->base.write = _smap_log_stream_write;
sp->base.flush = _smap_log_flush;
sp->base.ctl = _smap_log_stream_ioctl;
sp->transport = smap_error_str;
*pstr = (mu_stream_t) sp;
mu_stream_set_buffer(*pstr, smap_buffer_line, 1024);
return 0;
}
static int
create_log_stream (mu_stream_t *pstream)
{
int rc;
mu_stream_t transport, str;
rc = _smap_log_stream_create(&transport);
if (rc) {
smap_error("cannot create log stream: %s", mu_strerror(rc));
return 1;
} else {
mu_stream_t flt;
char *fltargs[3] = { "INLINE-COMMENT", };
mu_asprintf(&fltargs[1], "%s: ", mu_program_name);
fltargs[2] = NULL;
rc = mu_filter_create_args(&flt, transport,
"INLINE-COMMENT",
2, (const char**)fltargs,
MU_FILTER_ENCODE, MU_STREAM_WRITE);
free(fltargs[1]);
if (rc) {
smap_error("cannot open output filter stream: %s",
mu_strerror(rc));
/* try to continue anyway */
} else {
mu_stream_unref(transport);
transport = flt;
mu_stream_set_buffer(transport, mu_buffer_line, 0);
}
rc = mu_log_stream_create(&str, transport);
mu_stream_unref(transport);
if (rc) {
smap_error("cannot create mailutils logger stream: %s",
mu_strerror(rc));
return 1;
}
*pstream = str;
}
return 0;
}
#ifndef MU_PARSE_CONFIG_PLAIN
# define MU_PARSE_CONFIG_PLAIN 0
#endif
static void
init_hints(struct mu_cfg_parse_hints *hints)
{
hints->flags |= MU_CFHINT_SITE_FILE | MU_CFHINT_PROGRAM;
hints->site_file = mu_site_config_file();
hints->program = (char*) mu_program_name;
}
static int
mod_mailutils_init(int argc, char **argv)
{
int rc;
struct mu_cfg_tree *parse_tree = NULL;
struct mu_cfg_parse_hints hints = { 0 };
mu_stream_t logstr;
struct smap_option init_option[] = {
{ SMAP_OPTSTR(positive-reply), smap_opt_string,
&dfl_positive_reply },
{ SMAP_OPTSTR(negative-reply), smap_opt_string,
&dfl_negative_reply },
{ SMAP_OPTSTR(onerror-reply), smap_opt_string,
&dfl_onerror_reply },
{ SMAP_OPTSTR(config-verbose), smap_opt_bitmask,
&hints.flags, { MU_CF_VERBOSE } },
{ SMAP_OPTSTR(config-dump), smap_opt_bitmask,
&hints.flags, { MU_CF_DUMP } },
{ NULL }
};
mu_set_program_name("smap-mailutils"); /**argv++*/
/* Create MU log stream. Reference it twice lest it gets released
during mu_gocs_flush (see below) */
if (create_log_stream (&logstr))
return 1;
mu_stream_ref (logstr);
mu_strerr = logstr;
dbgid = smap_debug_alloc("mailutils");
if (smap_parseopt(init_option, argc, argv, 0, NULL))
return 1;
init_hints(&hints);
MU_AUTH_REGISTER_ALL_MODULES();
/* register the formats. */
mu_register_all_mbox_formats();
if (init_cfg_capa())
return 1;
rc = mu_cfg_parse_config(&parse_tree, &hints);
if (rc == 0)
rc = mu_cfg_tree_reduce(parse_tree, &hints, cfg_param, NULL);
mu_gocs_flush();
/* mu_gocs_flush may have reset the error stream, so restore it */
mu_stream_unref(mu_strerr);
mu_strerr = logstr;
mu_cfg_destroy_tree(&parse_tree);
return !!(rc || mu_cfg_error_count);
}
static smap_database_t
mod_mailutils_init_db(const char *dbid, int argc, char **argv)
{
struct _mu_smap_db *db;
char *positive_reply = NULL;
char *negative_reply = NULL;
char *onerror_reply = NULL;
#define MODE_AUTH 0
#define MODE_MBQ 1
static const char *mode_choice[] = { "auth", "mbq", NULL };
static query_fun_t qfn_tab[] = { _mu_auth_query, _mu_mbq_query };
int mode = MODE_AUTH;
char *config_file = NULL;
int cfgflags = MU_PARSE_CONFIG_PLAIN;
struct smap_option init_option[] = {
{ SMAP_OPTSTR(mode), smap_opt_enum, &mode,
{ enumstr: mode_choice } },
{ SMAP_OPTSTR(positive-reply), smap_opt_string,
&positive_reply },
{ SMAP_OPTSTR(negative-reply), smap_opt_string,
&negative_reply },
{ SMAP_OPTSTR(onerror-reply), smap_opt_string,
&onerror_reply },
{ SMAP_OPTSTR(config-file), smap_opt_string,
&config_file },
{ SMAP_OPTSTR(config-verbose), smap_opt_bitmask,
&cfgflags, { MU_CF_VERBOSE } },
{ SMAP_OPTSTR(config-dump), smap_opt_bitmask,
&cfgflags, { MU_CF_DUMP } },
{ NULL }
};
if (smap_parseopt(init_option, argc, argv, 0, NULL))
return NULL;
db = malloc(sizeof(*db));
if (!db) {
mu_error("not enough memory");
return NULL;
}
db->id = dbid;
db->positive_reply = positive_reply ?
positive_reply : strdup(dfl_positive_reply);
db->negative_reply = negative_reply ?
negative_reply : strdup(dfl_negative_reply);
db->onerror_reply = onerror_reply ?
onerror_reply : strdup(dfl_onerror_reply);
db->config_file = config_file;
db->config_flags = cfgflags;
if (!db->positive_reply || !db->negative_reply || !db->onerror_reply) {
_mu_smap_db_free(db);
return NULL;
}
db->qfn = qfn_tab[mode];
return (smap_database_t)db;
}
static int
mod_mailutils_free_db(smap_database_t dbp)
{
struct _mu_smap_db *db = (struct _mu_smap_db *)dbp;
_mu_smap_db_free(db);
return 0;
}
static int
mod_mailutils_open(smap_database_t dbp)
{
struct _mu_smap_db *db = (struct _mu_smap_db *)dbp;
if (db->config_file) {
struct mu_cfg_parse_hints hints = { db->config_flags };
mu_cfg_tree_t *tree = NULL;
int rc;
init_hints(&hints);
if (init_cfg_capa())
return 1;
mu_cfg_error_count = 0;
mu_cfg_parser_verbose = 0;
if (db->config_flags & MU_CF_VERBOSE)
mu_cfg_parser_verbose++;
if (db->config_flags & MU_CF_DUMP)
mu_cfg_parser_verbose++;
rc = mu_cfg_parse_file(&tree, db->config_file,
db->config_flags);
if (rc) {
mu_error("error parsing %s: %s", db->config_file,
mu_strerror(rc));
return 1;
}
mu_cfg_tree_postprocess(tree, &hints);
rc = mu_cfg_tree_reduce(tree, &hints, cfg_param, NULL);
mu_gocs_flush();
mu_cfg_destroy_tree(&tree);
if (rc || mu_cfg_error_count) {
mu_error("errors parsing %s", db->config_file);
return 1;
}
}
return 0;
}
struct smap_module SMAP_EXPORT(mailutils, module) = {
SMAP_MODULE_VERSION,
SMAP_CAPA_DEFAULT,
mod_mailutils_init,
mod_mailutils_init_db,
mod_mailutils_free_db,
mod_mailutils_open, /* smap_open */
NULL, /* smap_close */
mod_mailutils_query,
NULL, /* smap_xform */
};