/* wydawca - automatic release submission daemon Copyright (C) 2007, 2008, 2009, 2010 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 ")"; size_t size; char *buf; 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) { mu_address_to_string (rcpt, NULL, 0, &size); buf = xmalloc (size + 1); mu_address_to_string (rcpt, buf, size + 1, NULL); mu_header_set_value (hdr, "To", buf, 1); free (buf); if (from_address && mu_header_sget_value (hdr, "From", &sval)) { mu_address_to_string (from_address, NULL, 0, &size); buf = xmalloc (size + 1); mu_address_to_string (from_address, buf, size + 1, NULL); mu_header_set_value (hdr, "From", buf, 1); free (buf); } } 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) { size_t size; char *buf; mu_address_to_string (admin_address, NULL, 0, &size); buf = xmalloc (size + 1); mu_address_to_string (admin_address, buf, size + 1, NULL); logmsg (LOG_DEBUG, _("sending stats to %s"), buf); free (buf); } tc = timer_get_count () * 3; exp = make_stat_expansion (tc + 1); time (&t); exp[0].kw = "date"; exp[0].value = exp[0].storage = xstrdup (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) { size_t size; char *buf; mu_address_to_string (rcpt, NULL, 0, &size); buf = xmalloc (size + 1); mu_address_to_string (rcpt, buf, size + 1, NULL); logmsg (LOG_DEBUG, _("notifying %s (project %s) about %s"), buf, trp->project, notification_event_str (ev)); free (buf); } 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) { size_t size; if (mu_address_to_string (admin_address, NULL, 0, &size) == 0) { size++; def->storage = xmalloc (size); mu_address_to_string (admin_address, def->storage, size, NULL); 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; size_t size; 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_to_string (addr, NULL, 0, &size) == 0) { size++; def->storage = xmalloc (size); mu_address_to_string (addr, def->storage, size, NULL); def->value = def->storage; mu_address_destroy (&addr); } else def->value = ""; return def->value; }