2016-06-29 12:00:22 +02:00
|
|
|
|
/* gpg-wks-server.c - A server for the Web Key Service protocols.
|
2018-02-20 11:45:58 +01:00
|
|
|
|
* Copyright (C) 2016, 2018 Werner Koch
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
|
2016-06-29 12:00:22 +02:00
|
|
|
|
*
|
|
|
|
|
* This file is part of GnuPG.
|
|
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* This file is free software; you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU Lesser General Public License as
|
|
|
|
|
* published by the Free Software Foundation; either version 2.1 of
|
|
|
|
|
* the License, or (at your option) any later version.
|
2016-06-29 12:00:22 +02:00
|
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* This file is distributed in the hope that it will be useful,
|
2016-06-29 12:00:22 +02:00
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* GNU Lesser General Public License for more details.
|
2016-06-29 12:00:22 +02:00
|
|
|
|
*
|
2017-06-19 12:37:52 +02:00
|
|
|
|
* You should have received a copy of the GNU Lesser General Public License
|
2016-11-05 12:02:19 +01:00
|
|
|
|
* along with this program; if not, see <https://www.gnu.org/licenses/>.
|
2020-02-21 20:28:47 +01:00
|
|
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
2016-06-29 12:00:22 +02:00
|
|
|
|
*/
|
|
|
|
|
|
2016-08-31 16:39:55 +02:00
|
|
|
|
/* The Web Key Service I-D defines an update protocol to store a
|
2016-06-29 12:00:22 +02:00
|
|
|
|
* public key in the Web Key Directory. The current specification is
|
2018-02-20 11:45:58 +01:00
|
|
|
|
* draft-koch-openpgp-webkey-service-05.txt.
|
2016-06-29 12:00:22 +02:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include <config.h>
|
2020-02-21 20:28:47 +01:00
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <unistd.h>
|
2016-07-12 20:18:22 +02:00
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <dirent.h>
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2020-02-10 16:37:34 +01:00
|
|
|
|
#define INCLUDED_BY_MAIN_MODULE 1
|
2017-03-07 12:38:22 +01:00
|
|
|
|
#include "../common/util.h"
|
|
|
|
|
#include "../common/init.h"
|
|
|
|
|
#include "../common/sysutils.h"
|
2018-02-20 15:23:19 +01:00
|
|
|
|
#include "../common/userids.h"
|
2017-03-07 12:38:22 +01:00
|
|
|
|
#include "../common/ccparray.h"
|
|
|
|
|
#include "../common/exectool.h"
|
|
|
|
|
#include "../common/zb32.h"
|
|
|
|
|
#include "../common/mbox-util.h"
|
|
|
|
|
#include "../common/name-value.h"
|
2016-06-29 12:00:22 +02:00
|
|
|
|
#include "mime-maker.h"
|
2016-07-03 00:41:30 +02:00
|
|
|
|
#include "send-mail.h"
|
2016-06-29 12:00:22 +02:00
|
|
|
|
#include "gpg-wks.h"
|
|
|
|
|
|
|
|
|
|
|
2016-07-12 20:18:22 +02:00
|
|
|
|
/* The time we wait for a confirmation response. */
|
|
|
|
|
#define PENDING_TTL (86400 * 3) /* 3 days. */
|
|
|
|
|
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
/* Constants to identify the commands and options. */
|
|
|
|
|
enum cmd_and_opt_values
|
|
|
|
|
{
|
|
|
|
|
aNull = 0,
|
|
|
|
|
|
|
|
|
|
oQuiet = 'q',
|
|
|
|
|
oVerbose = 'v',
|
2016-07-03 00:41:30 +02:00
|
|
|
|
oOutput = 'o',
|
2018-10-26 14:44:32 +02:00
|
|
|
|
oDirectory = 'C',
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
oDebug = 500,
|
|
|
|
|
|
|
|
|
|
aReceive,
|
|
|
|
|
aCron,
|
2016-07-13 11:44:48 +02:00
|
|
|
|
aListDomains,
|
2017-07-26 17:49:39 +02:00
|
|
|
|
aInstallKey,
|
|
|
|
|
aRevokeKey,
|
|
|
|
|
aRemoveKey,
|
2017-12-19 17:42:10 +01:00
|
|
|
|
aCheck,
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
oGpgProgram,
|
2016-07-03 00:41:30 +02:00
|
|
|
|
oSend,
|
2016-07-02 18:59:22 +02:00
|
|
|
|
oFrom,
|
|
|
|
|
oHeader,
|
2017-12-19 17:42:10 +01:00
|
|
|
|
oWithDir,
|
|
|
|
|
oWithFile,
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
oDummy
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* The list of commands and options. */
|
2020-02-21 20:28:47 +01:00
|
|
|
|
static gpgrt_opt_t opts[] = {
|
2016-06-29 12:00:22 +02:00
|
|
|
|
ARGPARSE_group (300, ("@Commands:\n ")),
|
|
|
|
|
|
|
|
|
|
ARGPARSE_c (aReceive, "receive",
|
|
|
|
|
("receive a submission or confirmation")),
|
|
|
|
|
ARGPARSE_c (aCron, "cron",
|
|
|
|
|
("run regular jobs")),
|
2016-07-13 11:44:48 +02:00
|
|
|
|
ARGPARSE_c (aListDomains, "list-domains",
|
|
|
|
|
("list configured domains")),
|
2017-12-19 17:42:10 +01:00
|
|
|
|
ARGPARSE_c (aCheck, "check",
|
|
|
|
|
("check whether a key is installed")),
|
|
|
|
|
ARGPARSE_c (aCheck, "check-key", "@"),
|
2017-07-26 17:49:39 +02:00
|
|
|
|
ARGPARSE_c (aInstallKey, "install-key",
|
2017-12-19 17:42:10 +01:00
|
|
|
|
"install a key from FILE into the WKD"),
|
2017-07-26 17:49:39 +02:00
|
|
|
|
ARGPARSE_c (aRemoveKey, "remove-key",
|
2017-12-19 17:42:10 +01:00
|
|
|
|
"remove a key from the WKD"),
|
2017-07-26 17:49:39 +02:00
|
|
|
|
ARGPARSE_c (aRevokeKey, "revoke-key",
|
2017-12-19 17:42:10 +01:00
|
|
|
|
"mark a key as revoked"),
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
ARGPARSE_group (301, ("@\nOptions:\n ")),
|
|
|
|
|
|
|
|
|
|
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
|
|
|
|
|
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
|
|
|
|
|
ARGPARSE_s_s (oDebug, "debug", "@"),
|
|
|
|
|
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
|
2016-07-03 00:41:30 +02:00
|
|
|
|
ARGPARSE_s_n (oSend, "send", "send the mail using sendmail"),
|
|
|
|
|
ARGPARSE_s_s (oOutput, "output", "|FILE|write the mail to FILE"),
|
2018-10-26 14:44:32 +02:00
|
|
|
|
ARGPARSE_s_s (oDirectory, "directory", "|DIR|use DIR as top directory"),
|
2016-07-03 00:41:30 +02:00
|
|
|
|
ARGPARSE_s_s (oFrom, "from", "|ADDR|use ADDR as the default sender"),
|
2016-07-02 18:59:22 +02:00
|
|
|
|
ARGPARSE_s_s (oHeader, "header" ,
|
|
|
|
|
"|NAME=VALUE|add \"NAME: VALUE\" as header to all mails"),
|
2017-12-19 17:42:10 +01:00
|
|
|
|
ARGPARSE_s_n (oWithDir, "with-dir", "@"),
|
|
|
|
|
ARGPARSE_s_n (oWithFile, "with-file", "@"),
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
ARGPARSE_end ()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* The list of supported debug flags. */
|
|
|
|
|
static struct debug_flags_s debug_flags [] =
|
|
|
|
|
{
|
2016-09-29 17:55:32 +02:00
|
|
|
|
{ DBG_MIME_VALUE , "mime" },
|
|
|
|
|
{ DBG_PARSER_VALUE , "parser" },
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{ DBG_CRYPTO_VALUE , "crypto" },
|
|
|
|
|
{ DBG_MEMORY_VALUE , "memory" },
|
|
|
|
|
{ DBG_MEMSTAT_VALUE, "memstat" },
|
|
|
|
|
{ DBG_IPC_VALUE , "ipc" },
|
|
|
|
|
{ DBG_EXTPROG_VALUE, "extprog" },
|
|
|
|
|
{ 0, NULL }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* State for processing a message. */
|
|
|
|
|
struct server_ctx_s
|
|
|
|
|
{
|
|
|
|
|
char *fpr;
|
2017-09-18 11:16:07 +02:00
|
|
|
|
uidinfo_list_t mboxes; /* List with addr-specs taken from the UIDs. */
|
2016-09-29 17:55:32 +02:00
|
|
|
|
unsigned int draft_version_2:1; /* Client supports the draft 2. */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
};
|
|
|
|
|
typedef struct server_ctx_s *server_ctx_t;
|
|
|
|
|
|
2017-12-19 17:42:10 +01:00
|
|
|
|
|
|
|
|
|
/* Flag for --with-dir. */
|
|
|
|
|
static int opt_with_dir;
|
|
|
|
|
/* Flag for --with-file. */
|
|
|
|
|
static int opt_with_file;
|
|
|
|
|
|
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
/* Prototypes. */
|
|
|
|
|
static gpg_error_t get_domain_list (strlist_t *r_list);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
static gpg_error_t command_receive_cb (void *opaque,
|
2016-09-29 17:55:32 +02:00
|
|
|
|
const char *mediatype, estream_t fp,
|
|
|
|
|
unsigned int flags);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
static gpg_error_t command_list_domains (void);
|
2017-07-26 17:49:39 +02:00
|
|
|
|
static gpg_error_t command_revoke_key (const char *mailaddr);
|
2017-12-19 17:42:10 +01:00
|
|
|
|
static gpg_error_t command_check_key (const char *mailaddr);
|
2016-07-12 20:18:22 +02:00
|
|
|
|
static gpg_error_t command_cron (void);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2017-02-20 22:19:50 +01:00
|
|
|
|
/* Print usage information and provide strings for help. */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
static const char *
|
|
|
|
|
my_strusage( int level )
|
|
|
|
|
{
|
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
|
|
switch (level)
|
|
|
|
|
{
|
2020-02-21 20:28:47 +01:00
|
|
|
|
case 9: p = "LGPL-2.1-or-later"; break;
|
2017-07-26 17:45:28 +02:00
|
|
|
|
case 11: p = "gpg-wks-server"; break;
|
|
|
|
|
case 12: p = "@GNUPG@"; break;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
case 13: p = VERSION; break;
|
2020-02-21 20:28:47 +01:00
|
|
|
|
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
case 17: p = PRINTABLE_OS_NAME; break;
|
|
|
|
|
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
|
case 40:
|
|
|
|
|
p = ("Usage: gpg-wks-server command [options] (-h for help)");
|
|
|
|
|
break;
|
|
|
|
|
case 41:
|
|
|
|
|
p = ("Syntax: gpg-wks-server command [options]\n"
|
|
|
|
|
"Server for the Web Key Service protocol\n");
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default: p = NULL; break;
|
|
|
|
|
}
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
wrong_args (const char *text)
|
|
|
|
|
{
|
2020-02-21 20:28:47 +01:00
|
|
|
|
es_fprintf (es_stderr, "usage: %s [options] %s\n", gpgrt_strusage (11), text);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
exit (2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Command line parsing. */
|
|
|
|
|
static enum cmd_and_opt_values
|
2020-02-21 20:28:47 +01:00
|
|
|
|
parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
enum cmd_and_opt_values cmd = 0;
|
|
|
|
|
int no_more_options = 0;
|
|
|
|
|
|
2020-02-21 20:28:47 +01:00
|
|
|
|
while (!no_more_options && gpgrt_argparse (NULL, pargs, popts))
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
switch (pargs->r_opt)
|
|
|
|
|
{
|
|
|
|
|
case oQuiet: opt.quiet = 1; break;
|
|
|
|
|
case oVerbose: opt.verbose++; break;
|
|
|
|
|
case oDebug:
|
|
|
|
|
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
|
|
|
|
|
{
|
|
|
|
|
pargs->r_opt = ARGPARSE_INVALID_ARG;
|
|
|
|
|
pargs->err = ARGPARSE_PRINT_ERROR;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case oGpgProgram:
|
|
|
|
|
opt.gpg_program = pargs->r.ret_str;
|
|
|
|
|
break;
|
2018-10-26 14:44:32 +02:00
|
|
|
|
case oDirectory:
|
|
|
|
|
opt.directory = pargs->r.ret_str;
|
|
|
|
|
break;
|
2016-07-02 18:59:22 +02:00
|
|
|
|
case oFrom:
|
|
|
|
|
opt.default_from = pargs->r.ret_str;
|
|
|
|
|
break;
|
|
|
|
|
case oHeader:
|
|
|
|
|
append_to_strlist (&opt.extra_headers, pargs->r.ret_str);
|
|
|
|
|
break;
|
2016-07-03 00:41:30 +02:00
|
|
|
|
case oSend:
|
|
|
|
|
opt.use_sendmail = 1;
|
|
|
|
|
break;
|
|
|
|
|
case oOutput:
|
|
|
|
|
opt.output = pargs->r.ret_str;
|
|
|
|
|
break;
|
2017-12-19 17:42:10 +01:00
|
|
|
|
case oWithDir:
|
|
|
|
|
opt_with_dir = 1;
|
|
|
|
|
break;
|
|
|
|
|
case oWithFile:
|
|
|
|
|
opt_with_file = 1;
|
|
|
|
|
break;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
case aReceive:
|
|
|
|
|
case aCron:
|
2016-07-13 11:44:48 +02:00
|
|
|
|
case aListDomains:
|
2017-12-19 17:42:10 +01:00
|
|
|
|
case aCheck:
|
2017-07-26 17:49:39 +02:00
|
|
|
|
case aInstallKey:
|
|
|
|
|
case aRemoveKey:
|
|
|
|
|
case aRevokeKey:
|
2016-06-29 12:00:22 +02:00
|
|
|
|
cmd = pargs->r_opt;
|
|
|
|
|
break;
|
|
|
|
|
|
2020-02-21 20:28:47 +01:00
|
|
|
|
default: pargs->err = ARGPARSE_PRINT_ERROR; break;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* gpg-wks-server main. */
|
|
|
|
|
int
|
|
|
|
|
main (int argc, char **argv)
|
|
|
|
|
{
|
2017-12-19 17:42:10 +01:00
|
|
|
|
gpg_error_t err, firsterr;
|
2020-02-21 20:28:47 +01:00
|
|
|
|
gpgrt_argparse_t pargs;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
enum cmd_and_opt_values cmd;
|
|
|
|
|
|
|
|
|
|
gnupg_reopen_std ("gpg-wks-server");
|
2020-02-21 20:28:47 +01:00
|
|
|
|
gpgrt_set_strusage (my_strusage);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
log_set_prefix ("gpg-wks-server", GPGRT_LOG_WITH_PREFIX);
|
|
|
|
|
|
|
|
|
|
/* Make sure that our subsystems are ready. */
|
|
|
|
|
init_common_subsystems (&argc, &argv);
|
|
|
|
|
|
|
|
|
|
/* Parse the command line. */
|
|
|
|
|
pargs.argc = &argc;
|
|
|
|
|
pargs.argv = &argv;
|
|
|
|
|
pargs.flags = ARGPARSE_FLAG_KEEP;
|
|
|
|
|
cmd = parse_arguments (&pargs, opts);
|
2020-02-21 20:28:47 +01:00
|
|
|
|
gpgrt_argparse (NULL, &pargs, NULL);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
if (log_get_errorcount (0))
|
|
|
|
|
exit (2);
|
|
|
|
|
|
|
|
|
|
/* Print a warning if an argument looks like an option. */
|
|
|
|
|
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i=0; i < argc; i++)
|
|
|
|
|
if (argv[i][0] == '-' && argv[i][1] == '-')
|
|
|
|
|
log_info (("NOTE: '%s' is not considered an option\n"), argv[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Set defaults for non given options. */
|
|
|
|
|
if (!opt.gpg_program)
|
2024-03-04 14:28:48 +01:00
|
|
|
|
opt.gpg_program = xstrdup (gnupg_module_name (GNUPG_MODULE_NAME_GPG));
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
if (!opt.directory)
|
|
|
|
|
opt.directory = "/var/lib/gnupg/wks";
|
|
|
|
|
|
2016-07-02 18:59:22 +02:00
|
|
|
|
/* Check for syntax errors in the --header option to avoid later
|
|
|
|
|
* error messages with a not easy to find cause */
|
|
|
|
|
if (opt.extra_headers)
|
|
|
|
|
{
|
|
|
|
|
strlist_t sl;
|
|
|
|
|
|
|
|
|
|
for (sl = opt.extra_headers; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
err = mime_maker_add_header (NULL, sl->d, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
log_error ("syntax error in \"--header %s\": %s\n",
|
|
|
|
|
sl->d, gpg_strerror (err));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (log_get_errorcount (0))
|
|
|
|
|
exit (2);
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
/* Check that we have a working directory. */
|
|
|
|
|
#if defined(HAVE_STAT)
|
|
|
|
|
{
|
|
|
|
|
struct stat sb;
|
|
|
|
|
|
2020-10-20 16:38:06 +02:00
|
|
|
|
if (gnupg_stat (opt.directory, &sb))
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error accessing directory '%s': %s\n",
|
|
|
|
|
opt.directory, gpg_strerror (err));
|
|
|
|
|
exit (2);
|
|
|
|
|
}
|
|
|
|
|
if (!S_ISDIR(sb.st_mode))
|
|
|
|
|
{
|
|
|
|
|
log_error ("error accessing directory '%s': %s\n",
|
|
|
|
|
opt.directory, "not a directory");
|
|
|
|
|
exit (2);
|
|
|
|
|
}
|
|
|
|
|
if (sb.st_uid != getuid())
|
|
|
|
|
{
|
|
|
|
|
log_error ("directory '%s' not owned by user\n", opt.directory);
|
|
|
|
|
exit (2);
|
|
|
|
|
}
|
2016-08-31 16:39:55 +02:00
|
|
|
|
if ((sb.st_mode & (S_IROTH|S_IWOTH)))
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
log_error ("directory '%s' has too relaxed permissions\n",
|
|
|
|
|
opt.directory);
|
2018-10-26 14:44:32 +02:00
|
|
|
|
log_info ("Fix by running: chmod o-rw '%s'\n", opt.directory);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
exit (2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else /*!HAVE_STAT*/
|
|
|
|
|
log_fatal ("program build w/o stat() call\n");
|
|
|
|
|
#endif /*!HAVE_STAT*/
|
|
|
|
|
|
|
|
|
|
/* Run the selected command. */
|
|
|
|
|
switch (cmd)
|
|
|
|
|
{
|
|
|
|
|
case aReceive:
|
|
|
|
|
if (argc)
|
|
|
|
|
wrong_args ("--receive");
|
|
|
|
|
err = wks_receive (es_stdin, command_receive_cb, NULL);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case aCron:
|
|
|
|
|
if (argc)
|
|
|
|
|
wrong_args ("--cron");
|
2016-07-12 20:18:22 +02:00
|
|
|
|
err = command_cron ();
|
2016-07-13 11:44:48 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case aListDomains:
|
|
|
|
|
err = command_list_domains ();
|
2016-06-29 12:00:22 +02:00
|
|
|
|
break;
|
|
|
|
|
|
2017-07-26 17:49:39 +02:00
|
|
|
|
case aInstallKey:
|
2018-12-04 15:27:19 +01:00
|
|
|
|
if (!argc)
|
|
|
|
|
err = wks_cmd_install_key (NULL, NULL);
|
|
|
|
|
else if (argc == 2)
|
|
|
|
|
err = wks_cmd_install_key (*argv, argv[1]);
|
|
|
|
|
else
|
|
|
|
|
wrong_args ("--install-key [FILE|FINGERPRINT USER-ID]");
|
2017-07-26 17:49:39 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case aRemoveKey:
|
|
|
|
|
if (argc != 1)
|
2017-12-19 17:42:10 +01:00
|
|
|
|
wrong_args ("--remove-key USER-ID");
|
2018-12-04 09:45:42 +01:00
|
|
|
|
err = wks_cmd_remove_key (*argv);
|
2017-07-26 17:49:39 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case aRevokeKey:
|
|
|
|
|
if (argc != 1)
|
2017-12-19 17:42:10 +01:00
|
|
|
|
wrong_args ("--revoke-key USER-ID");
|
2017-07-26 17:49:39 +02:00
|
|
|
|
err = command_revoke_key (*argv);
|
|
|
|
|
break;
|
|
|
|
|
|
2017-12-19 17:42:10 +01:00
|
|
|
|
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;
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
default:
|
2020-02-21 20:28:47 +01:00
|
|
|
|
gpgrt_usage (1);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
err = gpg_error (GPG_ERR_BUG);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
if (err)
|
|
|
|
|
log_error ("command failed: %s\n", gpg_strerror (err));
|
2016-06-29 12:00:22 +02:00
|
|
|
|
return log_get_errorcount (0)? 1:0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-07-15 17:20:43 +02:00
|
|
|
|
/* Take the key in KEYFILE and write it to OUTFILE in binary encoding.
|
|
|
|
|
* If ADDRSPEC is given only matching user IDs are included in the
|
|
|
|
|
* output. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
copy_key_as_binary (const char *keyfile, const char *outfile,
|
|
|
|
|
const char *addrspec)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ccparray_t ccp;
|
2016-10-06 14:17:03 +02:00
|
|
|
|
const char **argv = NULL;
|
2016-07-15 17:20:43 +02:00
|
|
|
|
char *filterexp = NULL;
|
|
|
|
|
|
|
|
|
|
if (addrspec)
|
|
|
|
|
{
|
|
|
|
|
filterexp = es_bsprintf ("keep-uid=mbox = %s", addrspec);
|
|
|
|
|
if (!filterexp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error allocating memory buffer: %s\n",
|
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ccparray_init (&ccp, 0);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, "--no-options");
|
|
|
|
|
if (!opt.verbose)
|
|
|
|
|
ccparray_put (&ccp, "--quiet");
|
|
|
|
|
else if (opt.verbose > 1)
|
|
|
|
|
ccparray_put (&ccp, "--verbose");
|
|
|
|
|
ccparray_put (&ccp, "--batch");
|
|
|
|
|
ccparray_put (&ccp, "--yes");
|
|
|
|
|
ccparray_put (&ccp, "--always-trust");
|
|
|
|
|
ccparray_put (&ccp, "--no-keyring");
|
|
|
|
|
ccparray_put (&ccp, "--output");
|
|
|
|
|
ccparray_put (&ccp, outfile);
|
|
|
|
|
ccparray_put (&ccp, "--import-options=import-export");
|
|
|
|
|
if (filterexp)
|
|
|
|
|
{
|
|
|
|
|
ccparray_put (&ccp, "--import-filter");
|
|
|
|
|
ccparray_put (&ccp, filterexp);
|
|
|
|
|
}
|
|
|
|
|
ccparray_put (&ccp, "--import");
|
|
|
|
|
ccparray_put (&ccp, "--");
|
|
|
|
|
ccparray_put (&ccp, keyfile);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, NULL);
|
|
|
|
|
argv = ccparray_get (&ccp, NULL);
|
|
|
|
|
if (!argv)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
|
|
|
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("%s failed: %s\n", __func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (filterexp);
|
|
|
|
|
xfree (argv);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-07-12 16:54:55 +02:00
|
|
|
|
/* Take the key in KEYFILE and write it to DANEFILE using the DANE
|
|
|
|
|
* output format. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
copy_key_as_dane (const char *keyfile, const char *danefile)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ccparray_t ccp;
|
|
|
|
|
const char **argv;
|
|
|
|
|
|
|
|
|
|
ccparray_init (&ccp, 0);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, "--no-options");
|
|
|
|
|
if (!opt.verbose)
|
|
|
|
|
ccparray_put (&ccp, "--quiet");
|
|
|
|
|
else if (opt.verbose > 1)
|
|
|
|
|
ccparray_put (&ccp, "--verbose");
|
|
|
|
|
ccparray_put (&ccp, "--batch");
|
|
|
|
|
ccparray_put (&ccp, "--yes");
|
|
|
|
|
ccparray_put (&ccp, "--always-trust");
|
|
|
|
|
ccparray_put (&ccp, "--no-keyring");
|
|
|
|
|
ccparray_put (&ccp, "--output");
|
|
|
|
|
ccparray_put (&ccp, danefile);
|
|
|
|
|
ccparray_put (&ccp, "--export-options=export-dane");
|
|
|
|
|
ccparray_put (&ccp, "--import-options=import-export");
|
|
|
|
|
ccparray_put (&ccp, "--import");
|
|
|
|
|
ccparray_put (&ccp, "--");
|
|
|
|
|
ccparray_put (&ccp, keyfile);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, NULL);
|
|
|
|
|
argv = ccparray_get (&ccp, NULL);
|
|
|
|
|
if (!argv)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
|
|
|
|
|
NULL, NULL, NULL, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("%s failed: %s\n", __func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (argv);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
static void
|
|
|
|
|
encrypt_stream_status_cb (void *opaque, const char *keyword, char *args)
|
|
|
|
|
{
|
|
|
|
|
(void)opaque;
|
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
if (DBG_CRYPTO)
|
|
|
|
|
log_debug ("gpg status: %s %s\n", keyword, args);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Encrypt the INPUT stream to a new stream which is stored at success
|
2016-07-06 15:50:57 +02:00
|
|
|
|
* at R_OUTPUT. Encryption is done for the key in file KEYFIL. */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
static gpg_error_t
|
2016-07-06 15:50:57 +02:00
|
|
|
|
encrypt_stream (estream_t *r_output, estream_t input, const char *keyfile)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ccparray_t ccp;
|
|
|
|
|
const char **argv;
|
|
|
|
|
estream_t output;
|
|
|
|
|
|
|
|
|
|
*r_output = NULL;
|
|
|
|
|
|
|
|
|
|
output = es_fopenmem (0, "w+b");
|
|
|
|
|
if (!output)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ccparray_init (&ccp, 0);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, "--no-options");
|
|
|
|
|
if (!opt.verbose)
|
|
|
|
|
ccparray_put (&ccp, "--quiet");
|
|
|
|
|
else if (opt.verbose > 1)
|
|
|
|
|
ccparray_put (&ccp, "--verbose");
|
|
|
|
|
ccparray_put (&ccp, "--batch");
|
|
|
|
|
ccparray_put (&ccp, "--status-fd=2");
|
|
|
|
|
ccparray_put (&ccp, "--always-trust");
|
2016-07-06 15:50:57 +02:00
|
|
|
|
ccparray_put (&ccp, "--no-keyring");
|
2016-06-29 12:00:22 +02:00
|
|
|
|
ccparray_put (&ccp, "--armor");
|
2018-12-18 08:21:03 +01:00
|
|
|
|
ccparray_put (&ccp, "-z0"); /* No compression for improved robustness. */
|
2016-07-06 15:50:57 +02:00
|
|
|
|
ccparray_put (&ccp, "--recipient-file");
|
|
|
|
|
ccparray_put (&ccp, keyfile);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
ccparray_put (&ccp, "--encrypt");
|
|
|
|
|
ccparray_put (&ccp, "--");
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, NULL);
|
|
|
|
|
argv = ccparray_get (&ccp, NULL);
|
|
|
|
|
if (!argv)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
|
|
|
|
|
NULL, output,
|
|
|
|
|
encrypt_stream_status_cb, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("encryption failed: %s\n", gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
es_rewind (output);
|
|
|
|
|
*r_output = output;
|
|
|
|
|
output = NULL;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
es_fclose (output);
|
|
|
|
|
xfree (argv);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
static void
|
|
|
|
|
sign_stream_status_cb (void *opaque, const char *keyword, char *args)
|
|
|
|
|
{
|
|
|
|
|
(void)opaque;
|
|
|
|
|
|
|
|
|
|
if (DBG_CRYPTO)
|
|
|
|
|
log_debug ("gpg status: %s %s\n", keyword, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sign the INPUT stream to a new stream which is stored at success at
|
|
|
|
|
* R_OUTPUT. A detached signature is created using the key specified
|
|
|
|
|
* by USERID. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
sign_stream (estream_t *r_output, estream_t input, const char *userid)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
ccparray_t ccp;
|
|
|
|
|
const char **argv;
|
|
|
|
|
estream_t output;
|
|
|
|
|
|
|
|
|
|
*r_output = NULL;
|
|
|
|
|
|
|
|
|
|
output = es_fopenmem (0, "w+b");
|
|
|
|
|
if (!output)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ccparray_init (&ccp, 0);
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, "--no-options");
|
|
|
|
|
if (!opt.verbose)
|
|
|
|
|
ccparray_put (&ccp, "--quiet");
|
|
|
|
|
else if (opt.verbose > 1)
|
|
|
|
|
ccparray_put (&ccp, "--verbose");
|
|
|
|
|
ccparray_put (&ccp, "--batch");
|
|
|
|
|
ccparray_put (&ccp, "--status-fd=2");
|
|
|
|
|
ccparray_put (&ccp, "--armor");
|
|
|
|
|
ccparray_put (&ccp, "--local-user");
|
|
|
|
|
ccparray_put (&ccp, userid);
|
|
|
|
|
ccparray_put (&ccp, "--detach-sign");
|
|
|
|
|
ccparray_put (&ccp, "--");
|
|
|
|
|
|
|
|
|
|
ccparray_put (&ccp, NULL);
|
|
|
|
|
argv = ccparray_get (&ccp, NULL);
|
|
|
|
|
if (!argv)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = gnupg_exec_tool_stream (opt.gpg_program, argv, input,
|
|
|
|
|
NULL, output,
|
|
|
|
|
sign_stream_status_cb, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("signing failed: %s\n", gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
es_rewind (output);
|
|
|
|
|
*r_output = output;
|
|
|
|
|
output = NULL;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
es_fclose (output);
|
|
|
|
|
xfree (argv);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-07-02 18:59:22 +02:00
|
|
|
|
/* Get the submission address for address MBOX. Caller must free the
|
|
|
|
|
* value. If no address can be found NULL is returned. */
|
|
|
|
|
static char *
|
|
|
|
|
get_submission_address (const char *mbox)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
const char *domain;
|
|
|
|
|
char *fname, *line, *p;
|
|
|
|
|
size_t n;
|
|
|
|
|
estream_t fp;
|
|
|
|
|
|
|
|
|
|
domain = strchr (mbox, '@');
|
|
|
|
|
if (!domain)
|
|
|
|
|
return NULL;
|
|
|
|
|
domain++;
|
|
|
|
|
|
|
|
|
|
fname = make_filename_try (opt.directory, domain, "submission-address", NULL);
|
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("make_filename failed in %s: %s\n",
|
|
|
|
|
__func__, gpg_strerror (err));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fp = es_fopen (fname, "r");
|
|
|
|
|
if (!fp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
log_info ("Note: no specific submission address configured"
|
|
|
|
|
" for domain '%s'\n", domain);
|
|
|
|
|
else
|
|
|
|
|
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
xfree (fname);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line = NULL;
|
|
|
|
|
n = 0;
|
|
|
|
|
if (es_getline (&line, &n, fp) < 0)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
xfree (line);
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
es_fclose (fp);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
|
|
|
|
|
p = strchr (line, '\n');
|
|
|
|
|
if (p)
|
|
|
|
|
*p = 0;
|
|
|
|
|
trim_spaces (line);
|
|
|
|
|
if (!is_valid_mailbox (line))
|
|
|
|
|
{
|
|
|
|
|
log_error ("invalid submission address for domain '%s' detected\n",
|
|
|
|
|
domain);
|
|
|
|
|
xfree (line);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-09-02 16:54:42 +02:00
|
|
|
|
/* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-07-27 11:40:20 +02:00
|
|
|
|
/* Create the name for the pending file from NONCE and ADDRSPEC and
|
|
|
|
|
* store it at R_NAME. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
make_pending_fname (const char *nonce, const char *addrspec, char **r_name)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
const char *domain;
|
|
|
|
|
char *addrspechash = NULL;
|
|
|
|
|
char sha1buf[20];
|
|
|
|
|
|
|
|
|
|
*r_name = NULL;
|
|
|
|
|
|
|
|
|
|
domain = addrspec? strchr (addrspec, '@') : NULL;
|
|
|
|
|
if (!domain || !domain[1] || domain == addrspec)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_INV_ARG);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
domain++;
|
|
|
|
|
if (strchr (domain, '/') || strchr (domain, '\\'))
|
|
|
|
|
{
|
|
|
|
|
log_info ("invalid domain detected ('%s')\n", domain);
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
|
|
|
|
|
addrspechash = zb32_encode (sha1buf, 8*20);
|
|
|
|
|
if (!addrspechash)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*r_name = strconcat (nonce, ".", addrspechash, NULL);
|
|
|
|
|
if (!*r_name)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (addrspechash);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
/* We store the key under the name of the nonce we will then send to
|
2016-07-06 15:50:57 +02:00
|
|
|
|
* the user. On success the nonce is stored at R_NONCE and the file
|
2022-07-27 11:40:20 +02:00
|
|
|
|
* name at R_FNAME. ADDRSPEC is used as part of the pending file name
|
|
|
|
|
* so that the nonce is associated with an address */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
static gpg_error_t
|
2022-07-27 11:40:20 +02:00
|
|
|
|
store_key_as_pending (const char *dir, estream_t key, const char *addrspec,
|
2016-07-06 15:50:57 +02:00
|
|
|
|
char **r_nonce, char **r_fname)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
char *dname = NULL;
|
|
|
|
|
char *fname = NULL;
|
|
|
|
|
char *nonce = NULL;
|
2022-07-27 11:40:20 +02:00
|
|
|
|
char *pendingname = NULL;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
estream_t outfp = NULL;
|
|
|
|
|
char buffer[1024];
|
|
|
|
|
size_t nbytes, nwritten;
|
|
|
|
|
|
|
|
|
|
*r_nonce = NULL;
|
2016-07-06 15:50:57 +02:00
|
|
|
|
*r_fname = NULL;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
dname = make_filename_try (dir, "pending", NULL);
|
|
|
|
|
if (!dname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Create the nonce. We use 20 bytes so that we don't waste a
|
|
|
|
|
* character in our zBase-32 encoding. Using the gcrypt's nonce
|
|
|
|
|
* function is faster than using the strong random function; this is
|
|
|
|
|
* Good Enough for our purpose. */
|
|
|
|
|
log_assert (sizeof buffer > 20);
|
|
|
|
|
gcry_create_nonce (buffer, 20);
|
|
|
|
|
nonce = zb32_encode (buffer, 8 * 20);
|
|
|
|
|
memset (buffer, 0, 20); /* Not actually needed but it does not harm. */
|
|
|
|
|
if (!nonce)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:40:20 +02:00
|
|
|
|
err = make_pending_fname (nonce, addrspec, &pendingname);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fname = strconcat (dname, "/", pendingname, NULL);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* With 128 bits of random we can expect that no other file exists
|
|
|
|
|
* under this name. We use "x" to detect internal errors. */
|
|
|
|
|
outfp = es_fopen (fname, "wbx,mode=-rw");
|
|
|
|
|
if (!outfp)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
es_rewind (key);
|
|
|
|
|
for (;;)
|
|
|
|
|
{
|
|
|
|
|
if (es_read (key, buffer, sizeof buffer, &nbytes))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error reading '%s': %s\n",
|
|
|
|
|
es_fname_get (key), gpg_strerror (err));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!nbytes)
|
|
|
|
|
{
|
|
|
|
|
err = 0;
|
|
|
|
|
goto leave; /* Ready. */
|
|
|
|
|
}
|
|
|
|
|
if (es_write (outfp, buffer, nbytes, &nwritten))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
else if (nwritten != nbytes)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error (GPG_ERR_EIO);
|
|
|
|
|
log_error ("error writing '%s': %s\n", fname, "short write");
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
es_fclose (outfp);
|
|
|
|
|
gnupg_remove (fname);
|
|
|
|
|
}
|
|
|
|
|
else if (es_fclose (outfp))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!err)
|
2016-07-06 15:50:57 +02:00
|
|
|
|
{
|
|
|
|
|
*r_nonce = nonce;
|
|
|
|
|
*r_fname = fname;
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
else
|
2016-07-06 15:50:57 +02:00
|
|
|
|
{
|
|
|
|
|
xfree (nonce);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
xfree (dname);
|
2022-07-27 11:40:20 +02:00
|
|
|
|
xfree (pendingname);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-08-31 16:39:55 +02:00
|
|
|
|
/* Send a confirmation request. DIR is the directory used for the
|
2016-07-02 18:59:22 +02:00
|
|
|
|
* address MBOX. NONCE is the nonce we want to see in the response to
|
2016-07-06 15:50:57 +02:00
|
|
|
|
* this mail. FNAME the name of the file with the key. */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
static gpg_error_t
|
2016-07-02 18:59:22 +02:00
|
|
|
|
send_confirmation_request (server_ctx_t ctx,
|
2016-07-06 15:50:57 +02:00
|
|
|
|
const char *mbox, const char *nonce,
|
|
|
|
|
const char *keyfile)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
estream_t body = NULL;
|
|
|
|
|
estream_t bodyenc = NULL;
|
2016-09-29 17:55:32 +02:00
|
|
|
|
estream_t signeddata = NULL;
|
|
|
|
|
estream_t signature = NULL;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
mime_maker_t mime = NULL;
|
2016-07-02 18:59:22 +02:00
|
|
|
|
char *from_buffer = NULL;
|
|
|
|
|
const char *from;
|
|
|
|
|
strlist_t sl;
|
|
|
|
|
|
|
|
|
|
from = from_buffer = get_submission_address (mbox);
|
|
|
|
|
if (!from)
|
|
|
|
|
{
|
|
|
|
|
from = opt.default_from;
|
|
|
|
|
if (!from)
|
|
|
|
|
{
|
|
|
|
|
log_error ("no sender address found for '%s'\n", mbox);
|
|
|
|
|
err = gpg_error (GPG_ERR_CONFIGURATION);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
log_info ("Note: using default sender address '%s'\n", from);
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
body = es_fopenmem (0, "w+b");
|
|
|
|
|
if (!body)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
|
2016-07-02 18:59:22 +02:00
|
|
|
|
goto leave;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
2016-09-29 17:55:32 +02:00
|
|
|
|
|
|
|
|
|
if (!ctx->draft_version_2)
|
|
|
|
|
{
|
|
|
|
|
/* It is fine to use 8 bit encoding because that is encrypted and
|
|
|
|
|
* only our client will see it. */
|
|
|
|
|
es_fputs ("Content-Type: application/vnd.gnupg.wks\n"
|
|
|
|
|
"Content-Transfer-Encoding: 8bit\n"
|
|
|
|
|
"\n",
|
|
|
|
|
body);
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
es_fprintf (body, ("type: confirmation-request\n"
|
|
|
|
|
"sender: %s\n"
|
|
|
|
|
"address: %s\n"
|
|
|
|
|
"fingerprint: %s\n"
|
|
|
|
|
"nonce: %s\n"),
|
2016-07-02 18:59:22 +02:00
|
|
|
|
from,
|
2016-06-29 12:00:22 +02:00
|
|
|
|
mbox,
|
|
|
|
|
ctx->fpr,
|
|
|
|
|
nonce);
|
|
|
|
|
|
|
|
|
|
es_rewind (body);
|
2016-07-06 15:50:57 +02:00
|
|
|
|
err = encrypt_stream (&bodyenc, body, keyfile);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
es_fclose (body);
|
|
|
|
|
body = NULL;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
err = mime_maker_new (&mime, NULL);
|
2016-07-02 18:59:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "From", from);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "To", mbox);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-07-02 18:59:22 +02:00
|
|
|
|
err = mime_maker_add_header (mime, "Subject", "Confirm your key publication");
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2017-02-07 11:48:58 +01:00
|
|
|
|
|
2017-02-23 20:10:59 +01:00
|
|
|
|
err = mime_maker_add_header (mime, "Wks-Draft-Version",
|
|
|
|
|
STR2(WKS_DRAFT_VERSION));
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
/* Help Enigmail to identify messages. Note that this is in no way
|
2017-02-07 11:48:58 +01:00
|
|
|
|
* secured. */
|
|
|
|
|
err = mime_maker_add_header (mime, "WKS-Phase", "confirm");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2016-07-02 18:59:22 +02:00
|
|
|
|
for (sl = opt.extra_headers; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
err = mime_maker_add_header (mime, sl->d, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
if (!ctx->draft_version_2)
|
|
|
|
|
{
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"multipart/encrypted; "
|
|
|
|
|
"protocol=\"application/pgp-encrypted\"");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_container (mime);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/pgp-encrypted");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_body (mime, "Version: 1\n");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/octet-stream");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
err = mime_maker_add_stream (mime, &bodyenc);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
unsigned int partid;
|
|
|
|
|
|
|
|
|
|
/* FIXME: Add micalg. */
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"multipart/signed; "
|
|
|
|
|
"protocol=\"application/pgp-signature\"");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_container (mime);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type", "multipart/mixed");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_container (mime);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
partid = mime_maker_get_partid (mime);
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type", "text/plain");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_body
|
|
|
|
|
(mime,
|
|
|
|
|
"This message has been send to confirm your request\n"
|
|
|
|
|
"to publish your key. If you did not request a key\n"
|
|
|
|
|
"publication, simply ignore this message.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Most mail software can handle this kind of message\n"
|
|
|
|
|
"automatically and thus you would not have seen this\n"
|
|
|
|
|
"message. It seems that your client does not fully\n"
|
|
|
|
|
"support this service. The web page\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" https://gnupg.org/faq/wkd.html\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"explains how you can process this message anyway in\n"
|
|
|
|
|
"a few manual steps.\n");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/vnd.gnupg.wks");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_stream (mime, &bodyenc);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_end_container (mime);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2017-02-23 20:10:59 +01:00
|
|
|
|
/* mime_maker_dump_tree (mime); */
|
2016-09-29 17:55:32 +02:00
|
|
|
|
err = mime_maker_get_part (mime, partid, &signeddata);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = sign_stream (&signature, signeddata, from);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/pgp-signature");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_stream (mime, &signature);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-07-03 00:41:30 +02:00
|
|
|
|
err = wks_send_mime (mime);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
mime_maker_release (mime);
|
2016-09-29 17:55:32 +02:00
|
|
|
|
es_fclose (signature);
|
|
|
|
|
es_fclose (signeddata);
|
2016-07-12 17:27:15 +02:00
|
|
|
|
es_fclose (bodyenc);
|
|
|
|
|
es_fclose (body);
|
2016-07-02 18:59:22 +02:00
|
|
|
|
xfree (from_buffer);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Store the key given by KEY into the pending directory and send a
|
|
|
|
|
* confirmation requests. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
process_new_key (server_ctx_t ctx, estream_t key)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
2017-09-18 11:16:07 +02:00
|
|
|
|
uidinfo_list_t sl;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
const char *s;
|
|
|
|
|
char *dname = NULL;
|
|
|
|
|
char *nonce = NULL;
|
2016-07-06 15:50:57 +02:00
|
|
|
|
char *fname = NULL;
|
2016-09-02 16:54:42 +02:00
|
|
|
|
struct policy_flags_s policybuf;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2018-02-20 09:00:00 +01:00
|
|
|
|
memset (&policybuf, 0, sizeof policybuf);
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
/* First figure out the user id from the key. */
|
2016-12-08 16:11:42 +01:00
|
|
|
|
xfree (ctx->fpr);
|
2017-09-18 11:16:07 +02:00
|
|
|
|
free_uidinfo_list (ctx->mboxes);
|
2016-12-08 16:11:42 +01:00
|
|
|
|
err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2017-09-18 12:52:20 +02:00
|
|
|
|
log_assert (ctx->fpr);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
log_info ("fingerprint: %s\n", ctx->fpr);
|
|
|
|
|
for (sl = ctx->mboxes; sl; sl = sl->next)
|
|
|
|
|
{
|
2017-09-18 11:16:07 +02:00
|
|
|
|
if (sl->mbox)
|
|
|
|
|
log_info (" addr-spec: %s\n", sl->mbox);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Walk over all user ids and send confirmation requests for those
|
|
|
|
|
* we support. */
|
|
|
|
|
for (sl = ctx->mboxes; sl; sl = sl->next)
|
|
|
|
|
{
|
2017-09-18 11:16:07 +02:00
|
|
|
|
if (!sl->mbox)
|
|
|
|
|
continue;
|
|
|
|
|
s = strchr (sl->mbox, '@');
|
2016-06-29 12:00:22 +02:00
|
|
|
|
log_assert (s && s[1]);
|
|
|
|
|
xfree (dname);
|
|
|
|
|
dname = make_filename_try (opt.directory, s+1, NULL);
|
|
|
|
|
if (!dname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2016-09-02 16:54:42 +02:00
|
|
|
|
|
2020-10-20 10:43:55 +02:00
|
|
|
|
if (gnupg_access (dname, W_OK))
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
2017-09-18 11:16:07 +02:00
|
|
|
|
log_info ("skipping address '%s': Domain not configured\n", sl->mbox);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2017-09-18 11:16:07 +02:00
|
|
|
|
if (get_policy_flags (&policybuf, sl->mbox))
|
2016-09-02 16:54:42 +02:00
|
|
|
|
{
|
2017-09-18 11:16:07 +02:00
|
|
|
|
log_info ("skipping address '%s': Bad policy flags\n", sl->mbox);
|
2016-09-02 16:54:42 +02:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-09-02 16:54:42 +02:00
|
|
|
|
if (policybuf.auth_submit)
|
|
|
|
|
{
|
2017-02-20 22:19:50 +01:00
|
|
|
|
/* Bypass the confirmation stuff and publish the key as is. */
|
2017-09-18 11:16:07 +02:00
|
|
|
|
log_info ("publishing address '%s'\n", sl->mbox);
|
2016-09-02 16:54:42 +02:00
|
|
|
|
/* 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
|
|
|
|
|
{
|
2017-09-18 11:16:07 +02:00
|
|
|
|
log_info ("storing address '%s'\n", sl->mbox);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-09-02 16:54:42 +02:00
|
|
|
|
xfree (nonce);
|
|
|
|
|
xfree (fname);
|
2022-07-27 11:40:20 +02:00
|
|
|
|
err = store_key_as_pending (dname, key, sl->mbox, &nonce, &fname);
|
2016-09-02 16:54:42 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2017-09-18 11:16:07 +02:00
|
|
|
|
err = send_confirmation_request (ctx, sl->mbox, nonce, fname);
|
2016-09-02 16:54:42 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
if (nonce)
|
|
|
|
|
wipememory (nonce, strlen (nonce));
|
|
|
|
|
xfree (nonce);
|
2016-07-06 15:50:57 +02:00
|
|
|
|
xfree (fname);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
xfree (dname);
|
2018-02-20 09:00:00 +01:00
|
|
|
|
wks_free_policy (&policybuf);
|
2016-07-02 18:59:22 +02:00
|
|
|
|
return err;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-08-31 18:54:09 +02:00
|
|
|
|
/* Send a message to tell the user at MBOX that their key has been
|
|
|
|
|
* published. FNAME the name of the file with the key. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
send_congratulation_message (const char *mbox, const char *keyfile)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
estream_t body = NULL;
|
|
|
|
|
estream_t bodyenc = NULL;
|
|
|
|
|
mime_maker_t mime = NULL;
|
|
|
|
|
char *from_buffer = NULL;
|
|
|
|
|
const char *from;
|
|
|
|
|
strlist_t sl;
|
|
|
|
|
|
|
|
|
|
from = from_buffer = get_submission_address (mbox);
|
|
|
|
|
if (!from)
|
|
|
|
|
{
|
|
|
|
|
from = opt.default_from;
|
|
|
|
|
if (!from)
|
|
|
|
|
{
|
|
|
|
|
log_error ("no sender address found for '%s'\n", mbox);
|
|
|
|
|
err = gpg_error (GPG_ERR_CONFIGURATION);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
log_info ("Note: using default sender address '%s'\n", from);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body = es_fopenmem (0, "w+b");
|
|
|
|
|
if (!body)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
/* It is fine to use 8 bit encoding because that is encrypted and
|
|
|
|
|
* only our client will see it. */
|
|
|
|
|
es_fputs ("Content-Type: text/plain; charset=utf-8\n"
|
|
|
|
|
"Content-Transfer-Encoding: 8bit\n"
|
|
|
|
|
"\n",
|
|
|
|
|
body);
|
|
|
|
|
|
|
|
|
|
es_fprintf (body,
|
|
|
|
|
"Hello!\n\n"
|
|
|
|
|
"The key for your address '%s' has been published\n"
|
|
|
|
|
"and can now be retrieved from the Web Key Directory.\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"For more information on this system see:\n"
|
|
|
|
|
"\n"
|
|
|
|
|
" https://gnupg.org/faq/wkd.html\n"
|
|
|
|
|
"\n"
|
|
|
|
|
"Best regards\n"
|
|
|
|
|
"\n"
|
2021-12-20 17:04:32 +01:00
|
|
|
|
" GnuPG Key Publisher\n\n\n"
|
2016-08-31 18:54:09 +02:00
|
|
|
|
"-- \n"
|
2021-12-20 17:04:32 +01:00
|
|
|
|
"For information on GnuPG see: %s\n",
|
|
|
|
|
mbox, "https://gnupg.org");
|
2016-08-31 18:54:09 +02:00
|
|
|
|
|
|
|
|
|
es_rewind (body);
|
|
|
|
|
err = encrypt_stream (&bodyenc, body, keyfile);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
es_fclose (body);
|
|
|
|
|
body = NULL;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_new (&mime, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "From", from);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "To", mbox);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "Subject", "Your key has been published");
|
2017-02-23 20:10:59 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "Wks-Draft-Version",
|
|
|
|
|
STR2(WKS_DRAFT_VERSION));
|
2017-02-07 11:48:58 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "WKS-Phase", "done");
|
2016-08-31 18:54:09 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
for (sl = opt.extra_headers; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
err = mime_maker_add_header (mime, sl->d, NULL);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"multipart/encrypted; "
|
|
|
|
|
"protocol=\"application/pgp-encrypted\"");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-09-29 10:20:38 +02:00
|
|
|
|
err = mime_maker_add_container (mime);
|
2016-08-31 18:54:09 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/pgp-encrypted");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_body (mime, "Version: 1\n");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
err = mime_maker_add_header (mime, "Content-Type",
|
|
|
|
|
"application/octet-stream");
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = mime_maker_add_stream (mime, &bodyenc);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
err = wks_send_mime (mime);
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
mime_maker_release (mime);
|
|
|
|
|
es_fclose (bodyenc);
|
|
|
|
|
es_fclose (body);
|
|
|
|
|
xfree (from_buffer);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
/* Check that we have send a request with NONCE and publish the key. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
char *fname = NULL;
|
|
|
|
|
char *fnewname = NULL;
|
|
|
|
|
estream_t key = NULL;
|
|
|
|
|
char *hash = NULL;
|
2022-07-27 11:40:20 +02:00
|
|
|
|
char *pendingname = NULL;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
const char *domain;
|
|
|
|
|
const char *s;
|
2017-09-18 11:16:07 +02:00
|
|
|
|
uidinfo_list_t sl;
|
2016-07-12 16:54:55 +02:00
|
|
|
|
char shaxbuf[32]; /* Used for SHA-1 and SHA-256 */
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
2016-07-03 00:41:30 +02:00
|
|
|
|
/* FIXME: There is a bug in name-value.c which adds white space for
|
|
|
|
|
* the last pair and thus we strip the nonce here until this has
|
|
|
|
|
* been fixed. */
|
|
|
|
|
char *nonce2 = xstrdup (nonce);
|
|
|
|
|
trim_trailing_spaces (nonce2);
|
|
|
|
|
nonce = nonce2;
|
|
|
|
|
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
domain = strchr (address, '@');
|
|
|
|
|
log_assert (domain && domain[1]);
|
|
|
|
|
domain++;
|
2022-07-25 09:46:41 +02:00
|
|
|
|
if (strchr (domain, '/') || strchr (domain, '\\')
|
|
|
|
|
|| strchr (nonce, '/') || strchr (nonce, '\\'))
|
|
|
|
|
{
|
|
|
|
|
log_info ("invalid domain or nonce received ('%s', '%s')\n",
|
|
|
|
|
domain, nonce);
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-27 11:40:20 +02:00
|
|
|
|
err = make_pending_fname (nonce, address, &pendingname);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
|
|
|
|
fname = make_filename_try (opt.directory, domain, "pending", pendingname,
|
|
|
|
|
NULL);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Try to open the file with the key. */
|
|
|
|
|
key = es_fopen (fname, "rb");
|
|
|
|
|
if (!key)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
{
|
|
|
|
|
log_info ("no pending request for '%s'\n", address);
|
|
|
|
|
err = gpg_error (GPG_ERR_NOT_FOUND);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We need to get the fingerprint from the key. */
|
2016-12-08 16:11:42 +01:00
|
|
|
|
xfree (ctx->fpr);
|
2017-09-18 11:16:07 +02:00
|
|
|
|
free_uidinfo_list (ctx->mboxes);
|
2016-12-08 16:11:42 +01:00
|
|
|
|
err = wks_list_key (key, &ctx->fpr, &ctx->mboxes);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2017-09-18 12:52:20 +02:00
|
|
|
|
log_assert (ctx->fpr);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
log_info ("fingerprint: %s\n", ctx->fpr);
|
|
|
|
|
for (sl = ctx->mboxes; sl; sl = sl->next)
|
2017-09-18 11:16:07 +02:00
|
|
|
|
if (sl->mbox)
|
|
|
|
|
log_info (" addr-spec: %s\n", sl->mbox);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
/* 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)
|
2017-09-18 11:16:07 +02:00
|
|
|
|
if (sl->mbox && !strcmp (sl->mbox, address))
|
2016-06-29 12:00:22 +02:00
|
|
|
|
break;
|
|
|
|
|
if (!sl)
|
|
|
|
|
{
|
|
|
|
|
log_error ("error publishing key: '%s' is not a user ID of %s\n",
|
|
|
|
|
address, ctx->fpr);
|
|
|
|
|
err = gpg_error (GPG_ERR_NO_PUBKEY);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Hash user ID and create filename. */
|
2018-12-04 09:45:42 +01:00
|
|
|
|
err = wks_compute_hu_fname (&fnewname, address);
|
2018-02-20 11:45:58 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
/* Publish. */
|
2016-07-15 17:20:43 +02:00
|
|
|
|
err = copy_key_as_binary (fname, fnewname, address);
|
|
|
|
|
if (err)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
2016-07-15 17:20:43 +02:00
|
|
|
|
log_error ("copying '%s' to '%s' failed: %s\n",
|
2016-06-29 12:00:22 +02:00
|
|
|
|
fname, fnewname, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-06 13:21:50 +01:00
|
|
|
|
/* Make sure it is world readable. */
|
2021-10-23 15:23:26 +02:00
|
|
|
|
if (gnupg_chmod (fnewname, "-rw-r--r--"))
|
2017-03-06 13:21:50 +01:00
|
|
|
|
log_error ("can't set permissions of '%s': %s\n",
|
|
|
|
|
fnewname, gpg_strerror (gpg_err_code_from_syserror()));
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
log_info ("key %s published for '%s'\n", ctx->fpr, address);
|
2016-08-31 18:54:09 +02:00
|
|
|
|
send_congratulation_message (address, fnewname);
|
2016-07-12 16:54:55 +02:00
|
|
|
|
|
|
|
|
|
/* Try to publish as DANE record if the DANE directory exists. */
|
|
|
|
|
xfree (fname);
|
|
|
|
|
fname = fnewname;
|
|
|
|
|
fnewname = make_filename_try (opt.directory, domain, "dane", NULL);
|
|
|
|
|
if (!fnewname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2020-10-20 10:43:55 +02:00
|
|
|
|
if (!gnupg_access (fnewname, W_OK))
|
2016-07-12 16:54:55 +02:00
|
|
|
|
{
|
|
|
|
|
/* Yes, we have a dane directory. */
|
|
|
|
|
s = strchr (address, '@');
|
|
|
|
|
log_assert (s);
|
|
|
|
|
gcry_md_hash_buffer (GCRY_MD_SHA256, shaxbuf, address, s - address);
|
|
|
|
|
xfree (hash);
|
|
|
|
|
hash = bin2hex (shaxbuf, 28, NULL);
|
|
|
|
|
if (!hash)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
xfree (fnewname);
|
|
|
|
|
fnewname = make_filename_try (opt.directory, domain, "dane", hash, NULL);
|
|
|
|
|
if (!fnewname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
err = copy_key_as_dane (fname, fnewname);
|
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
log_info ("key %s published for '%s' (DANE record)\n", ctx->fpr, address);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
leave:
|
|
|
|
|
es_fclose (key);
|
|
|
|
|
xfree (hash);
|
|
|
|
|
xfree (fnewname);
|
|
|
|
|
xfree (fname);
|
2016-07-03 00:41:30 +02:00
|
|
|
|
xfree (nonce2);
|
2022-07-27 11:40:20 +02:00
|
|
|
|
xfree (pendingname);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Process a confirmation response in MSG. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
process_confirmation_response (server_ctx_t ctx, estream_t msg)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
nvc_t nvc;
|
|
|
|
|
nve_t item;
|
|
|
|
|
const char *value, *sender, *address, *nonce;
|
|
|
|
|
|
|
|
|
|
err = nvc_parse (&nvc, NULL, msg);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("parsing the WKS message failed: %s\n", gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (opt.debug)
|
|
|
|
|
{
|
|
|
|
|
log_debug ("response follows:\n");
|
|
|
|
|
nvc_write (nvc, log_get_stream ());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Check that this is a confirmation response. */
|
|
|
|
|
if (!((item = nvc_lookup (nvc, "type:")) && (value = nve_value (item))
|
|
|
|
|
&& !strcmp (value, "confirmation-response")))
|
|
|
|
|
{
|
|
|
|
|
if (item && value)
|
|
|
|
|
log_error ("received unexpected wks message '%s'\n", value);
|
|
|
|
|
else
|
|
|
|
|
log_error ("received invalid wks message: %s\n", "'type' missing");
|
|
|
|
|
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Get the sender. */
|
|
|
|
|
if (!((item = nvc_lookup (nvc, "sender:")) && (value = nve_value (item))
|
|
|
|
|
&& is_valid_mailbox (value)))
|
|
|
|
|
{
|
|
|
|
|
log_error ("received invalid wks message: %s\n",
|
|
|
|
|
"'sender' missing or invalid");
|
|
|
|
|
err = gpg_error (GPG_ERR_INV_DATA);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
sender = value;
|
|
|
|
|
(void)sender;
|
|
|
|
|
/* FIXME: Do we really need the sender?. */
|
|
|
|
|
|
|
|
|
|
/* Get the address. */
|
|
|
|
|
if (!((item = nvc_lookup (nvc, "address:")) && (value = nve_value (item))
|
|
|
|
|
&& is_valid_mailbox (value)))
|
|
|
|
|
{
|
|
|
|
|
log_error ("received invalid wks message: %s\n",
|
|
|
|
|
"'address' missing or invalid");
|
|
|
|
|
err = gpg_error (GPG_ERR_INV_DATA);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
address = value;
|
|
|
|
|
|
|
|
|
|
/* Get the nonce. */
|
|
|
|
|
if (!((item = nvc_lookup (nvc, "nonce:")) && (value = nve_value (item))
|
|
|
|
|
&& strlen (value) > 16))
|
|
|
|
|
{
|
|
|
|
|
log_error ("received invalid wks message: %s\n",
|
|
|
|
|
"'nonce' missing or too short");
|
|
|
|
|
err = gpg_error (GPG_ERR_INV_DATA);
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
nonce = value;
|
|
|
|
|
|
|
|
|
|
err = check_and_publish (ctx, address, nonce);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
nvc_release (nvc);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Called from the MIME receiver to process the plain text data in MSG . */
|
|
|
|
|
static gpg_error_t
|
2016-09-29 17:55:32 +02:00
|
|
|
|
command_receive_cb (void *opaque, const char *mediatype,
|
|
|
|
|
estream_t msg, unsigned int flags)
|
2016-06-29 12:00:22 +02:00
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
struct server_ctx_s ctx;
|
|
|
|
|
|
|
|
|
|
(void)opaque;
|
|
|
|
|
|
2016-09-29 17:55:32 +02:00
|
|
|
|
memset (&ctx, 0, sizeof ctx);
|
|
|
|
|
if ((flags & WKS_RECEIVE_DRAFT2))
|
|
|
|
|
ctx.draft_version_2 = 1;
|
|
|
|
|
|
2016-06-29 12:00:22 +02:00
|
|
|
|
if (!strcmp (mediatype, "application/pgp-keys"))
|
|
|
|
|
err = process_new_key (&ctx, msg);
|
|
|
|
|
else if (!strcmp (mediatype, "application/vnd.gnupg.wks"))
|
|
|
|
|
err = process_confirmation_response (&ctx, msg);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
log_info ("ignoring unexpected message of type '%s'\n", mediatype);
|
|
|
|
|
err = gpg_error (GPG_ERR_UNEXPECTED_MSG);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xfree (ctx.fpr);
|
2017-09-18 11:16:07 +02:00
|
|
|
|
free_uidinfo_list (ctx.mboxes);
|
2016-06-29 12:00:22 +02:00
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
2016-07-12 20:18:22 +02:00
|
|
|
|
|
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
|
2018-10-26 14:44:32 +02:00
|
|
|
|
/* Return a list of all configured domains. Each list element is the
|
2017-02-20 22:19:50 +01:00
|
|
|
|
* top directory for the domain. To figure out the actual domain
|
2016-07-13 11:44:48 +02:00
|
|
|
|
* name strrchr(name, '/') can be used. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
get_domain_list (strlist_t *r_list)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
2020-10-21 16:59:38 +02:00
|
|
|
|
gnupg_dir_t dir = NULL;
|
2016-07-13 11:44:48 +02:00
|
|
|
|
char *fname = NULL;
|
2020-10-21 16:59:38 +02:00
|
|
|
|
gnupg_dirent_t dentry;
|
2016-07-13 11:44:48 +02:00
|
|
|
|
struct stat sb;
|
|
|
|
|
strlist_t list = NULL;
|
|
|
|
|
|
|
|
|
|
*r_list = NULL;
|
|
|
|
|
|
2020-10-21 16:59:38 +02:00
|
|
|
|
dir = gnupg_opendir (opt.directory);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
if (!dir)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 16:59:38 +02:00
|
|
|
|
while ((dentry = gnupg_readdir (dir)))
|
2016-07-13 11:44:48 +02:00
|
|
|
|
{
|
|
|
|
|
if (*dentry->d_name == '.')
|
|
|
|
|
continue;
|
|
|
|
|
if (!strchr (dentry->d_name, '.'))
|
|
|
|
|
continue; /* No dot - can't be a domain subdir. */
|
|
|
|
|
|
|
|
|
|
xfree (fname);
|
|
|
|
|
fname = make_filename_try (opt.directory, dentry->d_name, NULL);
|
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("make_filename failed in %s: %s\n",
|
|
|
|
|
__func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-20 16:38:06 +02:00
|
|
|
|
if (gnupg_stat (fname, &sb))
|
2016-07-13 11:44:48 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!S_ISDIR(sb.st_mode))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!add_to_strlist_try (&list, fname))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("add_to_strlist failed in %s: %s\n",
|
|
|
|
|
__func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
*r_list = list;
|
|
|
|
|
list = NULL;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
free_strlist (list);
|
2020-10-21 16:59:38 +02:00
|
|
|
|
gnupg_closedir (dir);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
xfree (fname);
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-07-12 20:18:22 +02:00
|
|
|
|
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
expire_one_domain (const char *top_dirname, const char *domain)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err;
|
|
|
|
|
char *dirname;
|
|
|
|
|
char *fname = NULL;
|
2020-10-21 16:59:38 +02:00
|
|
|
|
gnupg_dir_t dir = NULL;
|
|
|
|
|
gnupg_dirent_t dentry;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
struct stat sb;
|
|
|
|
|
time_t now = gnupg_get_time ();
|
|
|
|
|
|
|
|
|
|
dirname = make_filename_try (top_dirname, "pending", NULL);
|
|
|
|
|
if (!dirname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("make_filename failed in %s: %s\n",
|
|
|
|
|
__func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 16:59:38 +02:00
|
|
|
|
dir = gnupg_opendir (dirname);
|
2016-07-12 20:18:22 +02:00
|
|
|
|
if (!dir)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error (("can't access directory '%s': %s\n"),
|
|
|
|
|
dirname, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-21 16:59:38 +02:00
|
|
|
|
while ((dentry = gnupg_readdir (dir)))
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
|
|
|
|
if (*dentry->d_name == '.')
|
|
|
|
|
continue;
|
|
|
|
|
xfree (fname);
|
|
|
|
|
fname = make_filename_try (dirname, dentry->d_name, NULL);
|
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("make_filename failed in %s: %s\n",
|
|
|
|
|
__func__, gpg_strerror (err));
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2022-07-27 11:40:20 +02:00
|
|
|
|
/* The old files are 32 bytes, those created since 2.3.8 are 65 bytes. */
|
|
|
|
|
if (strlen (dentry->d_name) != 32 && strlen (dentry->d_name) != 65)
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
|
|
|
|
log_info ("garbage file '%s' ignored\n", fname);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-10-20 16:38:06 +02:00
|
|
|
|
if (gnupg_stat (fname, &sb))
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("error accessing '%s': %s\n", fname, gpg_strerror (err));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (S_ISDIR(sb.st_mode))
|
|
|
|
|
{
|
|
|
|
|
log_info ("garbage directory '%s' ignored\n", fname);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (sb.st_mtime + PENDING_TTL < now)
|
|
|
|
|
{
|
|
|
|
|
if (opt.verbose)
|
|
|
|
|
log_info ("domain %s: removing pending key '%s'\n",
|
|
|
|
|
domain, dentry->d_name);
|
|
|
|
|
if (remove (fname))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
/* In case the file has just been renamed or another
|
|
|
|
|
* processes is cleaning up, we don't print a diagnostic
|
|
|
|
|
* for ENOENT. */
|
|
|
|
|
if (gpg_err_code (err) != GPG_ERR_ENOENT)
|
|
|
|
|
log_error ("error removing '%s': %s\n",
|
|
|
|
|
fname, gpg_strerror (err));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
leave:
|
2020-10-21 16:59:38 +02:00
|
|
|
|
gnupg_closedir (dir);
|
2016-07-12 20:18:22 +02:00
|
|
|
|
xfree (dirname);
|
|
|
|
|
xfree (fname);
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Scan spool directories and expire too old pending keys. */
|
|
|
|
|
static gpg_error_t
|
2016-07-13 11:44:48 +02:00
|
|
|
|
expire_pending_confirmations (strlist_t domaindirs)
|
|
|
|
|
{
|
|
|
|
|
gpg_error_t err = 0;
|
|
|
|
|
strlist_t sl;
|
|
|
|
|
const char *domain;
|
|
|
|
|
|
|
|
|
|
for (sl = domaindirs; sl; sl = sl->next)
|
|
|
|
|
{
|
|
|
|
|
domain = strrchr (sl->d, '/');
|
|
|
|
|
log_assert (domain);
|
|
|
|
|
domain++;
|
|
|
|
|
|
|
|
|
|
expire_one_domain (sl->d, domain);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* List all configured domains. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
command_list_domains (void)
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
2016-07-13 11:44:48 +02:00
|
|
|
|
static struct {
|
|
|
|
|
const char *name;
|
|
|
|
|
const char *perm;
|
|
|
|
|
} requireddirs[] = {
|
|
|
|
|
{ "pending", "-rwx" },
|
|
|
|
|
{ "hu", "-rwxr-xr-x" }
|
|
|
|
|
};
|
2020-10-20 10:43:55 +02:00
|
|
|
|
gpg_err_code_t ec;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
gpg_error_t err;
|
2016-07-13 11:44:48 +02:00
|
|
|
|
strlist_t domaindirs;
|
|
|
|
|
strlist_t sl;
|
|
|
|
|
const char *domain;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
char *fname = NULL;
|
2016-07-13 11:44:48 +02:00
|
|
|
|
int i;
|
2016-09-02 16:54:42 +02:00
|
|
|
|
estream_t fp;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
err = get_domain_list (&domaindirs);
|
|
|
|
|
if (err)
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
2016-07-13 11:44:48 +02:00
|
|
|
|
log_error ("error reading list of domains: %s\n", gpg_strerror (err));
|
|
|
|
|
return err;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
}
|
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
for (sl = domaindirs; sl; sl = sl->next)
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
2016-07-13 11:44:48 +02:00
|
|
|
|
domain = strrchr (sl->d, '/');
|
|
|
|
|
log_assert (domain);
|
|
|
|
|
domain++;
|
2017-12-19 17:42:10 +01:00
|
|
|
|
if (opt_with_dir)
|
|
|
|
|
es_printf ("%s %s\n", domain, sl->d);
|
|
|
|
|
else
|
|
|
|
|
es_printf ("%s\n", domain);
|
|
|
|
|
|
2016-07-12 20:18:22 +02:00
|
|
|
|
|
2016-07-13 11:44:48 +02:00
|
|
|
|
/* Check that the required directories are there. */
|
|
|
|
|
for (i=0; i < DIM (requireddirs); i++)
|
|
|
|
|
{
|
|
|
|
|
xfree (fname);
|
|
|
|
|
fname = make_filename_try (sl->d, requireddirs[i].name, NULL);
|
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2020-10-20 10:43:55 +02:00
|
|
|
|
if ((ec = gnupg_access (fname, W_OK)))
|
2016-07-13 11:44:48 +02:00
|
|
|
|
{
|
2020-10-20 10:43:55 +02:00
|
|
|
|
err = gpg_error (ec);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
{
|
|
|
|
|
if (gnupg_mkdir (fname, requireddirs[i].perm))
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
log_error ("domain %s: error creating subdir '%s': %s\n",
|
|
|
|
|
domain, requireddirs[i].name,
|
|
|
|
|
gpg_strerror (err));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
log_info ("domain %s: subdir '%s' created\n",
|
|
|
|
|
domain, requireddirs[i].name);
|
|
|
|
|
}
|
|
|
|
|
else if (err)
|
|
|
|
|
log_error ("domain %s: problem with subdir '%s': %s\n",
|
|
|
|
|
domain, requireddirs[i].name, gpg_strerror (err));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-02 16:54:42 +02:00
|
|
|
|
/* Print a warning if the submission address is not configured. */
|
2016-07-12 20:18:22 +02:00
|
|
|
|
xfree (fname);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
fname = make_filename_try (sl->d, "submission-address", NULL);
|
2016-07-12 20:18:22 +02:00
|
|
|
|
if (!fname)
|
|
|
|
|
{
|
|
|
|
|
err = gpg_error_from_syserror ();
|
|
|
|
|
goto leave;
|
|
|
|
|
}
|
2020-10-20 10:43:55 +02:00
|
|
|
|
if ((ec = gnupg_access (fname, F_OK)))
|
2016-07-12 20:18:22 +02:00
|
|
|
|
{
|
2020-10-20 10:43:55 +02:00
|
|
|
|
err = gpg_error (ec);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
if (gpg_err_code (err) == GPG_ERR_ENOENT)
|
|
|
|
|
log_error ("domain %s: submission address not configured\n",
|
|
|
|
|
domain);
|
|
|
|
|
else
|
|
|
|
|
log_error ("domain %s: problem with '%s': %s\n",
|
|
|
|
|
domain, fname, gpg_strerror (err));
|
2016-07-12 20:18:22 +02:00
|
|
|
|
}
|
2016-09-02 16:54:42 +02:00
|
|
|
|
|
|
|
|
|
/* 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 ();
|
2018-10-26 14:44:32 +02:00
|
|
|
|
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
|
2016-09-02 16:54:42 +02:00
|
|
|
|
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);
|
2018-02-20 09:00:00 +01:00
|
|
|
|
wks_free_policy (&policy);
|
2016-09-02 16:54:42 +02:00
|
|
|
|
}
|
2016-07-12 20:18:22 +02:00
|
|
|
|
}
|
|
|
|
|
err = 0;
|
|
|
|
|
|
|
|
|
|
leave:
|
|
|
|
|
xfree (fname);
|
2016-07-13 11:44:48 +02:00
|
|
|
|
free_strlist (domaindirs);
|
2016-07-12 20:18:22 +02:00
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Run regular maintenance jobs. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
command_cron (void)
|
|
|
|
|
{
|
2016-07-13 11:44:48 +02:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
strlist_t domaindirs;
|
|
|
|
|
|
|
|
|
|
err = get_domain_list (&domaindirs);
|
|
|
|
|
if (err)
|
|
|
|
|
{
|
|
|
|
|
log_error ("error reading list of domains: %s\n", gpg_strerror (err));
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = expire_pending_confirmations (domaindirs);
|
|
|
|
|
|
|
|
|
|
free_strlist (domaindirs);
|
|
|
|
|
return err;
|
2016-07-12 20:18:22 +02:00
|
|
|
|
}
|
2017-07-26 17:49:39 +02:00
|
|
|
|
|
|
|
|
|
|
2017-12-19 17:42:10 +01:00
|
|
|
|
/* Check whether the key with USER_ID is installed. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
command_check_key (const char *userid)
|
|
|
|
|
{
|
2020-10-20 10:43:55 +02:00
|
|
|
|
gpg_err_code_t ec;
|
2017-12-19 17:42:10 +01:00
|
|
|
|
gpg_error_t err;
|
|
|
|
|
char *addrspec = NULL;
|
|
|
|
|
char *fname = NULL;
|
|
|
|
|
|
2019-03-22 11:40:01 +01:00
|
|
|
|
err = wks_fname_from_userid (userid, 0, &fname, &addrspec);
|
2017-12-19 17:42:10 +01:00
|
|
|
|
if (err)
|
|
|
|
|
goto leave;
|
|
|
|
|
|
2020-10-20 10:43:55 +02:00
|
|
|
|
if ((ec = gnupg_access (fname, R_OK)))
|
2017-12-19 17:42:10 +01:00
|
|
|
|
{
|
2020-10-20 10:43:55 +02:00
|
|
|
|
err = gpg_error (ec);
|
2017-12-19 17:42:10 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-07-26 17:49:39 +02:00
|
|
|
|
/* Revoke the key with mail address MAILADDR. */
|
|
|
|
|
static gpg_error_t
|
|
|
|
|
command_revoke_key (const char *mailaddr)
|
|
|
|
|
{
|
2017-12-19 17:42:10 +01:00
|
|
|
|
/* Remove should be different from removing but we have not yet
|
|
|
|
|
* defined a suitable way to do this. */
|
2018-12-04 09:45:42 +01:00
|
|
|
|
return wks_cmd_remove_key (mailaddr);
|
2017-07-26 17:49:39 +02:00
|
|
|
|
}
|