/* wydawca - automatic release submission daemon
Copyright (C) 2007-2011 Sergey Poznyakoff
Wydawca 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 of the License, or (at your
option) any later version.
Wydawca 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 wydawca. If not, see . */
#include "wydawca.h"
#include
#include
int mailer_opened;
mu_mailer_t mailer;
mu_address_t admin_address;
mu_address_t from_address;
unsigned long mail_admin_mask;
unsigned long owner_notification_flags;
char *user_message_template[MAX_EVENT];
char *admin_stat_message;
char *admin_stat_sign_key;
void
mail_init ()
{
if (!mailer)
{
int rc;
if ((rc = mu_mailer_create (&mailer, NULL)))
{
const char *url = NULL;
mu_mailer_get_url_default (&url);
logmsg (LOG_ERR, _("cannot create default mailer `%s': %s"),
url, mu_strerror (rc));
}
}
}
static ssize_t
mu_stream_data_read_cb (void *handle, void *buffer, size_t size)
{
mu_stream_t str = handle;
size_t nread;
int rc;
rc = mu_stream_read (str, buffer, size, &nread);
if (rc)
{
logmsg (LOG_ERR, "mu_stream_read: %s", mu_strerror (rc));
errno = EIO;
return -1;
}
return nread;
}
static int
check_sign_result (gpgme_sign_result_t result, gpgme_sig_mode_t type)
{
gpgme_new_signature_t sign;
if (result->invalid_signers)
{
logmsg (LOG_ERR, _("GPGME: invalid signer found: %s"),
result->invalid_signers->fpr);
return 1;
}
if (!result->signatures)
{
logmsg (LOG_ERR, _("GPGME: no signatures created"));
return 1;
}
for (sign = result->signatures; sign; sign = sign->next)
{
if (sign->type != type)
{
logmsg (LOG_ERR, _("GPGME: wrong type of signature created"));
return 1;
}
}
/* FIXME: fingerprint? */
return 0;
}
static int
gpg_sign (gpgme_data_t *output, gpgme_data_t input, const char *sign_keys)
{
gpgme_ctx_t ctx;
gpgme_error_t err = 0;
gpgme_key_t key;
int ret;
err = gpgme_new (&ctx);
if (err)
{
logmsg (LOG_ERR, _("GPGME: cannot create context: %s"),
gpgme_strerror (err));
return 1;
}
err = gpgme_op_keylist_start (ctx, sign_keys, 0);
if (!err)
{
while ((err = gpgme_op_keylist_next (ctx, &key)) == 0)
{
err = gpgme_signers_add (ctx, key);
gpgme_key_release (key);
}
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
logmsg (LOG_ERR, _("GPGME: cannot list keys: %s"),
gpgme_strerror (err));
gpgme_release (ctx);
return 1;
}
err = gpgme_data_new (output);
if (err)
{
logmsg (LOG_ERR, _("%s: GPGME error: %s"),
"gpgme_data_new",
gpgme_strerror (err));
gpgme_release (ctx);
return 1;
}
/* FIXME: Passphrase */
gpgme_set_textmode (ctx, 1);
gpgme_set_armor (ctx, 1);
err = gpgme_op_sign (ctx, input, *output, GPGME_SIG_MODE_CLEAR);
if (err)
{
logmsg (LOG_ERR, _("%s: GPGME error: %s"),
"gpgme_op_sign",
gpgme_strerror (err));
ret = 1;
}
else
{
ret = check_sign_result (gpgme_op_sign_result (ctx),
GPGME_SIG_MODE_CLEAR);
#if 0
/* FIXME: */
if (debug_level > 1)
gpgme_debug_info (ctx);
#endif
}
gpgme_release (ctx);
return ret;
}
static int
sign_message (mu_message_t *pmsg, const char *key)
{
mu_message_t msg = *pmsg;
mu_message_t newmsg;
mu_body_t body;
mu_header_t hdr;
mu_stream_t istr, ostr, bodystr;
int rc;
struct gpgme_data_cbs cbs;
gpgme_data_t input, output;
gpgme_error_t err;
char *buf = NULL;
size_t size = 0;
size_t nread;
if (debug_level)
logmsg (LOG_DEBUG, _("signing message as %s"), key);
if (wydawca_gpg_homedir)
{
if (debug_level > 1)
logmsg (LOG_DEBUG, _("setting GNUPG home directory: %s"),
wydawca_gpg_homedir);
setenv ("GNUPGHOME", wydawca_gpg_homedir, 1);
}
if ((rc = mu_message_get_body (msg, &body)))
{
logmsg (LOG_ERR, "mu_message_get_body: %s", mu_strerror (rc));
return 1;
}
if ((rc = mu_body_get_streamref (body, &bodystr)))
{
logmsg (LOG_ERR, "mu_message_get_stream: %s", mu_strerror (rc));
return 1;
}
memset (&cbs, 0, sizeof (cbs));
cbs.read = mu_stream_data_read_cb;
err = gpgme_data_new_from_cbs (&input, &cbs, &bodystr);
if (err)
{
logmsg (LOG_ERR, "gpgme_data_new_from_cbs: %s",
gpgme_strerror (rc));
return 1;
}
rc = gpg_sign (&output, input, key);
mu_stream_unref (bodystr);
if (rc)
return 1;
if (gpgme_data_seek (output, 0, SEEK_SET) == -1)
{
logmsg (LOG_ERR, "gpgme_data_seek: %s", strerror (errno));
return 1;
}
mu_message_create (&newmsg, NULL);
mu_message_get_streamref (newmsg, &ostr);
/* Copy headers */
mu_message_get_header (msg, &hdr);
mu_header_get_streamref (hdr, &istr);
rc = mu_stream_copy (ostr, istr, 0, NULL);
if (rc)
logmsg (LOG_ERR, "mu_stream_copy: %s", mu_strerror (rc));
else
{
while ((nread = gpgme_data_read (output, buf, size)) > 0)
{
rc = mu_stream_write (ostr, buf, nread, NULL);
if (rc)
{
logmsg (LOG_ERR, "mu_stream_write: %s", mu_strerror (rc));
break;
}
}
if (rc == 0)
{
mu_message_destroy (&msg, mu_message_get_owner (msg));
*pmsg = newmsg;
}
}
mu_stream_unref (istr);
mu_stream_unref (ostr);
if (rc)
mu_message_destroy (&newmsg, mu_message_get_owner (msg));
gpgme_data_release (output);
free (buf);
return rc;
}
void
mail_send_message (mu_address_t rcpt, const char *text,
const char *signer_key)
{
int rc;
mu_message_t msg;
mu_stream_t stream = NULL;
mu_header_t hdr;
static char *x_mailer = "wydawca (" PACKAGE_STRING ")";
const char *sval;
mu_static_memory_stream_create (&stream, text, strlen (text));
rc = mu_stream_to_message (stream, &msg);
mu_stream_unref (stream);
if (rc)
{
logmsg (LOG_CRIT, _("cannot create message: %s"), mu_strerror (rc));
return;
}
mu_message_get_header (msg, &hdr);
mu_header_append (hdr, "X-Mailer", x_mailer);
if (rcpt)
{
const char *s;
if (mu_address_sget_printable (rcpt, &s) == 0)
mu_header_set_value (hdr, "To", s, 1);
if (from_address && mu_header_sget_value (hdr, "From", &sval))
{
if (mu_address_sget_printable (from_address, &s) == 0)
mu_header_set_value (hdr, "From", s, 1);
}
}
if (debug_level > 1)
{
mu_debug_level_t level;
mu_debug_get_category_level (MU_DEBCAT_MAILER, &level);
level |= MU_DEBUG_LEVEL_MASK (MU_DEBUG_TRACE0) |
MU_DEBUG_LEVEL_MASK (MU_DEBUG_PROT);
if (debug_level > 2)
level |= MU_DEBUG_LEVEL_MASK (MU_DEBUG_TRACE7);
mu_debug_set_category_level (MU_DEBCAT_MAILER, level);
}
if (!mailer_opened)
{
if ((rc = mu_mailer_open (mailer, 0)))
{
mu_url_t url = NULL;
mu_mailer_get_url (mailer, &url);
logmsg (LOG_CRIT, _("opening mailer `%s' failed: %s"),
url ? mu_url_to_string (url) : "(unknown URL)",
mu_strerror (rc));
return;
}
mailer_opened = 1;
}
if (signer_key)
sign_message (&msg, signer_key);
if (!dry_run_mode)
{
rc = mu_mailer_send_message (mailer, msg, from_address, rcpt);
if (rc)
logmsg (LOG_CRIT, _("cannot send message: %s"), mu_strerror (rc));
}
mu_message_destroy (&msg, mu_message_get_owner (msg));
}
void
mail_finish ()
{
if (mailer_opened)
{
mu_mailer_close (mailer);
mailer_opened = 0;
}
}
struct message_template
{
char *name;
char *text;
/* int mime; for future use */
};
static struct grecs_symtab *tmpl_table;
/* Register a template. */
void
register_message_template (const char *name, const char *text)
{
struct message_template key, *tmpl;
int install = 1;
key.name = (char*) text;
if (!tmpl_table)
{
tmpl_table = grecs_symtab_create_default (sizeof (key));
if (!tmpl_table)
grecs_alloc_die ();
}
tmpl = grecs_symtab_lookup_or_install (tmpl_table, &key, &install);
if (!tmpl)
grecs_alloc_die();
if (!install)
grecs_warning (NULL, 0, "template %s already registered", text);
}
const char *
resolve_message_template (const char *name)
{
if (name[0] == '@')
{
if (name[1] == '@')
return name + 1;
else if (!tmpl_table)
return NULL;
else
{
struct message_template *p, key;
key.name = (char*) name + 1;
p = grecs_symtab_lookup_or_install (tmpl_table, &key, NULL);
return p ? p->text : NULL;
}
}
return name;
}
void
mail_stats ()
{
struct metadef *exp;
time_t t;
const char *tmpl;
char *text;
size_t tc;
if (!admin_stat_message || !stat_mask_p (mail_admin_mask) || !mailer)
return;
if (!admin_address)
{
logmsg (LOG_ERR, _("cannot mail statistics: admin-address not defined"));
return;
}
if (debug_level)
{
const char *s;
if (mu_address_sget_printable (admin_address, &s) == 0)
logmsg (LOG_DEBUG, _("sending stats to %s"), s);
}
tc = timer_get_count () * 3;
exp = make_stat_expansion (tc + 1);
time (&t);
exp[0].kw = "date";
exp[0].value = exp[0].storage = grecs_strdup (ctime (&t));
exp[0].value [strlen (exp[0].value) - 1] = 0;
timer_fill_meta (exp + 1, tc);
tmpl = resolve_message_template (admin_stat_message);
if (!tmpl)
{
logmsg (LOG_ERR, _("undefined message reference: %s"),
admin_stat_message);
return;
}
text = meta_expand_string (tmpl, exp, NULL, NULL, NULL);
mail_send_message (admin_address, text, admin_stat_sign_key);
free (text);
meta_free (exp);
timer_free_meta (exp + 1, tc);
free (exp);
}
mu_address_t
get_uploader_email (struct uploader_info *info, struct file_triplet *trp,
const char **errp)
{
mu_address_t addr;
mu_address_t rcpt = NULL;
int rc;
if (!info)
return NULL;
rc = mu_address_create (&addr, info->email);
if (rc)
{
*errp = mu_strerror (rc);
return NULL;
}
mu_address_set_personal (addr, 1, info->realname);
/* This hack makes sure that mu_address_to_string (rcpt) will
return full email address (with personal part) */
mu_address_union (&rcpt, addr);
mu_address_destroy (&addr);
return rcpt;
}
mu_address_t
get_recipient (struct dictionary *dict, struct file_triplet *trp,
const char **errp)
{
unsigned nrows, ncols, i;
mu_address_t rcpt = NULL;
char *text;
int rc;
void *md;
if (dict->type == dictionary_none)
{
*errp = N_("dictionary is not configured");
return NULL;
}
md = dictionary_open (dict);
if (!md)
{
*errp = N_("failed to open dictionary");
return NULL;
}
text = triplet_expand_dictionary_query (dict, md, trp);
rc = dictionary_lookup (dict, md, text);
free (text);
if (rc)
{
*errp = N_("cannot obtain recipient emails");
dictionary_close (dict, md);
return NULL;
}
nrows = dictionary_num_rows (dict);
ncols = dictionary_num_cols (dict);
if (nrows == 0)
{
*errp = N_("cannot obtain recipient emails");
return NULL;
}
for (i = 0; i < nrows; i++)
{
mu_address_t addr;
const char *str = dictionary_result (dict, md, i, 0);
if (mu_address_create (&addr, str))
continue;
if (ncols > 0)
{
str = dictionary_result (dict, md, i, 1);
if (str)
mu_address_set_personal (addr, 1, str);
}
mu_address_union (&rcpt, addr);
mu_address_destroy (&addr);
}
dictionary_close (dict, md);
return rcpt;
}
void
do_notify (struct file_triplet *trp, enum notification_event ev,
struct notification *ntf)
{
mu_address_t rcpt = NULL;
const char *errp;
const char *msg;
switch (ntf->tgt)
{
case notify_read:
rcpt = NULL;
break;
case notify_admin:
rcpt = mu_address_dup (admin_address);
break;
case notify_user:
rcpt = get_uploader_email (trp->uploader, trp, &errp);
break;
case notify_owner:
rcpt = get_recipient (trp->spool->dictionary[project_owner_dict],
trp, &errp);
}
if (!rcpt && ntf->tgt != notify_read)
{
logmsg (LOG_ERR, _("not notifying %s (project %s) about %s: %s"),
notification_target_str (ntf->tgt),
trp->project,
notification_event_str (ev), gettext (errp));
return;
}
if (debug_level)
{
if (rcpt)
{
const char *s;
if (mu_address_sget_printable (rcpt, &s) == 0)
logmsg (LOG_DEBUG, _("notifying %s (project %s) about %s"),
s, trp->project, notification_event_str (ev));
}
else
logmsg (LOG_DEBUG,
_("notifying message recipients (project %s) about %s"),
trp->project, notification_event_str (ev));
}
msg = resolve_message_template (ntf->msg);
if (!msg)
logmsg (LOG_ERR, _("undefined message reference: %s"), ntf->msg);
else
{
char *text = triplet_expand_param (msg, trp);
mail_send_message (rcpt, text, ntf->sign_keys);
free (text);
}
mu_address_destroy (&rcpt);
}
void
notify (struct notification *notification_list,
struct file_triplet *trp, enum notification_event ev)
{
struct notification *p;
for (p = notification_list; p; p = p->next)
if (p->ev == ev)
do_notify (trp, ev, p);
/* FIXME */
}
const char *
expand_email_admin (struct metadef *def, void *data)
{
if (mu_address_aget_printable (admin_address, &def->storage) == 0)
def->value = def->storage;
else
def->value = "";
return def->value;
}
const char *
expand_email_owner (struct metadef *def, void *data)
{
struct file_triplet *trp = data;
mu_address_t addr;
const char *errp;
addr = get_recipient (trp->spool->dictionary[project_owner_dict],
trp, &errp);
if (!addr)
{
logmsg (LOG_ERR, _("cannot get email of the %s's owner: %s"),
trp->project, gettext (errp));
def->value = "";
}
else
{
if (mu_address_aget_printable (addr, &def->storage) == 0)
def->value = def->storage;
else
def->value = "";
mu_address_destroy (&addr);
}
return def->value;
}