wks: Add framework for policy flags.
authorWerner Koch <wk@gnupg.org>
Fri, 2 Sep 2016 14:54:42 +0000 (16:54 +0200)
committerWerner Koch <wk@gnupg.org>
Fri, 2 Sep 2016 14:56:04 +0000 (16:56 +0200)
* 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 <wk@gnupg.org>
tools/call-dirmngr.c
tools/call-dirmngr.h
tools/gpg-wks-client.c
tools/gpg-wks-server.c
tools/gpg-wks.h
tools/wks-util.c

index 0e591dd..9142350 100644 (file)
@@ -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;
+}
index f1bc368..6c866e7 100644 (file)
@@ -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*/
index 34b26ea..c0e34c4 100644 (file)
@@ -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)
index 221db05..678000c 100644 (file)
@@ -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;
 
index be85eec..85000cc 100644 (file)
@@ -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,
index 8d9f92b..7a87a27 100644 (file)
@@ -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;
+}