wks: New option --with-colons for gpg-wks-client.
authorWerner Koch <wk@gnupg.org>
Mon, 5 Nov 2018 19:58:27 +0000 (20:58 +0100)
committerWerner Koch <wk@gnupg.org>
Mon, 5 Nov 2018 19:58:27 +0000 (20:58 +0100)
* tools/gpg-wks.h (opt): Add field with_colons.
* tools/gpg-wks-client.c (oWithColons): New const.
(opts, parse_arguments): Add option --with-colons.
(main): Change aSupported to take several domains in --with-colons
mode.
(command_send): Factor policy getting code out to ...
(get_policy_and_sa): New function.
(command_supported): Make use of new function.
--

In addition to this the --create command now also supports a
submission address only in the policy file.  That means the
submission-address file is not anymore required and can be replaced by
the policy file.

Signed-off-by: Werner Koch <wk@gnupg.org>
doc/wks.texi
tools/gpg-wks-client.c
tools/gpg-wks.h
tools/wks-util.c

index bd2b8d5..03d7482 100644 (file)
@@ -65,7 +65,8 @@ site supports the Web Key Service.  The argument is an arbitrary
 address in the to be tested domain. For example
 @file{foo@@example.net}.  The command returns success if the Web Key
 Service is supported.  The operation is silent; to get diagnostic
-output use the option @option{--verbose}.
+output use the option @option{--verbose}.  See option
+@option{--with-colons} for a variant of this command.
 
 With the @option{--check} command the caller can test whether a key
 exists for a supplied mail address.  The command returns success if a
@@ -109,6 +110,44 @@ $(gpgconf --list-dirs libexecdir)/gpg-wks-client --check foo@@example.net
 Directly send created mails using the @command{sendmail} command.
 Requires installation of that command.
 
+@item --with-colons
+@opindex with-colons
+This option has currently only an effect on the @option{--supported}
+command.  If it is used all arguimenst on the command line are taken
+as domain names and tested for WKD support.  The output format is one
+line per domain with colon delimited fields.  The currently specified
+fields are (future versions may specify additional fields):
+
+@table @asis
+
+  @item 1 - domain
+  This is the domain name.  Although quoting is not required for valid
+  domain names this field is specified to be quoted in standard C
+  manner.
+
+  @item 2 - WKD
+  If the value is true the domain supports the Web Key Directory.
+
+  @item 3 - WKS
+  If the value is true the domain supports the Web Key Service
+  protocol to upload keys to the directory.
+
+  @item 4 - error-code
+  This may contain an gpg-error code to describe certain
+  failures.  Use @samp{gpg-error CODE} to explain the code.
+
+  @item 5 - protocol-version
+  The minimum protocol version supported by the server.
+
+  @item 6 - auth-submit
+  The auth-submit flag from the policy file of the server.
+
+  @item 7 - mailbox-only
+  The mailbox-only flag from the policy file of the server.
+@end table
+
+
+
 @item --output @var{file}
 @itemx -o
 @opindex output
index 73945ff..bf6b119 100644 (file)
@@ -61,6 +61,7 @@ enum cmd_and_opt_values
     oSend,
     oFakeSubmissionAddr,
     oStatusFD,
+    oWithColons,
 
     oDummy
   };
@@ -90,6 +91,7 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
   ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
+  ARGPARSE_s_n (oWithColons, "with-colons", "@"),
 
   ARGPARSE_s_s (oFakeSubmissionAddr, "fake-submission-addr", "@"),
 
@@ -204,6 +206,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
         case oStatusFD:
           wks_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
           break;
+        case oWithColons:
+          opt.with_colons = 1;
+          break;
 
        case aSupported:
        case aCreate:
@@ -271,11 +276,20 @@ main (int argc, char **argv)
   switch (cmd)
     {
     case aSupported:
-      if (argc != 1)
-        wrong_args ("--supported USER-ID");
-      err = command_supported (argv[0]);
-      if (err && gpg_err_code (err) != GPG_ERR_FALSE)
-        log_error ("checking support failed: %s\n", gpg_strerror (err));
+      if (opt.with_colons)
+        {
+          for (; argc; argc--, argv++)
+            command_supported (*argv);
+          err = 0;
+        }
+      else
+        {
+          if (argc != 1)
+            wrong_args ("--supported DOMAIN");
+          err = command_supported (argv[0]);
+          if (err && gpg_err_code (err) != GPG_ERR_FALSE)
+            log_error ("checking support failed: %s\n", gpg_strerror (err));
+        }
       break;
 
     case aCreate:
@@ -471,6 +485,134 @@ decrypt_stream (estream_t *r_output, struct decrypt_stream_parm_s *decinfo,
 }
 
 
+/* Return the submission address for the address or just the domain in
+ * ADDRSPEC.  The submission address is stored as a malloced string at
+ * R_SUBMISSION_ADDRESS.  At R_POLICY the policy flags of the domain
+ * are stored.  The caller needs to free them with wks_free_policy.
+ * The function returns an error code on failure to find a submission
+ * address or policy file.  Note: The function may store NULL at
+ * R_SUBMISSION_ADDRESS but return success to indicate that the web
+ * key directory is supported but not the web key service.  As per WKD
+ * specs a policy file is always required and will thus be return on
+ * success.  */
+static gpg_error_t
+get_policy_and_sa (const char *addrspec, int silent,
+                   policy_flags_t *r_policy, char **r_submission_address)
+{
+  gpg_error_t err;
+  estream_t mbuf = NULL;
+  const char *domain;
+  const char *s;
+  policy_flags_t policy = NULL;
+  char *submission_to = NULL;
+
+  *r_submission_address = NULL;
+  *r_policy = NULL;
+
+  domain = strchr (addrspec, '@');
+  if (domain)
+    domain++;
+
+  if (opt.with_colons)
+    {
+      s = domain? domain : addrspec;
+      es_write_sanitized (es_stdout, s, strlen (s), ":", NULL);
+      es_putc (':', es_stdout);
+    }
+
+  /* We first try to get the submission address from the policy file
+   * (this is the new method).  If both are available we check that
+   * they match and print a warning if not.  In the latter case we
+   * keep on using the one from the submission-address file.    */
+  err = wkd_get_policy_flags (addrspec, &mbuf);
+  if (err && gpg_err_code (err) != GPG_ERR_NO_DATA
+      && gpg_err_code (err) != GPG_ERR_NO_NAME)
+    {
+      if (!opt.with_colons)
+        log_error ("error reading policy flags for '%s': %s\n",
+                   domain, gpg_strerror (err));
+      goto leave;
+    }
+  if (!mbuf)
+    {
+      if (!opt.with_colons)
+        log_error ("provider for '%s' does NOT support the Web Key Directory\n",
+                   addrspec);
+      err = gpg_error (GPG_ERR_FALSE);
+      goto leave;
+    }
+
+  policy = xtrycalloc (1, sizeof *policy);
+  if (!policy)
+    err = gpg_error_from_syserror ();
+  else
+    err = wks_parse_policy (policy, mbuf, 1);
+  es_fclose (mbuf);
+  mbuf = NULL;
+  if (err)
+    goto leave;
+
+  err = wkd_get_submission_address (addrspec, &submission_to);
+  if (err && !policy->submission_address)
+    {
+      if (!silent && !opt.with_colons)
+        log_error (_("error looking up submission address for domain '%s'"
+                     ": %s\n"), domain, gpg_strerror (err));
+      if (!silent && gpg_err_code (err) == GPG_ERR_NO_DATA && !opt.with_colons)
+        log_error (_("this domain probably doesn't support WKS.\n"));
+      goto leave;
+    }
+
+  if (submission_to && policy->submission_address
+      && ascii_strcasecmp (submission_to, policy->submission_address))
+    log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
+              submission_to, policy->submission_address);
+
+  if (!submission_to && policy->submission_address)
+    {
+      submission_to = xtrystrdup (policy->submission_address);
+      if (!submission_to)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+
+ leave:
+  *r_submission_address = submission_to;
+  submission_to = NULL;
+  *r_policy = policy;
+  policy = NULL;
+
+  if (opt.with_colons)
+    {
+      if (*r_policy && !*r_submission_address)
+        es_fprintf (es_stdout, "1:0::");
+      else if (*r_policy && *r_submission_address)
+        es_fprintf (es_stdout, "1:1::");
+      else if (err && !(gpg_err_code (err) == GPG_ERR_FALSE
+                        || gpg_err_code (err) == GPG_ERR_NO_DATA
+                        || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST))
+        es_fprintf (es_stdout, "0:0:%d:", err);
+      else
+        es_fprintf (es_stdout, "0:0::");
+      if (*r_policy)
+        {
+          es_fprintf (es_stdout, "%u:%u:%u:",
+                      (*r_policy)->protocol_version,
+                      (*r_policy)->auth_submit,
+                      (*r_policy)->mailbox_only);
+        }
+      es_putc ('\n', es_stdout);
+    }
+
+  xfree (submission_to);
+  wks_free_policy (policy);
+  xfree (policy);
+  es_fclose (mbuf);
+  return err;
+}
+
 
 \f
 /* Check whether the  provider supports the WKS protocol.  */
@@ -480,6 +622,7 @@ command_supported (char *userid)
   gpg_error_t err;
   char *addrspec = NULL;
   char *submission_to = NULL;
+  policy_flags_t policy = NULL;
 
   if (!strchr (userid, '@'))
     {
@@ -497,24 +640,41 @@ command_supported (char *userid)
     }
 
   /* Get the submission address.  */
-  err = wkd_get_submission_address (addrspec, &submission_to);
-  if (err)
+  err = get_policy_and_sa (addrspec, 1, &policy, &submission_to);
+  if (err || !submission_to)
     {
-      if (gpg_err_code (err) == GPG_ERR_NO_DATA
-          || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST)
+      if (!submission_to
+          || gpg_err_code (err) == GPG_ERR_FALSE
+          || gpg_err_code (err) == GPG_ERR_NO_DATA
+          || gpg_err_code (err) == GPG_ERR_UNKNOWN_HOST
+          )
         {
-          if (opt.verbose)
-            log_info ("provider for '%s' does NOT support WKS (%s)\n",
-                      addrspec, gpg_strerror (err));
+          /* FALSE is returned if we already figured out that even the
+           * Web Key Directory is not supported and thus printed an
+           * error message.  */
+          if (opt.verbose && gpg_err_code (err) != GPG_ERR_FALSE
+              && !opt.with_colons)
+            {
+              if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+                log_info ("provider for '%s' does NOT support WKS\n",
+                          addrspec);
+              else
+                log_info ("provider for '%s' does NOT support WKS (%s)\n",
+                          addrspec, gpg_strerror (err));
+            }
           err = gpg_error (GPG_ERR_FALSE);
-          log_inc_errorcount ();
+          if (!opt.with_colons)
+            log_inc_errorcount ();
         }
       goto leave;
     }
-  if (opt.verbose)
+
+  if (opt.verbose && !opt.with_colons)
     log_info ("provider for '%s' supports WKS\n", addrspec);
 
  leave:
+  wks_free_policy (policy);
+  xfree (policy);
   xfree (submission_to);
   xfree (addrspec);
   return err;
@@ -628,7 +788,7 @@ command_send (const char *fingerprint, const char *userid)
   estream_t keyenc = NULL;
   char *submission_to = NULL;
   mime_maker_t mime = NULL;
-  struct policy_flags_s policy;
+  policy_flags_t policy = NULL;
   int no_encrypt = 0;
   int posteo_hack = 0;
   const char *domain;
@@ -636,8 +796,6 @@ command_send (const char *fingerprint, const char *userid)
   uidinfo_list_t uid, thisuid;
   time_t thistime;
 
-  memset (&policy, 0, sizeof policy);
-
   if (classify_user_id (fingerprint, &desc, 1)
       || !(desc.mode == KEYDB_SEARCH_MODE_FPR
            || desc.mode == KEYDB_SEARCH_MODE_FPR20))
@@ -665,62 +823,26 @@ command_send (const char *fingerprint, const char *userid)
   /* Get the submission address.  */
   if (fake_submission_addr)
     {
+      policy = xcalloc (1, sizeof *policy);
       submission_to = xstrdup (fake_submission_addr);
       err = 0;
     }
   else
     {
-      /* We first try to get the submission address from the policy
-       * file (this is the new method).  If both are available we
-       * check that they match and print a warning if not.  In the
-       * latter case we keep on using the one from the
-       * submission-address file.  */
-      estream_t mbuf;
-
-      err = wkd_get_policy_flags (addrspec, &mbuf);
-      if (err && gpg_err_code (err) != GPG_ERR_NO_DATA)
-        {
-          log_error ("error reading policy flags for '%s': %s\n",
-                     domain, gpg_strerror (err));
-          goto leave;
-        }
-      if (mbuf)
-        {
-          err = wks_parse_policy (&policy, mbuf, 1);
-          es_fclose (mbuf);
-          if (err)
-            goto leave;
-        }
-
-      err = wkd_get_submission_address (addrspec, &submission_to);
-      if (err && !policy.submission_address)
-        {
-          log_error (_("error looking up submission address for domain '%s'"
-                       ": %s\n"), domain, gpg_strerror (err));
-          if (gpg_err_code (err) == GPG_ERR_NO_DATA)
-            log_error (_("this domain probably doesn't support WKS.\n"));
-          goto leave;
-        }
-
-      if (submission_to && policy.submission_address
-          && ascii_strcasecmp (submission_to, policy.submission_address))
-        log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
-                  submission_to, policy.submission_address);
-
+      err = get_policy_and_sa (addrspec, 0, &policy, &submission_to);
+      if (err)
+        goto leave;
       if (!submission_to)
         {
-          submission_to = xtrystrdup (policy.submission_address);
-          if (!submission_to)
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
+          log_error (_("this domain probably doesn't support WKS.\n"));
+          err = gpg_error (GPG_ERR_NO_DATA);
+          goto leave;
         }
     }
 
   log_info ("submitting request to '%s'\n", submission_to);
 
-  if (policy.auth_submit)
+  if (policy->auth_submit)
     log_info ("no confirmation required for '%s'\n", addrspec);
 
   /* In case the key has several uids with the same addr-spec we will
@@ -738,8 +860,7 @@ command_send (const char *fingerprint, const char *userid)
     {
       if (!uid->mbox)
         continue; /* Should not happen anyway.  */
-      if (policy.mailbox_only
-          && ascii_strcasecmp (uid->uid, uid->mbox))
+      if (policy->mailbox_only && ascii_strcasecmp (uid->uid, uid->mbox))
         continue; /* UID has more than just the mailbox.  */
       if (uid->created > thistime)
         {
@@ -770,7 +891,7 @@ command_send (const char *fingerprint, const char *userid)
       key = newkey;
     }
 
-  if (policy.mailbox_only
+  if (policy->mailbox_only
       && (!thisuid->mbox || ascii_strcasecmp (thisuid->uid, thisuid->mbox)))
     {
       log_info ("Warning: policy requires 'mailbox-only'"
@@ -791,7 +912,7 @@ command_send (const char *fingerprint, const char *userid)
 
   /* Hack to support posteo but let them disable this by setting the
    * new policy-version flag.  */
-  if (policy.protocol_version < 3
+  if (policy->protocol_version < 3
       && !ascii_strcasecmp (domain, "posteo.de"))
     {
       log_info ("Warning: Using draft-1 method for domain '%s'\n", domain);
@@ -908,7 +1029,8 @@ command_send (const char *fingerprint, const char *userid)
   free_uidinfo_list (uidlist);
   es_fclose (keyenc);
   es_fclose (key);
-  wks_free_policy (&policy);
+  wks_free_policy (policy);
+  xfree (policy);
   xfree (addrspec);
   return err;
 }
index 1b91b65..fba73f0 100644 (file)
@@ -36,6 +36,7 @@ struct
   unsigned int debug;
   int quiet;
   int use_sendmail;
+  int with_colons;
   const char *output;
   const char *gpg_program;
   const char *directory;
index 862cd33..729098a 100644 (file)
@@ -556,7 +556,7 @@ wks_send_mime (mime_maker_t mime)
 
 
 /* Parse the policy flags by reading them from STREAM and storing them
- * into FLAGS.  If IGNORE_UNKNOWN is iset unknown keywords are
+ * into FLAGS.  If IGNORE_UNKNOWN is set unknown keywords are
  * ignored.  */
 gpg_error_t
 wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)