diff options
-rw-r--r-- | doc/Makefile.am | 9 | ||||
-rw-r--r-- | src/config.c | 9 | ||||
-rw-r--r-- | src/gpg.c | 9 | ||||
-rw-r--r-- | src/mail.c | 244 | ||||
-rw-r--r-- | src/wydawca.c | 1 | ||||
-rw-r--r-- | src/wydawca.h | 4 |
6 files changed, 249 insertions, 27 deletions
diff --git a/doc/Makefile.am b/doc/Makefile.am index f53e47c..721ea29 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -90,24 +90,33 @@ check-writeme: check-unrevised: @grep -Hn @UNREVISED $(info_TEXINFOS) > $@-t; \ if [ -s $@-t ]; then \ echo "Unrevised nodes:"; \ cat $@-t; \ rm $@-t; \ false;\ else \ rm $@-t; \ fi +check-config: + @check-docs.sh 'configuration statements' \ + '/wydawca_keywords\[\] *= *{/,/^}/s/[ \t]*{ *"\([^,"]*\)".*/\1/pg' \ + 's/@deffnx\{0,1\} {Config} *\([^@,]*\).*/\1/p' \ + $(top_srcdir)/src/config.c -- \ + $(MAKEINFO) $(AM_MAKEINFOFLAGS) $(MAKEINFOFLAGS) -I $(srcdir) -E - \ + $(info_TEXINFOS) + + all-check-docs: check-format check-options check-refs check-fixmes check-unrevised check-writeme check-docs: $(MAKE) -k all-check-docs master-menu: emacs -batch -l mastermenu.el -f make-master-menu $(info_TEXINFOS) untabify: emacs -batch -l untabify.el $(info_TEXINFOS) $(wydawca_TEXINFOS) fix-sentence-spacing: diff --git a/src/config.c b/src/config.c index 559f766..f0a7042 100644 --- a/src/config.c +++ b/src/config.c @@ -854,24 +854,27 @@ cb_archive (enum grecs_callback_command cmd, } return 0; } static struct grecs_keyword mail_statistics_kw[] = { { "message", N_("text"), N_("Message text"), grecs_type_string, &admin_stat_message }, { "statistics", N_("items"), N_("Send mail if one or more of these items are set"), grecs_type_string, &mail_admin_mask, 0, cb_statistics }, + { "gpg-sign", + N_("key"), N_("Sign message with this key"), + grecs_type_string, &admin_stat_sign_key }, { NULL } }; static int cb_event (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { @@ -898,24 +901,27 @@ cb_recipient (enum grecs_callback_command cmd, return 0; } static struct grecs_keyword notify_event_kw[] = { { "event", N_("ev-id"), N_("Event on which to notify"), grecs_type_string, NULL, offsetof(struct notification, ev), cb_event }, { "recipient", N_("who"), N_("Notify this recipient"), grecs_type_string, NULL, offsetof(struct notification, tgt), cb_recipient }, { "message", N_("text-or-id"), N_("Text of the notification or identifier of a defined message template"), grecs_type_string, NULL, offsetof(struct notification, msg) }, + { "gpg-sign", N_("key"), + N_("Sign message with this key"), + grecs_type_string, NULL, offsetof(struct notification, sign_keys) }, { NULL } }; static int cb_notify_event (enum grecs_callback_command cmd, grecs_locus_t *locus, void *varptr, grecs_value_t *value, void *cb_data) { struct notification *ntf; void **pdata = cb_data; @@ -1447,24 +1453,27 @@ static struct grecs_keyword wydawca_kw[] = { cb_notify_event, NULL, notify_event_kw }, { "access-method", N_("ident"), N_("Define access method"), grecs_type_section, default_access_method, 0, cb_access_method, NULL, access_method_kw }, { "spool", N_("tag: string"), N_("Define distribution spool"), grecs_type_section, NULL, 0, cb_spool, NULL, spool_kw }, { "all-spools", NULL, N_("Service names that request scanning all spools"), grecs_type_string|GRECS_LIST, &all_spool_aliases }, + + { "gpg-homedir", NULL, N_("GPG home directory"), + grecs_type_string, &wydawca_gpg_homedir }, { NULL } }; void config_help () { static char docstring[] = N_("Configuration file structure for wydawca.\n" "For more information, use `info wydawca configuration'."); grecs_format_docstring (stdout, docstring, 0); grecs_format_statement_array (stdout, wydawca_kw, 1, 0); @@ -119,26 +119,26 @@ rmdir_r (const char *name) /* Remove temporary GPG home directory */ static void remove_homedir () { if (debug_level > 1) logmsg (LOG_DEBUG, _("removing GNUPG home directory: %s"), homedir); if (rmdir_r (homedir)) logmsg (LOG_CRIT, _("failed to remove GPG directory %s"), homedir); } /* Create a temporary GPG home directory */ -int -wydawca_gpg_homedir () +static int +create_gpg_homedir () { if (homedir) return 0; homedir = xstrdup ("/tmp/wydawca-XXXXXX"); if (!mkdtemp (homedir)) { logmsg (LOG_CRIT, _("cannot create GPG home directory (%s): %s"), homedir, strerror (errno)); return 1; } atexit (remove_homedir); @@ -213,30 +213,29 @@ gpg_verify_signature (gpgme_ctx_t ctx, gpgme_signature_t sig, } return 1; } /* Verify the directive file from TRP using public key PUBKEY */ /* FIXME: spool currently unused */ int verify_directive_signature (struct file_triplet *trp, const struct spool *spool) { gpgme_ctx_t ctx; gpgme_data_t key_data, directive_data, plain; - off_t size; gpgme_error_t ec; int rc; struct uploader_info *uptr; - wydawca_gpg_homedir (); + create_gpg_homedir (); fail_if_err (gpgme_new (&ctx)); for (uptr = trp->uploader_list; uptr; uptr = uptr->next) { gpgme_import_result_t res; gpgme_import_status_t pstat; fail_if_err (gpgme_data_new_from_mem (&key_data, uptr->gpg_key, strlen (uptr->gpg_key), 0)); fail_if_err (gpgme_op_import (ctx, key_data)); @@ -292,25 +291,25 @@ verify_detached_signature (struct file_triplet *trp, fail_if_err (gpgme_get_engine_info (&info)); while (info && info->protocol != GPGME_PROTOCOL_OpenPGP) info = info->next; if (!info) { logmsg (LOG_CRIT, _("cannot find path to gpg binary (attempting to verify " "the detached signature for %s"), trp->name); return 1; } - wydawca_gpg_homedir (); + create_gpg_homedir (); argv[0] = info->file_name; argv[1] = "--verify"; argv[2] = trp->file[file_signature].name; argv[3] = trp->file[file_dist].name; argv[4] = NULL; switch (wydawca_exec (5, argv, NULL)) { case exec_success: if (debug_level) logmsg (LOG_DEBUG, _("good detached signature for %s"), trp->name); return 0; @@ -8,53 +8,250 @@ 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 <http://www.gnu.org/licenses/>. */ #include "wydawca.h" #include <mail.h> #include <hash.h> +#include <gpgme.h> 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)); } } } +struct mu_stream_handle +{ + mu_stream_t str; + mu_off_t off; +}; + +static ssize_t +mu_stream_data_read_cb (void *handle, void *buffer, size_t size) +{ + struct mu_stream_handle *mhp = handle; + size_t nread; + int rc; + + rc = mu_stream_read (mhp->str, buffer, size, mhp->off, &nread); + if (rc) + { + logmsg (LOG_ERR, "mu_stream_read: %s", mu_strerror (rc)); + errno = EIO; + return -1; + } + + mhp->off += nread; + return nread; +} + +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; + + 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)); +#if 0 + /* FIXME: */ + else if (debug_level > 1) + gpgme_debug_info (ctx); +#endif + + gpgme_release (ctx); + return err != 0; +} + +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; + struct mu_stream_handle mhn; + mu_stream_t istr, ostr; + 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_stream (body, &mhn.str))) + { + logmsg (LOG_ERR, "mu_message_get_stream: %s", mu_strerror (rc)); + return 1; + } + + mu_stream_seek (mhn.str, 0, SEEK_SET); + mhn.off = 0; + + memset (&cbs, 0, sizeof (cbs)); + cbs.read = mu_stream_data_read_cb; + + err = gpgme_data_new_from_cbs (&input, &cbs, &mhn); + if (err) + { + logmsg (LOG_ERR, "gpgme_data_new_from_cbs: %s", + gpgme_strerror (rc)); + return 1; + } + + rc = gpg_sign (&output, input, key); + + 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_stream (newmsg, &ostr); + + /* Copy headers */ + mu_message_get_header (msg, &hdr); + mu_header_get_stream (hdr, &istr); + mu_stream_seek (istr, 0, SEEK_SET); + while ((rc = mu_stream_sequential_getline (istr, &buf, &size, &nread)) == 0 + && nread) + { + rc = mu_stream_sequential_write (ostr, buf, nread); + if (rc) + { + logmsg (LOG_ERR, "mu_stream_sequential_write: %s", + mu_strerror (rc)); + break; + } + } + + if (rc == 0) + { + while ((nread = gpgme_data_read (output, buf, size)) > 0) + { + rc = mu_stream_sequential_write (ostr, buf, nread); + if (rc) + { + logmsg (LOG_ERR, "mu_stream_sequential_write: %s", + mu_strerror (rc)); + break; + } + } + + if (rc == 0) + { + mu_message_destroy (&msg, mu_message_get_owner (msg)); + *pmsg = newmsg; + } + } + + gpgme_data_release (output); + free (buf); + + return rc; +} + void -mail_send_message (mu_address_t rcpt, const char *text) +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; int mailer_flags = 0; static char *x_mailer = "wydawca (" PACKAGE_STRING ")"; size_t size; char *buf; const char *sval; mu_message_create (&msg, NULL); @@ -82,43 +279,49 @@ mail_send_message (mu_address_t rcpt, const char *text) free (buf); } } if (debug_level > 1) { mu_debug_t debug; mu_mailer_get_debug (mailer, &debug); mu_debug_set_level (debug, MU_DEBUG_TRACE | MU_DEBUG_PROT); if (debug_level > 2) mailer_flags = MAILER_FLAG_DEBUG_DATA; } - + if (!mailer_opened) { if ((rc = mu_mailer_open (mailer, mailer_flags))) { 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; } - rc = mu_mailer_send_message (mailer, msg, from_address, rcpt); - if (rc) - logmsg (LOG_CRIT, _("cannot send message: %s"), mu_strerror (rc)); - + 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; } } @@ -235,45 +438,42 @@ mail_stats () 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); } - if (dry_run_mode) - return; - 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); + 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; @@ -357,24 +557,26 @@ get_recipient (struct access_method *method, struct file_triplet *trp, } method_close (method, 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); @@ -399,39 +601,37 @@ do_notify (struct file_triplet *trp, enum notification_event ev, 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"), + logmsg (LOG_DEBUG, + _("notifying message recipients (project %s) about %s"), trp->project, notification_event_str (ev)); } - - if (!dry_run_mode) + + msg = resolve_message_template (ntf->msg); + if (!msg) + logmsg (LOG_ERR, _("undefined message reference: %s"), ntf->msg); + else { - const char *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); - free (text); - } + 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) diff --git a/src/wydawca.c b/src/wydawca.c index 07cac5c..f098bb1 100644 --- a/src/wydawca.c +++ b/src/wydawca.c @@ -36,24 +36,25 @@ char *tar_command_name = "tar"; int archive_signatures = 1; /* Archive sig files by default */ int lint_mode = 0; int preprocess_only = 0; int cron_option = 0; int foreground_option = -1; int single_process_option = -1; int daemon_mode = 0; int foreground; int single_process; time_t wakeup_interval; gl_list_t all_spool_aliases; +char *wydawca_gpg_homedir; struct grecs_sockaddr listen_sockaddr; unsigned wydawca_stat[MAX_STAT]; void initstats () { memset (wydawca_stat, 0, sizeof wydawca_stat); } diff --git a/src/wydawca.h b/src/wydawca.h index 805b72b..96fd336 100644 --- a/src/wydawca.h +++ b/src/wydawca.h @@ -272,24 +272,25 @@ enum notification_target { notify_read, /* Read recipients from the message headers */ notify_admin, /* System administrator */ notify_owner, /* Project admin */ notify_user /* User (uploader) */ }; struct notification { struct notification *next; enum notification_event ev; enum notification_target tgt; + const char *sign_keys; const char *msg; }; void register_message_template (const char *name, const char *text); void notify (struct notification *, struct file_triplet *, enum notification_event); const char *notification_event_str (enum notification_event evt); const char *notification_target_str (enum notification_target tgt); @@ -318,41 +319,44 @@ extern int dry_run_mode; /* Dry run indicator */ extern int log_to_stderr; /* Log to stderr instead of the syslog */ extern int log_facility; /* Syslog facility to use if !log_to_stderr */ extern char *syslog_tag; /* Syslog tag */ extern int syslog_include_prio;/* Syslog priority indication */ extern time_t file_sweep_time; /* Unlink stale file after this amount of time */ extern char *tar_command_name; /* Name of the tar command */ extern unsigned wydawca_stat[MAX_STAT]; extern unsigned long print_stats; extern int archive_signatures; extern char *admin_stat_message; +extern char *admin_stat_sign_key; extern char *pidfile; extern int force_startup; extern char *lockdir; extern time_t lock_expire_time; extern time_t lock_timeout; extern int enable_locking; extern int daemon_mode; extern time_t wakeup_interval; extern int foreground; extern int single_process; extern struct grecs_sockaddr listen_sockaddr; extern gl_list_t all_spool_aliases; +extern char *wydawca_gpg_homedir; + #define UPDATE_STATS(what) \ do \ { \ if (what >= MAX_STAT) abort(); \ wydawca_stat[what]++; \ } \ while (0) int stat_mask_p (unsigned long mask); struct metadef *make_stat_expansion (size_t count); void initstats (void); void logstats (void); |