From 46362cbc0e2260e989820795a6e4245c72335172 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 2 Sep 2016 16:54:42 +0200 Subject: [PATCH] wks: Add framework for policy flags. * tools/call-dirmngr.c (wkd_get_policy_flags): New. * tools/gpg-wks.h (struct policy_flags_s, policy_flags_t): New. * tools/wks-util.c (wks_parse_policy): New. * tools/gpg-wks-client.c (command_send): Get the policy flags to show a new info line. * tools/gpg-wks-server.c (get_policy_flags): New. (process_new_key): get policy flag and add a stub for "auth-submit". (command_list_domains): Check policy flags. Signed-off-by: Werner Koch --- tools/call-dirmngr.c | 55 +++++++++++++++++++ tools/call-dirmngr.h | 1 + tools/gpg-wks-client.c | 26 +++++++++ tools/gpg-wks-server.c | 117 +++++++++++++++++++++++++++++++++++++---- tools/gpg-wks.h | 14 +++++ tools/wks-util.c | 108 +++++++++++++++++++++++++++++++++++++ 6 files changed, 310 insertions(+), 11 deletions(-) diff --git a/tools/call-dirmngr.c b/tools/call-dirmngr.c index 0e591dd6d..9142350cd 100644 --- a/tools/call-dirmngr.c +++ b/tools/call-dirmngr.c @@ -203,3 +203,58 @@ wkd_get_submission_address (const char *addrspec, char **r_addrspec) assuan_release (ctx); return err; } + + +/* Ask the dirmngr for the policy flags and return them as an estream + * memory stream. If no policy flags are set, NULL is stored at + * R_BUFFER. */ +gpg_error_t +wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer) +{ + gpg_error_t err; + assuan_context_t ctx; + struct wkd_get_parm_s parm; + char *line = NULL; + char *buffer = NULL; + + memset (&parm, 0, sizeof parm); + *r_buffer = NULL; + + err = connect_dirmngr (&ctx); + if (err) + return err; + + line = es_bsprintf ("WKD_GET --policy-flags -- %s", addrspec); + if (!line) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (strlen (line) + 2 >= ASSUAN_LINELENGTH) + { + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + } + + parm.memfp = es_fopenmem (0, "rwb"); + if (!parm.memfp) + { + err = gpg_error_from_syserror (); + goto leave; + } + err = assuan_transact (ctx, line, wkd_get_data_cb, &parm, + NULL, NULL, wkd_get_status_cb, &parm); + if (err) + goto leave; + + es_rewind (parm.memfp); + *r_buffer = parm.memfp; + parm.memfp = 0; + + leave: + es_free (buffer); + es_fclose (parm.memfp); + xfree (line); + assuan_release (ctx); + return err; +} diff --git a/tools/call-dirmngr.h b/tools/call-dirmngr.h index f1bc3686b..6c866e7e6 100644 --- a/tools/call-dirmngr.h +++ b/tools/call-dirmngr.h @@ -23,6 +23,7 @@ void set_dirmngr_options (int verbose, int debug_ipc, int autostart); gpg_error_t wkd_get_submission_address (const char *addrspec, char **r_addrspec); +gpg_error_t wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer); #endif /*GNUPG_TOOLS_CALL_DIRMNGR_H*/ diff --git a/tools/gpg-wks-client.c b/tools/gpg-wks-client.c index 34b26ea8e..c0e34c499 100644 --- a/tools/gpg-wks-client.c +++ b/tools/gpg-wks-client.c @@ -447,6 +447,9 @@ command_send (const char *fingerprint, char *userid) estream_t key = NULL; char *submission_to = NULL; mime_maker_t mime = NULL; + struct policy_flags_s policy; + + memset (&policy, 0, sizeof policy); if (classify_user_id (fingerprint, &desc, 1) || !(desc.mode == KEYDB_SEARCH_MODE_FPR @@ -473,6 +476,29 @@ command_send (const char *fingerprint, char *userid) goto leave; log_info ("submitting request to '%s'\n", submission_to); + /* Get the policy flags. */ + { + estream_t mbuf; + + err = wkd_get_policy_flags (addrspec, &mbuf); + if (err) + { + log_error ("error reading policy flags for '%s': %s\n", + submission_to, gpg_strerror (err)); + goto leave; + } + if (mbuf) + { + err = wks_parse_policy (&policy, mbuf, 1); + es_fclose (mbuf); + if (err) + goto leave; + } + } + + if (policy.auth_submit) + log_info ("no confirmation required for '%s'\n", addrspec); + /* Send the key. */ err = mime_maker_new (&mime, NULL); if (err) diff --git a/tools/gpg-wks-server.c b/tools/gpg-wks-server.c index 221db05f1..678000c80 100644 --- a/tools/gpg-wks-server.c +++ b/tools/gpg-wks-server.c @@ -766,6 +766,50 @@ get_submission_address (const char *mbox) } +/* Get the policy flags for address MBOX and store them in POLICY. */ +static gpg_error_t +get_policy_flags (policy_flags_t policy, const char *mbox) +{ + gpg_error_t err; + const char *domain; + char *fname; + estream_t fp; + + memset (policy, 0, sizeof *policy); + + domain = strchr (mbox, '@'); + if (!domain) + return gpg_error (GPG_ERR_INV_USER_ID); + domain++; + + fname = make_filename_try (opt.directory, domain, "policy", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + log_error ("make_filename failed in %s: %s\n", + __func__, gpg_strerror (err)); + return err; + } + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = 0; + else + log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + return err; + } + + err = wks_parse_policy (policy, fp, 0); + es_fclose (fp); + xfree (fname); + return err; +} + + /* We store the key under the name of the nonce we will then send to * the user. On success the nonce is stored at R_NONCE and the file * name at R_FNAME. */ @@ -1005,6 +1049,7 @@ process_new_key (server_ctx_t ctx, estream_t key) char *dname = NULL; char *nonce = NULL; char *fname = NULL; + struct policy_flags_s policybuf; /* First figure out the user id from the key. */ err = list_key (ctx, key); @@ -1035,23 +1080,40 @@ process_new_key (server_ctx_t ctx, estream_t key) err = gpg_error_from_syserror (); goto leave; } - /* Fixme: check for proper directory permissions. */ + if (access (dname, W_OK)) { log_info ("skipping address '%s': Domain not configured\n", sl->d); continue; } - log_info ("storing address '%s'\n", sl->d); + if (get_policy_flags (&policybuf, sl->d)) + { + log_info ("skipping address '%s': Bad policy flags\n", sl->d); + continue; + } - xfree (nonce); - xfree (fname); - err = store_key_as_pending (dname, key, &nonce, &fname); - if (err) - goto leave; + if (policybuf.auth_submit) + { + /* Bypass the confirmation stuff and publish the the key as is. */ + log_info ("publishing address '%s'\n", sl->d); + /* FIXME: We need to make sure that we do this only for the + * address in the mail. */ + log_debug ("auth-submit not yet working!\n"); + } + else + { + log_info ("storing address '%s'\n", sl->d); - err = send_confirmation_request (ctx, sl->d, nonce, fname); - if (err) - goto leave; + xfree (nonce); + xfree (fname); + err = store_key_as_pending (dname, key, &nonce, &fname); + if (err) + goto leave; + + err = send_confirmation_request (ctx, sl->d, nonce, fname); + if (err) + goto leave; + } } leave: @@ -1639,6 +1701,7 @@ command_list_domains (void) const char *domain; char *fname = NULL; int i; + estream_t fp; err = get_domain_list (&domaindirs); if (err) @@ -1686,7 +1749,7 @@ command_list_domains (void) } } - /* Print a warning if the sumbission address is not configured. */ + /* Print a warning if the submission address is not configured. */ xfree (fname); fname = make_filename_try (sl->d, "submission-address", NULL); if (!fname) @@ -1704,6 +1767,38 @@ command_list_domains (void) log_error ("domain %s: problem with '%s': %s\n", domain, fname, gpg_strerror (err)); } + + /* Check the syntax of the optional policy file. */ + xfree (fname); + fname = make_filename_try (sl->d, "policy", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("domain %s: error in policy file: %s\n", + domain, gpg_strerror (err)); + } + else + { + struct policy_flags_s policy; + err = wks_parse_policy (&policy, fp, 0); + es_fclose (fp); + if (!err) + { + struct policy_flags_s empty_policy; + memset (&empty_policy, 0, sizeof empty_policy); + if (!memcmp (&empty_policy, &policy, sizeof policy)) + log_error ("domain %s: empty policy file\n", domain); + } + } + + } err = 0; diff --git a/tools/gpg-wks.h b/tools/gpg-wks.h index be85eecfb..85000cc71 100644 --- a/tools/gpg-wks.h +++ b/tools/gpg-wks.h @@ -46,8 +46,22 @@ struct #define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ +/* The parsed policy flags. */ +struct policy_flags_s +{ + unsigned int mailbox_only : 1; + unsigned int dane_only : 1; + unsigned int auth_submit : 1; + unsigned int max_pending; /* Seconds to wait for a confirmation. */ +}; +typedef struct policy_flags_s *policy_flags_t; + + + /*-- wks-util.c --*/ gpg_error_t wks_send_mime (mime_maker_t mime); +gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream, + int ignore_unknown); /*-- wks-receive.c --*/ gpg_error_t wks_receive (estream_t fp, diff --git a/tools/wks-util.c b/tools/wks-util.c index 8d9f92bd3..7a87a27a8 100644 --- a/tools/wks-util.c +++ b/tools/wks-util.c @@ -63,3 +63,111 @@ wks_send_mime (mime_maker_t mime) es_fclose (mail); return err; } + + +/* Parse the policy flags by reading them from STREAM and storing them + * into FLAGS. If IGNORE_UNKNOWN is iset unknown keywords are + * ignored. */ +gpg_error_t +wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown) +{ + enum tokens { + TOK_MAILBOX_ONLY, + TOK_DANE_ONLY, + TOK_AUTH_SUBMIT, + TOK_MAX_PENDING + }; + static struct { + const char *name; + enum tokens token; + } keywords[] = { + { "mailbox-only", TOK_MAILBOX_ONLY }, + { "dane-only", TOK_DANE_ONLY }, + { "auth-submit", TOK_AUTH_SUBMIT }, + { "max-pending", TOK_MAX_PENDING } + }; + gpg_error_t err = 0; + int lnr = 0; + char line[1024]; + char *p, *keyword, *value; + int i, n; + + memset (flags, 0, sizeof *flags); + + while (es_fgets (line, DIM(line)-1, stream) ) + { + lnr++; + n = strlen (line); + if (!n || line[n-1] != '\n') + { + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + break; + } + trim_trailing_spaces (line); + /* Skip empty and comment lines. */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '#') + continue; + + if (*p == ':') + { + err = gpg_error (GPG_ERR_SYNTAX); + break; + } + + keyword = p; + value = NULL; + if ((p = strchr (p, ':'))) + { + /* Colon found: Keyword with value. */ + *p++ = 0; + for (; spacep (p); p++) + ; + if (!*p) + { + err = gpg_error (GPG_ERR_MISSING_VALUE); + break; + } + value = p; + } + + for (i=0; i < DIM (keywords); i++) + if (!ascii_strcasecmp (keywords[i].name, keyword)) + break; + if (!(i < DIM (keywords))) + { + if (ignore_unknown) + continue; + err = gpg_error (GPG_ERR_INV_NAME); + break; + } + + switch (keywords[i].token) + { + case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break; + case TOK_DANE_ONLY: flags->dane_only = 1; break; + case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break; + case TOK_MAX_PENDING: + if (!value) + { + err = gpg_error (GPG_ERR_SYNTAX); + goto leave; + } + /* FIXME: Define whether these are seconds, hours, or days + * and decide whether to allow other units. */ + flags->max_pending = atoi (value); + break; + } + } + + if (!err && !es_feof (stream)) + err = gpg_error_from_syserror (); + leave: + if (err) + log_error ("error reading '%s', line %d: %s\n", + es_fname_get (stream), lnr, gpg_strerror (err)); + + return err; +}