diff --git a/tools/Makefile.am b/tools/Makefile.am index 61837a63f..a3fe6e31c 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -1,216 +1,219 @@ # Makefile.am - Tools directory # Copyright (C) 2003, 2007 Free Software Foundation, Inc. # # This file is part of GnuPG. # # GnuPG is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # GnuPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, see . EXTRA_DIST = \ Manifest watchgnupg.c no-libgcrypt.c \ addgnupghome applygnupgdefaults \ lspgpot mail-signed-keys convert-from-106 sockprox.c \ ccidmon.c ChangeLog-2011 \ gpg-connect-agent-w32info.rc gpg-connect-agent.w32-manifest.in \ gpgconf-w32info.rc gpgconf.w32-manifest.in \ gpgtar-w32info.rc gpgtar.w32-manifest.in \ gpg-check-pattern-w32info.rc gpg-check-pattern.w32-manifest.in \ gpg-wks-client-w32info.rc gpg-wks-client.w32-manifest.in \ gpg-card-w32info.rc gpg-card.w32-manifest.in AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM gpg_connect_agent_rc_objs = gpg-connect-agent-w32info.o gpgconf_rc_objs = gpgconf-w32info.o gpg_card_rc_objs = gpg-card-w32info.o gpgtar_rc_objs = gpgtar-w32info.o gpg_check_pattern_rc_objs = gpg-check-pattern-w32info.o gpg_wks_client_rc_objs = gpg-wks-client-w32info.o resource_objs += $(gpg_connect_agent_rc_objs) resource_objs += $(gpgconf_rc_objs) resource_objs += $(gpg_card_tool_rc_objs) resource_objs += $(gpgtar_rc_objs) resource_objs += $(gpg_check_pattern_rc_objs) resource_objs += $(gpg_wks_client_rc_objs) endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(LIBASSUAN_CFLAGS) sbin_SCRIPTS = addgnupghome applygnupgdefaults if BUILD_WKS_TOOLS gpg_wks_server = gpg-wks-server else gpg_wks_server = endif bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card gpg-wks-client if !HAVE_W32_SYSTEM -bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit gpg-auth +bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit else bin_PROGRAMS += gpgconf-w32 endif libexec_PROGRAMS = gpg-check-pattern gpg-pair-tool +if !HAVE_W32_SYSTEM +libexec_PROGRAMS += gpg-auth +endif noinst_PROGRAMS = clean-sat make-dns-cert if BUILD_GPGTAR bin_PROGRAMS += gpgtar else noinst_PROGRAMS += gpgtar endif common_libs = $(libcommon) commonpth_libs = $(libcommonpth) pwquery_libs = ../common/libsimple-pwquery.a regexp_libs = ../regexp/libregexp.a gpgsplit_LDADD = $(common_libs) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(ZLIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c # common sucks in gpg-error, will they, nil they (some compilers # do not eliminate the supposed-to-be-unused-inline-functions). gpgconf_LDADD = $(common_libs) \ $(LIBINTL) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(NETLIBS) \ $(LIBICONV) $(W32SOCKLIBS) \ $(gpgconf_rc_objs) gpgconf_LDFLAGS = $(extra_bin_ldflags) gpgconf_w32_SOURCES = $(gpgconf_SOURCES) gpgconf_w32_LDADD = $(gpgconf_LDADD) gpgconf_w32_LDFLAGS = $(gpgconf_LDFLAGS) -Wl,-subsystem,windows gpgparsemail_SOURCES = gpgparsemail.c rfc822parse.c rfc822parse.h gpgparsemail_LDADD = watchgnupg_SOURCES = watchgnupg.c watchgnupg_LDADD = $(NETLIBS) gpg_connect_agent_SOURCES = gpg-connect-agent.c gpg_connect_agent_LDADD = ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_connect_agent_rc_objs) gpg_card_SOURCES = \ gpg-card.c \ gpg-card.h \ card-call-scd.c \ card-keys.c \ card-yubikey.c \ card-misc.c gpg_card_LDADD = \ ../common/libgpgrl.a $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBREADLINE) $(LIBINTL) $(NETLIBS) $(LIBICONV) \ $(gpg_card_rc_objs) gpg_check_pattern_SOURCES = gpg-check-pattern.c gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_check_pattern_LDADD = $(common_libs) $(regexp_libs) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ $(LIBICONV) \ $(gpg_check_pattern_rc_objs) gpgtar_SOURCES = \ gpgtar.c gpgtar.h \ gpgtar-create.c \ gpgtar-extract.c \ gpgtar-list.c gpgtar_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) gpgtar_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) $(W32SOCKLIBS) \ $(gpgtar_rc_objs) gpg_wks_server_SOURCES = \ gpg-wks-server.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.c mime-maker.h \ send-mail.c send-mail.h gpg_wks_server_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_server_LDADD = $(libcommon) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) gpg_wks_client_SOURCES = \ gpg-wks-client.c \ gpg-wks.h \ wks-util.c \ wks-receive.c \ rfc822parse.c rfc822parse.h \ mime-parser.c mime-parser.h \ mime-maker.h mime-maker.c \ send-mail.c send-mail.h \ call-dirmngr.c call-dirmngr.h gpg_wks_client_CFLAGS = $(LIBASSUAN_CFLAGS) $(LIBGCRYPT_CFLAGS) \ $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_wks_client_LDADD = $(libcommon) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(NETLIBS) \ $(gpg_wks_client_rc_objs) gpg_pair_tool_SOURCES = \ gpg-pair-tool.c gpg_pair_tool_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS) $(INCICONV) gpg_pair_tool_LDADD = $(libcommon) \ $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \ $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) gpg_auth_SOURCES = gpg-auth.c gpg_auth_LDADD = $(common_libs) \ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ $(GPG_ERROR_LIBS) \ $(LIBINTL) $(NETLIBS) $(LIBICONV) # Instead of a symlink we install a simple wrapper script for the new # gpg-wks-client location. We assume bin is a sibling of libexec. install-exec-local: $(mkinstalldirs) $(DESTDIR)$(libexecdir) (set -e ;\ if [ "$(libexecdir)" != "$(bindir)" ]; then \ printf '#!/bin/sh\nexec "$(bindir)/gpg-wks-client" "$$@"\n' \ > $(DESTDIR)$(libexecdir)/gpg-wks-client ;\ chmod +x $(DESTDIR)$(libexecdir)/gpg-wks-client ;\ fi ) uninstall-local: (if [ "$(libexecdir)" != "$(bindir)" ]; then \ rm $(DESTDIR)$(libexecdir)/gpg-wks-client || true ;\ fi ) # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(pwquery_libs) ../common/libgpgrl.a diff --git a/tools/gpg-auth.c b/tools/gpg-auth.c index b7ab6dece..f433ba220 100644 --- a/tools/gpg-auth.c +++ b/tools/gpg-auth.c @@ -1,975 +1,999 @@ /* gpg-auth.c - Authenticate using GnuPG * Copyright (C) 2022 g10 Code GmbH * * This file is part of GnuPG. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/session-env.h" #include "../common/membuf.h" #include "../common/exechelp.h" /* We keep all global options in the structure OPT. */ struct { int interactive; int verbose; unsigned int debug; int quiet; int with_colons; const char *agent_program; int autostart; + int use_scd_directly; /* Options passed to the gpg-agent: */ char *lc_ctype; char *lc_messages; } opt; /* Debug values and macros. */ #define DBG_IPC_VALUE 1024 /* Debug assuan communication. */ #define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */ #define DBG_IPC (opt.debug & DBG_IPC_VALUE) #define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oAgentProgram, oStatusFD, oWithColons, oNoAutostart, oLCctype, oLCmessages, - oChUid, + oUseSCDDirectly, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { 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_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), + ARGPARSE_s_n (oUseSCDDirectly, "use-scdaemon-directly", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-auth"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-auth" " [options] (-h for help)"); break; case 41: p = ("Syntax: gpg-auth" " [options] \n\n" "Tool to authenticate a user using a smartcard.\n" "Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { 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 oAgentProgram: opt.agent_program = pargs->r.ret_str; break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; + case oUseSCDDirectly: opt.use_scd_directly = 1; break; + default: pargs->err = ARGPARSE_PRINT_ERROR; break; } } } struct ga_key_list { struct ga_key_list *next; char keygrip[41]; /* Keygrip to identify a key. */ size_t pubkey_len; char *pubkey; /* Public key in SSH format. */ char *comment; }; /* Local prototypes. */ static gpg_error_t scd_passwd_reset (assuan_context_t ctx, const char *keygrip); static gpg_error_t ga_scd_connect (assuan_context_t *r_scd_ctx, int use_agent); static gpg_error_t ga_scd_get_auth_keys (assuan_context_t ctx, struct ga_key_list **r_key_list); -static gpg_error_t ga_filter_by_authorized_keys (struct ga_key_list **r_key_list); +static gpg_error_t ga_filter_by_authorized_keys (const char *user, + struct ga_key_list **r_key_list); static void ga_release_auth_keys (struct ga_key_list *key_list); static gpg_error_t scd_pkauth (assuan_context_t ctx, const char *keygrip); static gpg_error_t authenticate (assuan_context_t ctx, struct ga_key_list *key_list); static int getpin (const char *comment, const char *info, char *buf, size_t *r_len); /* gpg-auth main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; assuan_context_t scd_ctx = NULL; struct ga_key_list *key_list = NULL; + const char *user; gnupg_reopen_std ("gpg-auth"); gpgrt_set_strusage (my_strusage); log_set_prefix ("gpg-auth", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) exit (2); if (argc != 0) gpgrt_usage (1); /* Never returns. */ - err = ga_scd_connect (&scd_ctx, 1); + if (opt.use_scd_directly) + { + user = getenv ("PAM_USER"); + if (user == NULL) + exit (2); + } + else + user = NULL; + + err = ga_scd_connect (&scd_ctx, opt.use_scd_directly); if (!err) err = ga_scd_get_auth_keys (scd_ctx, &key_list); if (!err) - err = ga_filter_by_authorized_keys (&key_list); + err = ga_filter_by_authorized_keys (user, &key_list); if (!err) err = authenticate (scd_ctx, key_list); ga_release_auth_keys (key_list); if (scd_ctx) assuan_release (scd_ctx); if (err) exit (1); return 0; } static gpg_error_t authenticate (assuan_context_t ctx, struct ga_key_list *key_list) { gpg_error_t err; while (key_list) { err = scd_passwd_reset (ctx, key_list->keygrip); if (err) return err; assuan_set_pointer (ctx, key_list->comment); err = scd_pkauth (ctx, key_list->keygrip); if (!err) /* Success! */ return 0; key_list = key_list->next; } return gpg_error (GPG_ERR_NOT_FOUND); } static gpg_error_t get_serialno_cb (void *opaque, const char *line) { char **serialno = opaque; const char *keyword = line; const char *s; int keywordlen, n; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen)) { if (*serialno) return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */ for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n || (n&1)|| !(spacep (s) || !*s) ) return gpg_error (GPG_ERR_ASS_PARAMETER); *serialno = xtrymalloc (n+1); if (!*serialno) return gpg_error_from_syserror (); memcpy (*serialno, line, n); (*serialno)[n] = 0; } return 0; } /* Helper function, which is used by scd_connect. Try to retrieve the SCDaemon's socket name from the gpg-agent context CTX. On success, *SOCKET_NAME is filled with a copy of the socket name. Return proper error code or zero on success. */ static gpg_error_t agent_scd_getinfo_socket_name (assuan_context_t ctx, char **socket_name) { membuf_t data; gpg_error_t err = 0; unsigned char *databuf; size_t datalen; init_membuf (&data, 256); *socket_name = NULL; err = assuan_transact (ctx, "SCD GETINFO socket_name", put_membuf_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (!err) { if (databuf && datalen) { char *res = xtrymalloc (datalen + 1); if (!res) err = gpg_error_from_syserror (); else { memcpy (res, databuf, datalen); res[datalen] = 0; *socket_name = res; } } } xfree (databuf); return err; } /* Callback parameter for learn card */ struct learn_parm_s { void (*kpinfo_cb)(void*, const char *); void *kpinfo_cb_arg; void (*certinfo_cb)(void*, const char *); void *certinfo_cb_arg; void (*sinfo_cb)(void*, const char *, size_t, const char *); void *sinfo_cb_arg; }; /* Connect to the agent and send the standard options. */ static gpg_error_t start_agent (assuan_context_t *ctx_p) { gpg_error_t err; session_env_t session_env; session_env = session_env_new (); if (!session_env) log_fatal ("error allocating session environment block: %s\n", strerror (errno)); err = start_new_gpg_agent (ctx_p, GPG_ERR_SOURCE_DEFAULT, opt.agent_program, NULL, NULL, session_env, opt.autostart, !opt.quiet, 0, NULL, NULL); session_env_release (session_env); return err; } static gpg_error_t scd_serialno (assuan_context_t ctx) { char *serialno = NULL; gpg_error_t err; err = assuan_transact (ctx, "SERIALNO", NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); xfree (serialno); return err; } static gpg_error_t scd_passwd_reset (assuan_context_t ctx, const char *keygrip) { char line[ASSUAN_LINELENGTH]; gpg_error_t err; snprintf (line, DIM(line), "PASSWD --clear OPENPGP.2 %s", keygrip); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); return err; } /* Connect to scdaemon by pipe or socket. Execute initial "SEREIALNO" command to enable all connected token under scdaemon control. */ static gpg_error_t -ga_scd_connect (assuan_context_t *r_scd_ctx, int use_agent) +ga_scd_connect (assuan_context_t *r_scd_ctx, int use_scd_directly) { assuan_context_t assuan_ctx; gpg_error_t err; err = assuan_new (&assuan_ctx); if (err) return err; - if (use_agent) + if (!use_scd_directly) /* Use scdaemon under gpg-agent. */ { char *scd_socket_name = NULL; assuan_context_t ctx; err = start_agent (&ctx); if (err) return err; /* Note that if gpg-agent is there but no scdaemon yet, * gpg-agent automatically invokes scdaemon by this query * itself. */ err = agent_scd_getinfo_socket_name (ctx, &scd_socket_name); assuan_release (ctx); if (!err) err = assuan_socket_connect (assuan_ctx, scd_socket_name, 0, 0); if (!err && DBG_IPC) log_debug ("got scdaemon socket name from gpg-agent, " "connected to socket '%s'", scd_socket_name); xfree (scd_socket_name); } else { const char *scd_path; const char *pgmname; const char *argv[3]; int no_close_list[2]; scd_path = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); if (!(pgmname = strrchr (scd_path, '/'))) pgmname = scd_path; else pgmname++; /* Fill argument vector for scdaemon. */ argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; no_close_list[0] = assuan_fd_from_posix_fd (fileno (stderr)); no_close_list[1] = ASSUAN_INVALID_FD; /* Connect to the scdaemon */ err = assuan_pipe_connect (assuan_ctx, scd_path, argv, no_close_list, NULL, NULL, 0); if (err) { - log_error ("could not spawn scdaemon: %s", gpg_strerror (err)); + log_error ("could not spawn scdaemon: %s\n", gpg_strerror (err)); return err; } if (DBG_IPC) log_debug ("spawned a new scdaemon (path: '%s')", scd_path); } if (err) assuan_release (assuan_ctx); else { scd_serialno (assuan_ctx); *r_scd_ctx = assuan_ctx; } return err; } /* Handle the NEEDPIN inquiry. */ static gpg_error_t inq_needpin (void *opaque, const char *line) { assuan_context_t ctx = opaque; const char *s; char *pin; size_t pinlen; int rc; const char *comment = assuan_get_pointer (ctx); rc = 0; if ((s = has_leading_keyword (line, "NEEDPIN"))) { line = s; pinlen = 90; pin = gcry_malloc_secure (pinlen); if (!pin) return out_of_core (); rc = getpin (comment, line, pin, &pinlen); if (!rc) { assuan_begin_confidential (ctx); rc = assuan_send_data (ctx, pin, pinlen); assuan_end_confidential (ctx); } wipememory (pin, pinlen); xfree (pin); } else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT"))) { if (comment) { int msg_len = 27 + strlen (comment); fprintf (stdout, "i %d\n", msg_len); fprintf (stdout, "Please use PINPAD for KEY: %s\n", comment); fflush (stdout); } else { fputs ("i 18\n", stdout); fputs ("Please use PINPAD!\n", stdout); fflush (stdout); } } else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT"))) { ; } else { log_error ("unsupported inquiry '%s'\n", line); rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); } return gpg_error (rc); } struct card_keyinfo_parm_s { int error; struct ga_key_list *list; }; /* Callback function for scd_keyinfo_list. */ static gpg_error_t card_keyinfo_cb (void *opaque, const char *line) { gpg_error_t err = 0; struct card_keyinfo_parm_s *parm = opaque; const char *keyword = line; int keywordlen; struct ga_key_list *keyinfo = NULL; for (keywordlen=0; *line && !spacep (line); line++, keywordlen++) ; while (spacep (line)) line++; if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen)) { const char *s; int n; struct ga_key_list **l_p = &parm->list; /* It's going to append the information at the end. */ while ((*l_p)) l_p = &(*l_p)->next; keyinfo = xtrycalloc (1, sizeof *keyinfo); if (!keyinfo) goto alloc_error; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (n != 40) goto parm_error; memcpy (keyinfo->keygrip, line, 40); keyinfo->keygrip[40] = 0; line = s; if (!*line) goto parm_error; while (spacep (line)) line++; if (*line++ != 'T') goto parm_error; if (!*line) goto parm_error; while (spacep (line)) line++; for (n=0,s=line; hexdigitp (s); s++, n++) ; if (!n) goto skip; skip: *l_p = keyinfo; } return err; alloc_error: xfree (keyinfo); if (!parm->error) parm->error = gpg_error_from_syserror (); return 0; parm_error: xfree (keyinfo); if (!parm->error) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); return 0; } /* Call the scdaemon to retrieve list of available keys on cards. On success, the allocated structure is stored at R_KEY_LIST. On error, an error code is returned and NULL is stored at R_KEY_LIST. */ static gpg_error_t scd_keyinfo_list (assuan_context_t ctx, struct ga_key_list **r_key_list) { int err; struct card_keyinfo_parm_s parm; memset (&parm, 0, sizeof parm); err = assuan_transact (ctx, "KEYINFO --list=auth", NULL, NULL, NULL, NULL, card_keyinfo_cb, &parm); if (!err && parm.error) err = parm.error; if (!err) *r_key_list = parm.list; else ga_release_auth_keys (parm.list); return err; } /* A variant of put_membuf_cb, which only put the second field. */ static gpg_error_t put_second_field_cb (void *opaque, const void *buf, size_t len) { char line[ASSUAN_LINELENGTH]; membuf_t *data = opaque; if (buf && len < ASSUAN_LINELENGTH) { const char *fields[3]; size_t field_len; memcpy (line, buf, len); if (split_fields (line, fields, DIM (fields)) < 2) return 0; field_len = strlen (fields[1]); put_membuf (data, fields[1], field_len); } return 0; } static gpg_error_t scd_get_pubkey (assuan_context_t ctx, struct ga_key_list *key) { char line[ASSUAN_LINELENGTH]; membuf_t data; unsigned char *databuf; size_t datalen; gpg_error_t err = 0; init_membuf (&data, 256); snprintf (line, DIM(line), "READKEY --format=ssh %s", key->keygrip); err = assuan_transact (ctx, line, put_second_field_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (!err) { key->pubkey_len = datalen; key->pubkey = databuf; } else xfree (databuf); return err; } static gpg_error_t ga_scd_get_auth_keys (assuan_context_t ctx, struct ga_key_list **r_key_list) { gpg_error_t err; struct ga_key_list *kl, *key_list = NULL; /* Get list of auth keys with their keygrips. */ err = scd_keyinfo_list (ctx, &key_list); /* And retrieve public key for each key. */ kl = key_list; while (kl) { err = scd_get_pubkey (ctx, kl); if (err) break; kl = kl->next; } if (err) ga_release_auth_keys (key_list); else *r_key_list = key_list; return err; } struct ssh_key_list { struct ssh_key_list *next; char *pubkey; /* Public key in SSH format. */ char *comment; }; static void release_ssh_key_list (struct ssh_key_list *key_list) { struct ssh_key_list *key; while (key_list) { key = key_list; key_list = key_list->next; xfree (key->pubkey); xfree (key->comment); xfree (key); } } static gpg_error_t -ssh_authorized_keys (struct ssh_key_list **r_ssh_key_list) +ssh_authorized_keys (const char *user, struct ssh_key_list **r_ssh_key_list) { gpg_error_t err = 0; char *fname = NULL; estream_t fp = NULL; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; const char *fields[3]; struct ssh_key_list *ssh_key_list = NULL; struct ssh_key_list *ssh_key_prev = NULL; struct ssh_key_list *ssh_key = NULL; - fname = make_absfilename_try ("~", ".ssh", "authorized_keys", NULL); + if (user) + { + char tilde_user[256]; + + snprintf (tilde_user, sizeof tilde_user, "~%s", user); + fname = make_absfilename_try (tilde_user, ".ssh", "authorized_keys", NULL); + } + else + fname = make_absfilename_try ("~", ".ssh", "authorized_keys", NULL); + if (fname == NULL) return gpg_error (GPG_ERR_INV_NAME); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); xfree (fname); return err; } xfree (fname); maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; fields[2] = NULL; if (split_fields (line, fields, DIM (fields)) < 2) continue; /* Skip empty lines or line with only a field. */ if (*fields[0] == '#') continue; /* Skip comments. */ ssh_key = xtrycalloc (1, sizeof *ssh_key); if (!ssh_key) { err = gpg_error_from_syserror (); release_ssh_key_list (ssh_key_list); goto leave; } ssh_key->pubkey = strdup (fields[1]); ssh_key->comment = strdup (fields[2]); if (ssh_key_list) ssh_key_prev->next = ssh_key; else ssh_key_list = ssh_key; ssh_key_prev = ssh_key; } *r_ssh_key_list = ssh_key_list; leave: xfree (line); es_fclose (fp); return err; } static gpg_error_t -ga_filter_by_authorized_keys (struct ga_key_list **r_key_list) +ga_filter_by_authorized_keys (const char *user, struct ga_key_list **r_key_list) { gpg_error_t err; struct ga_key_list *cur = *r_key_list; struct ga_key_list *key_list = NULL; struct ga_key_list *prev = NULL; struct ssh_key_list *ssh_key_list = NULL; - err = ssh_authorized_keys (&ssh_key_list); + err = ssh_authorized_keys (user, &ssh_key_list); if (err) return err; if (ssh_key_list == NULL) return gpg_error (GPG_ERR_NOT_FOUND); while (cur) { struct ssh_key_list *skl = ssh_key_list; while (skl) if (!strncmp (cur->pubkey, skl->pubkey, cur->pubkey_len)) break; else skl = skl->next; /* valid? */ if (skl) { if (key_list) prev->next = cur; else key_list = cur; cur->comment = skl->comment; skl->comment = NULL; prev = cur; cur = cur->next; } else { struct ga_key_list *k = cur; cur = cur->next; xfree (k->pubkey); xfree (k); } } if (prev && prev->next) prev->next = NULL; release_ssh_key_list (ssh_key_list); *r_key_list = key_list; return 0; } static void ga_release_auth_keys (struct ga_key_list *key_list) { struct ga_key_list *key; while (key_list) { key = key_list; key_list = key_list->next; xfree (key->pubkey); xfree (key); } } static int getpin (const char *comment, const char *info, char *buf, size_t *r_len) { int rc = 0; char line[ASSUAN_LINELENGTH]; const char *fields[2]; (void)info; if (comment) { int msg_len = 29 + strlen (comment); fprintf (stdout, "P %d\n", msg_len); fprintf (stdout, "Please input PIN for KEY (%s): \n", comment); fflush (stdout); } else { fputs ("P 18\n", stdout); fputs ("Please input PIN: \n", stdout); fflush (stdout); } fgets (line, ASSUAN_LINELENGTH, stdin); if (split_fields (line, fields, DIM (fields)) < DIM (fields)) rc = GPG_ERR_PROTOCOL_VIOLATION; else if (strcmp (fields[0], "p") != 0) rc = GPG_ERR_CANCELED; if (!fgets (line, ASSUAN_LINELENGTH, stdin)) rc = GPG_ERR_PROTOCOL_VIOLATION; if (!rc) { size_t len = strlen (line); /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; len++; /* Include last '\0' in the data. */ if (len > *r_len) rc = GPG_ERR_BUFFER_TOO_SHORT; else memcpy (buf, line, len); *r_len = len; } return rc; } static gpg_error_t scd_pkauth (assuan_context_t ctx, const char *keygrip) { char line[ASSUAN_LINELENGTH]; gpg_error_t err; snprintf (line, DIM(line), "PKAUTH --challenge-response %s", keygrip); err = assuan_transact (ctx, line, NULL, NULL, inq_needpin, ctx, NULL, NULL); return err; }