wks: Move a few server functions to wks-util.
[gnupg.git] / tools / gpg-wks-server.c
index 1633a20..eae93b3 100644 (file)
@@ -1,5 +1,5 @@
 /* gpg-wks-server.c - A server for the Web Key Service protocols.
 /* gpg-wks-server.c - A server for the Web Key Service protocols.
- * Copyright (C) 2016 Werner Koch
+ * Copyright (C) 2016, 2018 Werner Koch
  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GnuPG.
  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GnuPG.
@@ -20,7 +20,7 @@
 
 /* The Web Key Service I-D defines an update protocol to store a
  * public key in the Web Key Directory.  The current specification is
 
 /* The Web Key Service I-D defines an update protocol to store a
  * public key in the Web Key Directory.  The current specification is
- * draft-koch-openpgp-webkey-service-01.txt.
+ * draft-koch-openpgp-webkey-service-05.txt.
  */
 
 #include <config.h>
  */
 
 #include <config.h>
@@ -35,6 +35,7 @@
 #include "../common/util.h"
 #include "../common/init.h"
 #include "../common/sysutils.h"
 #include "../common/util.h"
 #include "../common/init.h"
 #include "../common/sysutils.h"
+#include "../common/userids.h"
 #include "../common/ccparray.h"
 #include "../common/exectool.h"
 #include "../common/zb32.h"
 #include "../common/ccparray.h"
 #include "../common/exectool.h"
 #include "../common/zb32.h"
@@ -57,6 +58,7 @@ enum cmd_and_opt_values
     oQuiet      = 'q',
     oVerbose   = 'v',
     oOutput     = 'o',
     oQuiet      = 'q',
     oVerbose   = 'v',
     oOutput     = 'o',
+    oDirectory  = 'C',
 
     oDebug      = 500,
 
 
     oDebug      = 500,
 
@@ -66,11 +68,14 @@ enum cmd_and_opt_values
     aInstallKey,
     aRevokeKey,
     aRemoveKey,
     aInstallKey,
     aRevokeKey,
     aRemoveKey,
+    aCheck,
 
     oGpgProgram,
     oSend,
     oFrom,
     oHeader,
 
     oGpgProgram,
     oSend,
     oFrom,
     oHeader,
+    oWithDir,
+    oWithFile,
 
     oDummy
   };
 
     oDummy
   };
@@ -86,12 +91,15 @@ static ARGPARSE_OPTS opts[] = {
               ("run regular jobs")),
   ARGPARSE_c (aListDomains, "list-domains",
               ("list configured domains")),
               ("run regular jobs")),
   ARGPARSE_c (aListDomains, "list-domains",
               ("list configured domains")),
+  ARGPARSE_c (aCheck, "check",
+              ("check whether a key is installed")),
+  ARGPARSE_c (aCheck, "check-key", "@"),
   ARGPARSE_c (aInstallKey, "install-key",
   ARGPARSE_c (aInstallKey, "install-key",
-              "|FILE|install a key from FILE into the WKD"),
+              "install a key from FILE into the WKD"),
   ARGPARSE_c (aRemoveKey, "remove-key",
   ARGPARSE_c (aRemoveKey, "remove-key",
-              "|ADDR|remove the key ADDR from the WKD"),
+              "remove a key from the WKD"),
   ARGPARSE_c (aRevokeKey, "revoke-key",
   ARGPARSE_c (aRevokeKey, "revoke-key",
-              "|ADDR|mark the key ADDR in the WKD as revoked"),
+              "mark a key as revoked"),
 
   ARGPARSE_group (301, ("@\nOptions:\n ")),
 
 
   ARGPARSE_group (301, ("@\nOptions:\n ")),
 
@@ -101,9 +109,12 @@ static ARGPARSE_OPTS opts[] = {
   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
   ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
   ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
   ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
+  ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"),
   ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"),
   ARGPARSE_s_s (oHeader, "header" ,
                 "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"),
   ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"),
   ARGPARSE_s_s (oHeader, "header" ,
                 "|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"),
+  ARGPARSE_s_n (oWithDir, "with-dir", "@"),
+  ARGPARSE_s_n (oWithFile, "with-file", "@"),
 
   ARGPARSE_end ()
 };
 
   ARGPARSE_end ()
 };
@@ -127,11 +138,18 @@ static struct debug_flags_s debug_flags [] =
 struct server_ctx_s
 {
   char *fpr;
 struct server_ctx_s
 {
   char *fpr;
-  strlist_t mboxes;  /* List of addr-specs taken from the UIDs.  */
+  uidinfo_list_t mboxes;  /* List with addr-specs taken from the UIDs.  */
   unsigned int draft_version_2:1; /* Client supports the draft 2.  */
 };
 typedef struct server_ctx_s *server_ctx_t;
 
   unsigned int draft_version_2:1; /* Client supports the draft 2.  */
 };
 typedef struct server_ctx_s *server_ctx_t;
 
+
+/* Flag for --with-dir.  */
+static int opt_with_dir;
+/* Flag for --with-file.  */
+static int opt_with_file;
+
+
 /* Prototypes.  */
 static gpg_error_t get_domain_list (strlist_t *r_list);
 
 /* Prototypes.  */
 static gpg_error_t get_domain_list (strlist_t *r_list);
 
@@ -139,9 +157,8 @@ static gpg_error_t command_receive_cb (void *opaque,
                                        const char *mediatype, estream_t fp,
                                        unsigned int flags);
 static gpg_error_t command_list_domains (void);
                                        const char *mediatype, estream_t fp,
                                        unsigned int flags);
 static gpg_error_t command_list_domains (void);
-static gpg_error_t command_install_key (const char *fname);
-static gpg_error_t command_remove_key (const char *mailaddr);
 static gpg_error_t command_revoke_key (const char *mailaddr);
 static gpg_error_t command_revoke_key (const char *mailaddr);
+static gpg_error_t command_check_key (const char *mailaddr);
 static gpg_error_t command_cron (void);
 
 
 static gpg_error_t command_cron (void);
 
 
@@ -208,6 +225,9 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
         case oGpgProgram:
           opt.gpg_program = pargs->r.ret_str;
           break;
         case oGpgProgram:
           opt.gpg_program = pargs->r.ret_str;
           break;
+        case oDirectory:
+          opt.directory = pargs->r.ret_str;
+          break;
         case oFrom:
           opt.default_from = pargs->r.ret_str;
           break;
         case oFrom:
           opt.default_from = pargs->r.ret_str;
           break;
@@ -220,10 +240,17 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
         case oOutput:
           opt.output = pargs->r.ret_str;
           break;
         case oOutput:
           opt.output = pargs->r.ret_str;
           break;
+        case oWithDir:
+          opt_with_dir = 1;
+          break;
+        case oWithFile:
+          opt_with_file = 1;
+          break;
 
        case aReceive:
         case aCron:
         case aListDomains:
 
        case aReceive:
         case aCron:
         case aListDomains:
+        case aCheck:
         case aInstallKey:
         case aRemoveKey:
         case aRevokeKey:
         case aInstallKey:
         case aRemoveKey:
         case aRevokeKey:
@@ -243,7 +270,7 @@ parse_arguments (ARGPARSE_ARGS *pargs, ARGPARSE_OPTS *popts)
 int
 main (int argc, char **argv)
 {
 int
 main (int argc, char **argv)
 {
-  gpg_error_t err;
+  gpg_error_t err, firsterr;
   ARGPARSE_ARGS pargs;
   enum cmd_and_opt_values cmd;
 
   ARGPARSE_ARGS pargs;
   enum cmd_and_opt_values cmd;
 
@@ -326,6 +353,7 @@ main (int argc, char **argv)
       {
         log_error ("directory '%s' has too relaxed permissions\n",
                    opt.directory);
       {
         log_error ("directory '%s' has too relaxed permissions\n",
                    opt.directory);
+        log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory);
         exit (2);
       }
   }
         exit (2);
       }
   }
@@ -353,23 +381,36 @@ main (int argc, char **argv)
       break;
 
     case aInstallKey:
       break;
 
     case aInstallKey:
-      if (argc != 1)
-        wrong_args ("--install-key FILE");
-      err = command_install_key (*argv);
+      if (argc != 2)
+        wrong_args ("--install-key FILE USER-ID");
+      err = wks_cmd_install_key (*argv, argv[1]);
       break;
 
     case aRemoveKey:
       if (argc != 1)
       break;
 
     case aRemoveKey:
       if (argc != 1)
-        wrong_args ("--remove-key MAILADDR");
-      err = command_remove_key (*argv);
+        wrong_args ("--remove-key USER-ID");
+      err = wks_cmd_remove_key (*argv);
       break;
 
     case aRevokeKey:
       if (argc != 1)
       break;
 
     case aRevokeKey:
       if (argc != 1)
-        wrong_args ("--revoke-key MAILADDR");
+        wrong_args ("--revoke-key USER-ID");
       err = command_revoke_key (*argv);
       break;
 
       err = command_revoke_key (*argv);
       break;
 
+    case aCheck:
+      if (!argc)
+        wrong_args ("--check USER-IDs");
+      firsterr = 0;
+      for (; argc; argc--, argv++)
+        {
+          err = command_check_key (*argv);
+          if (!firsterr)
+            firsterr = err;
+        }
+      err = firsterr;
+      break;
+
     default:
       usage (1);
       err = gpg_error (GPG_ERR_BUG);
     default:
       usage (1);
       err = gpg_error (GPG_ERR_BUG);
@@ -1092,36 +1133,36 @@ static gpg_error_t
 process_new_key (server_ctx_t ctx, estream_t key)
 {
   gpg_error_t err;
 process_new_key (server_ctx_t ctx, estream_t key)
 {
   gpg_error_t err;
-  strlist_t sl;
+  uidinfo_list_t sl;
   const char *s;
   char *dname = NULL;
   char *nonce = NULL;
   char *fname = NULL;
   struct policy_flags_s policybuf;
 
   const char *s;
   char *dname = NULL;
   char *nonce = NULL;
   char *fname = NULL;
   struct policy_flags_s policybuf;
 
+  memset (&policybuf, 0, sizeof policybuf);
+
   /* First figure out the user id from the key.  */
   xfree (ctx->fpr);
   /* First figure out the user id from the key.  */
   xfree (ctx->fpr);
-  free_strlist (ctx->mboxes);
+  free_uidinfo_list (ctx->mboxes);
   err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
   err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
-  if (!ctx->fpr)
-    {
-      log_error ("error parsing key (no fingerprint)\n");
-      err = gpg_error (GPG_ERR_NO_PUBKEY);
-      goto leave;
-    }
+  log_assert (ctx->fpr);
   log_info ("fingerprint: %s\n", ctx->fpr);
   for (sl = ctx->mboxes; sl; sl = sl->next)
     {
   log_info ("fingerprint: %s\n", ctx->fpr);
   for (sl = ctx->mboxes; sl; sl = sl->next)
     {
-      log_info ("  addr-spec: %s\n", sl->d);
+      if (sl->mbox)
+        log_info ("  addr-spec: %s\n", sl->mbox);
     }
 
   /* Walk over all user ids and send confirmation requests for those
    * we support.  */
   for (sl = ctx->mboxes; sl; sl = sl->next)
     {
     }
 
   /* Walk over all user ids and send confirmation requests for those
    * we support.  */
   for (sl = ctx->mboxes; sl; sl = sl->next)
     {
-      s = strchr (sl->d, '@');
+      if (!sl->mbox)
+        continue;
+      s = strchr (sl->mbox, '@');
       log_assert (s && s[1]);
       xfree (dname);
       dname = make_filename_try (opt.directory, s+1, NULL);
       log_assert (s && s[1]);
       xfree (dname);
       dname = make_filename_try (opt.directory, s+1, NULL);
@@ -1133,26 +1174,26 @@ process_new_key (server_ctx_t ctx, estream_t key)
 
       if (access (dname, W_OK))
         {
 
       if (access (dname, W_OK))
         {
-          log_info ("skipping address '%s': Domain not configured\n", sl->d);
+          log_info ("skipping address '%s': Domain not configured\n", sl->mbox);
           continue;
         }
           continue;
         }
-      if (get_policy_flags (&policybuf, sl->d))
+      if (get_policy_flags (&policybuf, sl->mbox))
         {
         {
-          log_info ("skipping address '%s': Bad policy flags\n", sl->d);
+          log_info ("skipping address '%s': Bad policy flags\n", sl->mbox);
           continue;
         }
 
       if (policybuf.auth_submit)
         {
           /* Bypass the confirmation stuff and publish the key as is.  */
           continue;
         }
 
       if (policybuf.auth_submit)
         {
           /* Bypass the confirmation stuff and publish the key as is.  */
-          log_info ("publishing address '%s'\n", sl->d);
+          log_info ("publishing address '%s'\n", sl->mbox);
           /* 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
         {
           /* 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);
+          log_info ("storing address '%s'\n", sl->mbox);
 
           xfree (nonce);
           xfree (fname);
 
           xfree (nonce);
           xfree (fname);
@@ -1160,7 +1201,7 @@ process_new_key (server_ctx_t ctx, estream_t key)
           if (err)
             goto leave;
 
           if (err)
             goto leave;
 
-          err = send_confirmation_request (ctx, sl->d, nonce, fname);
+          err = send_confirmation_request (ctx, sl->mbox, nonce, fname);
           if (err)
             goto leave;
         }
           if (err)
             goto leave;
         }
@@ -1172,6 +1213,7 @@ process_new_key (server_ctx_t ctx, estream_t key)
   xfree (nonce);
   xfree (fname);
   xfree (dname);
   xfree (nonce);
   xfree (fname);
   xfree (dname);
+  wks_free_policy (&policybuf);
   return err;
 }
 
   return err;
 }
 
@@ -1313,7 +1355,7 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
   char *hash = NULL;
   const char *domain;
   const char *s;
   char *hash = NULL;
   const char *domain;
   const char *s;
-  strlist_t sl;
+  uidinfo_list_t sl;
   char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
 
   /* FIXME: There is a bug in name-value.c which adds white space for
   char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
 
   /* FIXME: There is a bug in name-value.c which adds white space for
@@ -1351,25 +1393,21 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
 
   /* We need to get the fingerprint from the key.  */
   xfree (ctx->fpr);
 
   /* We need to get the fingerprint from the key.  */
   xfree (ctx->fpr);
-  free_strlist (ctx->mboxes);
+  free_uidinfo_list (ctx->mboxes);
   err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
   err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
   if (err)
     goto leave;
-  if (!ctx->fpr)
-    {
-      log_error ("error parsing key (no fingerprint)\n");
-      err = gpg_error (GPG_ERR_NO_PUBKEY);
-      goto leave;
-    }
+  log_assert (ctx->fpr);
   log_info ("fingerprint: %s\n", ctx->fpr);
   for (sl = ctx->mboxes; sl; sl = sl->next)
   log_info ("fingerprint: %s\n", ctx->fpr);
   for (sl = ctx->mboxes; sl; sl = sl->next)
-    log_info ("  addr-spec: %s\n", sl->d);
+    if (sl->mbox)
+      log_info ("  addr-spec: %s\n", sl->mbox);
 
   /* Check that the key has 'address' as a user id.  We use
    * case-insensitive matching because the client is expected to
    * return the address verbatim.  */
   for (sl = ctx->mboxes; sl; sl = sl->next)
 
   /* Check that the key has 'address' as a user id.  We use
    * case-insensitive matching because the client is expected to
    * return the address verbatim.  */
   for (sl = ctx->mboxes; sl; sl = sl->next)
-    if (!strcmp (sl->d, address))
+    if (sl->mbox && !strcmp (sl->mbox, address))
       break;
   if (!sl)
     {
       break;
   if (!sl)
     {
@@ -1379,24 +1417,10 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
       goto leave;
     }
 
       goto leave;
     }
 
-
   /* Hash user ID and create filename.  */
   /* Hash user ID and create filename.  */
-  s = strchr (address, '@');
-  log_assert (s);
-  gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, address, s - address);
-  hash = zb32_encode (shaxbuf, 8*20);
-  if (!hash)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
-
-  fnewname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
-  if (!fnewname)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
+  err = wks_compute_hu_fname (&fnewname, address);
+  if (err)
+    goto leave;
 
   /* Publish.  */
   err = copy_key_as_binary (fname, fnewname, address);
 
   /* Publish.  */
   err = copy_key_as_binary (fname, fnewname, address);
@@ -1565,14 +1589,14 @@ command_receive_cb (void *opaque, const char *mediatype,
     }
 
   xfree (ctx.fpr);
     }
 
   xfree (ctx.fpr);
-  free_strlist (ctx.mboxes);
+  free_uidinfo_list (ctx.mboxes);
 
   return err;
 }
 
 
 \f
 
   return err;
 }
 
 
 \f
-/* Return a list of all configured domains.  ECh list element is the
+/* Return a list of all configured domains.  Each list element is the
  * top directory for the domain.  To figure out the actual domain
  * name strrchr(name, '/') can be used.  */
 static gpg_error_t
  * top directory for the domain.  To figure out the actual domain
  * name strrchr(name, '/') can be used.  */
 static gpg_error_t
@@ -1782,7 +1806,11 @@ command_list_domains (void)
       domain = strrchr (sl->d, '/');
       log_assert (domain);
       domain++;
       domain = strrchr (sl->d, '/');
       log_assert (domain);
       domain++;
-      es_printf ("%s\n", domain);
+      if (opt_with_dir)
+        es_printf ("%s %s\n", domain, sl->d);
+      else
+        es_printf ("%s\n", domain);
+
 
       /* Check that the required directories are there.  */
       for (i=0; i < DIM (requireddirs); i++)
 
       /* Check that the required directories are there.  */
       for (i=0; i < DIM (requireddirs); i++)
@@ -1847,7 +1875,17 @@ command_list_domains (void)
       if (!fp)
         {
           err = gpg_error_from_syserror ();
       if (!fp)
         {
           err = gpg_error_from_syserror ();
-          if (gpg_err_code (err) != GPG_ERR_ENOENT)
+          if (gpg_err_code (err) == GPG_ERR_ENOENT)
+            {
+              fp = es_fopen (fname, "w");
+              if (!fp)
+                log_error ("domain %s: can't create policy file: %s\n",
+                           domain, gpg_strerror (err));
+              else
+                es_fclose (fp);
+              fp = NULL;
+            }
+          else
             log_error ("domain %s: error in policy file: %s\n",
                        domain, gpg_strerror (err));
         }
             log_error ("domain %s: error in policy file: %s\n",
                        domain, gpg_strerror (err));
         }
@@ -1856,16 +1894,8 @@ command_list_domains (void)
           struct policy_flags_s policy;
           err = wks_parse_policy (&policy, fp, 0);
           es_fclose (fp);
           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);
-            }
+          wks_free_policy (&policy);
         }
         }
-
-
     }
   err = 0;
 
     }
   err = 0;
 
@@ -1897,21 +1927,46 @@ command_cron (void)
 }
 
 
 }
 
 
-/* Install a single key into the WKD by reading FNAME.  */
+/* Check whether the key with USER_ID is installed.  */
 static gpg_error_t
 static gpg_error_t
-command_install_key (const char *fname)
+command_check_key (const char *userid)
 {
 {
-  (void)fname;
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-}
+  gpg_error_t err;
+  char *addrspec = NULL;
+  char *fname = NULL;
 
 
+  err = wks_fname_from_userid (userid, &fname, &addrspec);
+  if (err)
+    goto leave;
 
 
-/* Remove the key with mail address MAILADDR.  */
-static gpg_error_t
-command_remove_key (const char *mailaddr)
-{
-  (void)mailaddr;
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  if (access (fname, R_OK))
+    {
+      err = gpg_error_from_syserror ();
+      if (opt_with_file)
+        es_printf ("%s n %s\n", addrspec, fname);
+      if (gpg_err_code (err) == GPG_ERR_ENOENT)
+        {
+          if (!opt.quiet)
+            log_info ("key for '%s' is NOT installed\n", addrspec);
+          log_inc_errorcount ();
+          err = 0;
+        }
+      else
+        log_error ("error stating '%s': %s\n", fname, gpg_strerror (err));
+      goto leave;
+    }
+
+  if (opt_with_file)
+    es_printf ("%s i %s\n", addrspec, fname);
+
+  if (opt.verbose)
+    log_info ("key for '%s' is installed\n", addrspec);
+  err = 0;
+
+ leave:
+  xfree (fname);
+  xfree (addrspec);
+  return err;
 }
 
 
 }
 
 
@@ -1919,6 +1974,7 @@ command_remove_key (const char *mailaddr)
 static gpg_error_t
 command_revoke_key (const char *mailaddr)
 {
 static gpg_error_t
 command_revoke_key (const char *mailaddr)
 {
-  (void)mailaddr;
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  /* Remove should be different from removing but we have not yet
+   * defined a suitable way to do this.  */
+  return wks_cmd_remove_key (mailaddr);
 }
 }