Implemented more gpg-agen options to support certain passphrase policies.

New tool gpg-check-pattern.
This commit is contained in:
Werner Koch 2007-08-27 18:10:27 +00:00
parent 503f91e0ae
commit 15d0cb42a1
28 changed files with 1166 additions and 72 deletions

View File

@ -1,3 +1,10 @@
2007-08-27 Werner Koch <wk@g10code.com>
* configure.ac: Remove remaining support for internal regex.
Define DISABLE_REGEX automake conditional. Add option
--with-regex.
* autogen.sh [--build-w32]: Remove --disable-regex. Use --with-regex.
2007-08-16 Werner Koch <wk@g10code.com>
Released 2.0.6.

6
NEWS
View File

@ -4,7 +4,11 @@ Noteworthy changes in version 2.0.7
* Fixed encryption problem if duplicate certificates are in the
keybox.
* Made it work on Windows Vista.
* Made it work on Windows Vista. Note that the entire Windows port
is still considered Beta.
* Add new options min-passphrase-nonalpha, check-passphrase-pattern
and enforce-passphrase-constraints to gpg-agent.
Noteworthy changes in version 2.0.6 (2007-08-16)

View File

@ -1,3 +1,22 @@
2007-08-27 Werner Koch <wk@g10code.com>
* gpg-agent.c: Add options --min-passphrase-nonalpha,
--check-passphrase-pattern and --enforce-passphrase-constraints.
(MIN_PASSPHRASE_NONALPHA): Init nonalpha option to 1.
(main): Declare options for gpgconf.
* agent.h (struct): Add members MIN_PASSPHRASE_NONALPHA,
ENFORCE_PASSPHRASE_CONSTRAINTS and CHECK_PASSPHRASE_PATTERN.
* genkey.c (nonalpha_charcount): New.
(check_passphrase_pattern): New.
(check_passphrase_constraints): Implement. Factor some code out...
(take_this_one_anyway, take_this_one_anyway2): .. New.
* call-pinentry.c (agent_show_message): New.
(agent_askpin): We better reset the pin buffer before asking.
* trustlist.c (insert_colons): New.
(agent_marktrusted): Pretty print the fpr.
2007-08-22 Werner Koch <wk@g10code.com>
* findkey.c (O_BINARY): Make sure it is defined.

View File

@ -80,8 +80,15 @@ struct
unsigned long max_cache_ttl; /* Default. */
unsigned long max_cache_ttl_ssh; /* for SSH. */
/* Flag disallowin bypassing of the warning. */
int enforce_passphrase_constraints;
/* The require minmum length of a passphrase. */
unsigned int min_passphrase_len;
/* The minimum number of non-alpha characters in a passphrase. */
unsigned int min_passphrase_nonalpha;
/* File name with a patternfile or NULL if not enabled. */
const char *check_passphrase_pattern;
int running_detached; /* We are running detached from the tty. */
@ -227,6 +234,7 @@ int agent_get_passphrase (ctrl_t ctrl, char **retpass,
const char *errtext);
int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
const char *cancel);
int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
int agent_popup_message_start (ctrl_t ctrl,
const char *desc, const char *ok_btn);
void agent_popup_message_stop (ctrl_t ctrl);

View File

@ -213,7 +213,9 @@ start_pinentry (ctrl_t ctrl)
#endif
if (fflush (NULL))
{
#ifndef HAVE_W32_SYSTEM
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
#endif
log_error ("error flushing pending output: %s\n", strerror (errno));
/* At least Windows XP fails here with EBADF. According to docs
and Wine an fflush(NULL) is the same as _flushall. However
@ -476,6 +478,7 @@ agent_askpin (ctrl_t ctrl,
{
memset (&parm, 0, sizeof parm);
parm.size = pininfo->max_length;
*pininfo->pin = 0; /* Reset the PIN. */
parm.buffer = (unsigned char*)pininfo->pin;
if (errtext)
@ -671,6 +674,55 @@ agent_get_confirmation (ctrl_t ctrl,
}
/* Pop up the PINentry, display the text DESC and a button with the
text OK_BTN (which may be NULL to use the default of "OK") and waut
for the user to hit this button. The return value is not
relevant. */
int
agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn)
{
int rc;
char line[ASSUAN_LINELENGTH];
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
snprintf (line, DIM(line)-1, "SETDESC %s", desc);
else
snprintf (line, DIM(line)-1, "RESET");
line[DIM(line)-1] = 0;
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error and
not to the code for a user cancel. Fix this here. */
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
if (rc)
return unlock_pinentry (rc);
if (ok_btn)
{
snprintf (line, DIM(line)-1, "SETOK %s", ok_btn);
line[DIM(line)-1] = 0;
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
if (rc)
return unlock_pinentry (rc);
}
rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL,
NULL, NULL, NULL);
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
return unlock_pinentry (rc);
}
/* The thread running the popup message. */
static void *
popup_message_thread (void *arg)

View File

@ -27,6 +27,8 @@
#include "agent.h"
#include "i18n.h"
#include "exechelp.h"
#include "sysutils.h"
static int
store_key (gcry_sexp_t private, const char *passphrase, int force)
@ -70,6 +72,100 @@ store_key (gcry_sexp_t private, const char *passphrase, int force)
}
/* Count the number of non-alpha characters in S. Control characters
and non-ascii characters are not considered. */
static size_t
nonalpha_count (const char *s)
{
size_t n;
for (n=0; *s; s++)
if (isascii (*s) && ( isdigit (*s) || ispunct (*s) ))
n++;
return n;
}
/* Check PW against a list of pattern. Return 0 if PW does not match
these pattern. */
static int
check_passphrase_pattern (ctrl_t ctrl, const char *pw)
{
gpg_error_t err = 0;
const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN);
FILE *infp;
const char *argv[10];
pid_t pid;
int result, i;
infp = gnupg_tmpfile ();
if (!infp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating temporary file: %s\n"), strerror (errno));
return 1; /* Error - assume password should not be used. */
}
if (fwrite (pw, strlen (pw), 1, infp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing to temporary file: %s\n"),
strerror (errno));
fclose (infp);
return 1; /* Error - assume password should not be used. */
}
rewind (infp);
i = 0;
argv[i++] = "--null";
argv[i++] = "--",
argv[i++] = opt.check_passphrase_pattern,
argv[i] = NULL;
assert (i < sizeof argv);
if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid))
result = 1; /* Execute error - assume password should no be used. */
else if (gnupg_wait_process (pgmname, pid))
result = 1; /* Helper returned an error - probably a match. */
else
result = 0; /* Success; i.e. no match. */
/* Overwrite our temporary file. */
rewind (infp);
for (i=((strlen (pw)+99)/100)*100; i > 0; i--)
putc ('\xff', infp);
fflush (infp);
fclose (infp);
return result;
}
static int
take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn)
{
gpg_error_t err;
if (opt.enforce_passphrase_constraints)
{
err = agent_show_message (ctrl, desc, _("Enter new passphrase"));
if (!err)
err = gpg_error (GPG_ERR_CANCELED);
}
else
err = agent_get_confirmation (ctrl, desc,
anyway_btn, _("Enter new passphrase"));
return err;
}
static int
take_this_one_anyway (ctrl_t ctrl, const char *desc)
{
return take_this_one_anyway2 (ctrl, desc, _("Take this one anyway"));
}
/* Check whether the passphrase PW is suitable. Returns 0 if the
passphrase is suitable and true if it is not and the user should be
asked to provide a different one. */
@ -78,7 +174,8 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw)
{
gpg_error_t err;
unsigned int minlen = opt.min_passphrase_len;
unsigned int minnonalpha = opt.min_passphrase_nonalpha;
if (!pw)
pw = "";
@ -93,25 +190,60 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw)
"be at least %u characters long.", minlen), minlen );
if (!desc)
return gpg_error_from_syserror ();
err = agent_get_confirmation (ctrl, desc,
_("Take this one anyway"),
_("Enter new passphrase"));
err = take_this_one_anyway (ctrl, desc);
xfree (desc);
if (err)
return err;
}
if (nonalpha_count (pw) < minnonalpha )
{
char *desc = xtryasprintf
( ngettext ("Warning: You have entered a passphrase that%%0A"
"is obviously not secure. A passphrase should%%0A"
"contain at least %u digit or special character.",
"Warning: You have entered a passphrase that%%0A"
"is obviously not secure. A passphrase should%%0A"
"contain at least %u digits or special characters.",
minnonalpha), minnonalpha );
if (!desc)
return gpg_error_from_syserror ();
err = take_this_one_anyway (ctrl, desc);
xfree (desc);
if (err)
return err;
}
/* If configured check the passphrase against a list of know words
and pattern. The actual test is done by an external program.
The warning message is generic to give the user no hint on how to
circumvent this list. */
if (*pw && opt.check_passphrase_pattern &&
check_passphrase_pattern (ctrl, pw))
{
const char *desc =
/* */ _("Warning: You have entered a passphrase that%0A"
"is obviously not secure. A passphrase may not%0A"
"be a known term or match certain pattern.");
err = take_this_one_anyway (ctrl, desc);
if (err)
return err;
}
/* The final check is to warn about an empty passphrase. */
if (!*pw)
{
const char *desc = _("You have not entered a passphrase - "
"this is in general a bad idea!%0A"
"Please confirm that you do not want to "
"have any protection on your key.");
const char *desc = (opt.enforce_passphrase_constraints?
_("You have not entered a passphrase!%0A"
"An empty passphrase is not allowed.") :
_("You have not entered a passphrase - "
"this is in general a bad idea!%0A"
"Please confirm that you do not want to "
"have any protection on your key."));
err = agent_get_confirmation (ctrl, desc,
_("Yes, protection is not needed"),
_("Enter new passphrase"));
err = take_this_one_anyway2 (ctrl, desc,
_("Yes, protection is not needed"));
if (err)
return err;
}

View File

@ -88,7 +88,10 @@ enum cmd_and_opt_values
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oUseStandardSocket,
oNoUseStandardSocket,
@ -149,7 +152,12 @@ static ARGPARSE_OPTS opts[] = {
{ oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" },
{ oMaxCacheTTL, "max-cache-ttl", 4, "@" },
{ oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" },
{ oEnforcePassphraseConstraints, "enforce-passphrase-constraints", 0, "@"},
{ oMinPassphraseLen, "min-passphrase-len", 4, "@" },
{ oMinPassphraseNonalpha, "min-passphrase-nonalpha", 4, "@" },
{ oCheckPassphrasePattern, "check-passphrase-pattern", 2, "@" },
{ oIgnoreCacheForSigning, "ignore-cache-for-signing", 0,
N_("do not use the PIN cache when signing")},
{ oAllowMarkTrusted, "allow-mark-trusted", 0,
@ -168,6 +176,7 @@ static ARGPARSE_OPTS opts[] = {
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
/* The timer tick used for housekeeping stuff. For Windows we use a
longer period as the SetWaitableTimer seems to signal earlier than
@ -362,7 +371,10 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 0;
opt.disable_scdaemon = 0;
@ -402,7 +414,16 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
@ -723,8 +744,15 @@ main (int argc, char **argv )
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
printf ("enforce-passphrase-constraints:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME,
MIN_PASSPHRASE_NONALPHA);
printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME);
printf ("no-grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
printf ("ignore-cache-for-signing:%lu:\n",

View File

@ -455,13 +455,40 @@ agent_listtrusted (void *assuan_context)
}
/* Create a copy of string with colons inserted after each two bytes.
Caller needs to release the string. In case of a memory failure,
NULL is returned. */
static char *
insert_colons (const char *string)
{
char *buffer, *p;
size_t n = strlen (string);
p = buffer = xtrymalloc ( n + (n+2)/3 + 1 );
if (!buffer)
return NULL;
while (*string)
{
*p++ = *string++;
if (*string)
{
*p++ = *string++;
if (*string)
*p++ = ':';
}
}
*p = 0;
return buffer;
}
/* Insert the given fpr into our trustdb. We expect FPR to be an all
uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'.
This function does first check whether that key has alreay been put
This function does first check whether that key has already been put
into the trustdb and returns success in this case. Before a FPR
actually gets inserted, the user is asked by means of the pin-entry
whether this is actual wants he want to do.
*/
actually gets inserted, the user is asked by means of the Pinentry
whether this is actual wants he want to do. */
gpg_error_t
agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
{
@ -469,6 +496,8 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
char *desc;
char *fname;
FILE *fp;
char *fprformatted;
/* Check whether we are at all allowed to modify the trustlist.
This is useful so that the trustlist may be a symlink to a global
@ -494,6 +523,9 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* Insert a new one. */
fprformatted = insert_colons (fpr);
if (!fprformatted)
return out_of_core ();
if (asprintf (&desc,
/* TRANSLATORS: This prompt is shown by the Pinentry
and has one special property: A "%%0A" is used by
@ -503,12 +535,15 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
plain % sign, you need to encode it as "%%25". The
second "%s" gets replaced by a hexdecimal
fingerprint string whereas the first one receives
the name as store in the certificate. */
the name as stored in the certificate. */
_("Please verify that the certificate identified as:%%0A"
" \"%s\"%%0A"
"has the fingerprint:%%0A"
" %s"), name, fpr) < 0 )
return out_of_core ();
" %s"), name, fprformatted) < 0 )
{
xfree (fprformatted);
return out_of_core ();
}
/* TRANSLATORS: "Correct" is the label of a button and intended to
be hit if the fingerprint matches the one of the CA. The other
@ -519,8 +554,11 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
gpgsm may stop asking further questions. We won't do this for
the second question of course. */
if (err)
return (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED ?
gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED) : err);
{
xfree (fprformatted);
return (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED ?
gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED) : err);
}
@ -537,12 +575,18 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
" \"%s\"%%0A"
"to correctly certify user certificates?"),
name) < 0 )
return out_of_core ();
{
xfree (fprformatted);
return out_of_core ();
}
err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No"));
free (desc);
if (err)
return err;
{
xfree (fprformatted);
return err;
}
/* Now check again to avoid duplicates. We take the lock to make
sure that nobody else plays with our file. Frankly we don't work
@ -552,6 +596,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
if (!agent_istrusted (ctrl, fpr))
{
unlock_trusttable ();
xfree (fprformatted);
return 0;
}
@ -566,6 +611,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
log_error ("can't create `%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
return err;
}
fputs (headerblurb, fp);
@ -578,13 +624,14 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
log_error ("can't open `%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
return err;
}
/* Append the key. */
fputs ("\n# ", fp);
print_sanitized_string (fp, name, 0);
fprintf (fp, "\n%s %c\n", fpr, flag);
fprintf (fp, "\n%s %c\n", fprformatted, flag);
if (ferror (fp))
err = gpg_error_from_syserror ();
@ -595,6 +642,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
agent_reload_trustlist ();
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
return err;
}

View File

@ -91,9 +91,9 @@ if test "$1" = "--build-w32"; then
--with-libgcrypt-prefix=${w32root} \
--with-libassuan-prefix=${w32root} \
--with-zlib=${w32root} \
--with-regex=${w32root} \
--with-pth-prefix=${w32root} \
--without-included-gettext \
--disable-regex "$@"
--without-included-gettext "$@"
rc=$?
exit $rc
fi

View File

@ -1,3 +1,12 @@
2007-08-27 Werner Koch <wk@g10code.com>
* util.h (GNUPG_MODULE_NAME_CHECK_PATTERN): New.
* homedir.c (gnupg_module_name): Add it.
* exechelp.c (w32_fd_or_null) [W32]: New.
(gnupg_spawn_process_fd): New.
(gnupg_wait_process) [W32]: Close the handle after if the process has
returned.
2007-08-22 Werner Koch <wk@g10code.com>
Updated estream from libestream.

View File

@ -1,5 +1,5 @@
/* exechelp.c - fork and exec helpers
* Copyright (C) 2004 Free Software Foundation, Inc.
* Copyright (C) 2004, 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
@ -191,6 +191,23 @@ create_inheritable_pipe (int filedes[2])
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
static HANDLE
w32_open_null (int for_write)
{
HANDLE hfile;
hfile = CreateFile ("nul",
for_write? GENERIC_WRITE : GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hfile == INVALID_HANDLE_VALUE)
log_debug ("can't open `nul': %s\n", w32_strerror (-1));
return hfile;
}
#endif /*HAVE_W32_SYSTEM*/
#ifndef HAVE_W32_SYSTEM
/* The exec core used right after the fork. This will never return. */
static void
@ -257,9 +274,9 @@ do_exec (const char *pgmname, const char *argv[],
stdin, write the output to OUTFILE, return a new stream in
STATUSFILE for stderr and the pid of the process in PID. The
arguments for the process are expected in the NULL terminated array
ARGV. The program name itself should not be included there. if
ARGV. The program name itself should not be included there. If
PREEXEC is not NULL, that function will be called right before the
exec.
exec. Calling gnupg_wait_process is required.
Returns 0 on success or an error code. */
gpg_error_t
@ -439,6 +456,119 @@ gnupg_spawn_process (const char *pgmname, const char *argv[],
}
/* Simplified version of gnupg_spawn_process. This function forks and
then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
and ERRFD to stderr (any of them may be -1 to connect them to
/dev/null). The arguments for the process are expected in the NULL
terminated array ARGV. The program name itself should not be
included there. Calling gnupg_wait_process is required.
Returns 0 on success or an error code. */
gpg_error_t
gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
int infd, int outfd, int errfd, pid_t *pid)
{
#ifdef HAVE_W32_SYSTEM
gpg_error_t err;
SECURITY_ATTRIBUTES sec_attr;
PROCESS_INFORMATION pi = { NULL, 0, 0, 0 };
STARTUPINFO si;
char *cmdline;
int i;
HANDLE stdhd[3];
/* Setup return values. */
*pid = (pid_t)(-1);
/* Prepare security attributes. */
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = FALSE;
/* Build the command line. */
err = build_w32_commandline (pgmname, argv, &cmdline);
if (err)
return err;
memset (&si, 0, sizeof si);
si.cb = sizeof (si);
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
stdhd[0] = infd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE;
stdhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
stdhd[2] = errfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
si.hStdInput = infd == -1? stdhd[0] : (void*)_get_osfhandle (infd);
si.hStdOutput = outfd == -1? stdhd[1] : (void*)_get_osfhandle (outfd);
si.hStdError = errfd == -1? stdhd[2] : (void*)_get_osfhandle (errfd);
log_debug ("CreateProcess, path=`%s' cmdline=`%s'\n", pgmname, cmdline);
if (!CreateProcess (pgmname, /* Program to start. */
cmdline, /* Command line arguments. */
&sec_attr, /* Process security attributes. */
&sec_attr, /* Thread security attributes. */
TRUE, /* Inherit handles. */
(CREATE_DEFAULT_ERROR_MODE
| GetPriorityClass (GetCurrentProcess ())
| CREATE_SUSPENDED),
NULL, /* Environment. */
NULL, /* Use current drive/directory. */
&si, /* Startup information. */
&pi /* Returns process information. */
))
{
log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
err = gpg_error (GPG_ERR_GENERAL);
}
else
err = 0;
xfree (cmdline);
for (i=0; i < 3; i++)
if (stdhd[i] != INVALID_HANDLE_VALUE)
CloseHandle (stdhd[i]);
if (err)
return err;
log_debug ("CreateProcess ready: hProcess=%p hThread=%p"
" dwProcessID=%d dwThreadId=%d\n",
pi.hProcess, pi.hThread,
(int) pi.dwProcessId, (int) pi.dwThreadId);
/* Process has been created suspended; resume it now. */
ResumeThread (pi.hThread);
CloseHandle (pi.hThread);
*pid = handle_to_pid (pi.hProcess);
return 0;
#else /* !HAVE_W32_SYSTEM */
gpg_error_t err;
#ifdef USE_GNU_PTH
*pid = pth_fork? pth_fork () : fork ();
#else
*pid = fork ();
#endif
if (*pid == (pid_t)(-1))
{
err = gpg_error_from_syserror ();
log_error (_("error forking process: %s\n"), strerror (errno));
return err;
}
if (!*pid)
{
gcry_control (GCRYCTL_TERM_SECMEM);
/* Run child. */
do_exec (pgmname, argv, infd, outfd, errfd, NULL);
/*NOTREACHED*/
}
return 0;
#endif /* !HAVE_W32_SYSTEM */
}
/* Wait for the process identified by PID to terminate. PGMNAME should
be the same as suplieed to the spawn fucntion and is only used for
diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL for
@ -483,6 +613,7 @@ gnupg_wait_process (const char *pgmname, pid_t pid)
}
else
ec = 0;
CloseHandle (proc);
break;
default:

View File

@ -28,16 +28,31 @@
arguments for the process are expected in the NULL terminated array
ARGV. The program name itself should not be included there. if
PREEXEC is not NULL, that function will be called right before the
exec. Returns 0 on success or an error code. */
exec. Calling gnupg_wait_process is required. Returns 0 on
success or an error code. */
gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[],
FILE *infile, FILE *outfile,
void (*preexec)(void),
FILE **statusfile, pid_t *pid);
/* Simplified version of gnupg_spawn_process. This function forks and
then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
and ERRFD to stderr (any of them may be -1 to connect them to
/dev/null). The arguments for the process are expected in the NULL
terminated array ARGV. The program name itself should not be
included there. Calling gnupg_wait_process is required. Returns 0
on success or an error code. */
gpg_error_t gnupg_spawn_process_fd (const char *pgmname,
const char *argv[],
int infd, int outfd, int errfd,
pid_t *pid);
/* Wait for the process identified by PID to terminate. PGMNAME should
be the same as suplieed to the spawn fucntion and is only used for
diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL for
any failures of the spawned program or other error codes.*/
be the same as supplied to the spawn fucntion and is only used for
diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL
for any failures of the spawned program or other error codes.*/
gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid);

View File

@ -369,6 +369,9 @@ gnupg_module_name (int which)
X(libexecdir, "gpg-protect-tool");
#endif
case GNUPG_MODULE_NAME_CHECK_PATTERN:
X(libexecdir, "gpg-check-pattern");
default:
BUG ();
}

View File

@ -185,6 +185,7 @@ const char *dirmngr_socket_name (void);
#define GNUPG_MODULE_NAME_SCDAEMON 3
#define GNUPG_MODULE_NAME_DIRMNGR 4
#define GNUPG_MODULE_NAME_PROTECT_TOOL 5
#define GNUPG_MODULE_NAME_CHECK_PATTERN 6
const char *gnupg_module_name (int which);

View File

@ -1025,7 +1025,6 @@ GNUPG_FUNC_MKDIR_TAKES_ONE_ARG
#
# Sanity check regex. Tests adapted from mutt.
# FIXME: We should use the the regex from gnulib
#
AC_MSG_CHECKING([whether regular expression support is requested])
AC_ARG_ENABLE(regex,
@ -1035,20 +1034,28 @@ AC_ARG_ENABLE(regex,
AC_MSG_RESULT($use_regex)
if test "$use_regex" = yes ; then
AC_MSG_CHECKING([whether the included regex lib is requested])
AC_ARG_WITH(included-regex,
[ --with-included-regex use the included GNU regex library],
[gnupg_cv_included_regex="$withval"],[gnupg_cv_included_regex=no])
AC_MSG_RESULT($gnupg_cv_included_regex)
_cppflags="${CPPFLAGS}"
_ldflags="${LDFLAGS}"
AC_ARG_WITH(regex,
AC_HELP_STRING([--with-regex=DIR],[look for regex in DIR]),
[
if test -d "$withval" ; then
CPPFLAGS="${CPPFLAGS} -I$withval/include"
LDFLAGS="${LDFLAGS} -L$withval/lib"
fi
],withval="")
if test $gnupg_cv_included_regex = no ; then
# Does the system have regex functions at all?
AC_CHECK_FUNC(regcomp,gnupg_cv_included_regex=no,
gnupg_cv_included_regex=yes)
fi
# Does the system have regex functions at all?
AC_SEARCH_LIBS([regcomp], [regex])
AC_CHECK_FUNC(regcomp, gnupg_cv_have_regex=yes, gnupg_cv_have_regex=no)
if test $gnupg_cv_included_regex = no ; then
AC_CACHE_CHECK([whether your system's regexp library is broken],
if test $gnupg_cv_have_regex = no; then
use_regex=no
else
if test x"$cross_compiling" = xyes; then
AC_MSG_WARN([cross compiling; assuming regexp libray is not broken])
else
AC_CACHE_CHECK([whether your system's regexp library is broken],
[gnupg_cv_regex_broken],
AC_TRY_RUN([
#include <unistd.h>
@ -1056,20 +1063,20 @@ if test "$use_regex" = yes ; then
main() { regex_t blah ; regmatch_t p; p.rm_eo = p.rm_eo; return regcomp(&blah, "foo.*bar", REG_NOSUB) || regexec (&blah, "foobar", 0, NULL, 0); }],
gnupg_cv_regex_broken=no, gnupg_cv_regex_broken=yes, gnupg_cv_regex_broken=yes))
if test $gnupg_cv_regex_broken = yes ; then
AC_MSG_WARN(your regex is broken - using the included GNU regex instead.)
gnupg_cv_included_regex=yes
fi
if test $gnupg_cv_regex_broken = yes; then
AC_MSG_WARN([your regex is broken - disabling regex use])
use_regex=no
fi
fi
fi
if test $gnupg_cv_included_regex = yes; then
AC_DEFINE(USE_INTERNAL_REGEX,1,[ Define if you want to use the included regex lib ])
fi
else
AC_DEFINE(DISABLE_REGEX,1,[ Define to disable regular expression support ])
CPPFLAGS="${_cppflags}"
LDFLAGS="${_ldflags}"
fi
AM_CONDITIONAL(USE_INTERNAL_REGEX, test x"$gnupg_cv_included_regex" = xyes)
if test "$use_regex" != yes ; then
AC_DEFINE(DISABLE_REGEX,1, [Define to disable regular expression support])
fi
AM_CONDITIONAL(DISABLE_REGEX, test x"$use_regex" != xyes)
@ -1397,6 +1404,12 @@ echo "
Default scdaemon: $show_gnupg_scdaemon_pgm
Default dirmngr: $show_gnupg_dirmngr_pgm
PKITS based tests: $run_pkits_tests
PKITS based tests: $run_pkits_tests"
if test x"$use_regex" != xyes ; then
echo "
Warning: No regular expression support available.
OpenPGP trust signatures won't work.
gpg-check-pattern will not be build.
"
fi

View File

@ -1,3 +1,7 @@
2007-08-27 Werner Koch <wk@g10code.com>
* examples/pwpattern.list: New.
2007-08-24 Werner Koch <wk@g10code.com>
* debugging.texi (Common Problems): Add "A root certifciate does

View File

@ -18,7 +18,7 @@
## Process this file with automake to produce Makefile.in
examples = examples/README examples/scd-event examples/trustlist.txt \
examples/gpgconf.conf
examples/gpgconf.conf examples/pwpattern.list
EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \
gnupg-logo.eps gnupg-logo.pdf gnupg-logo.png \

View File

@ -0,0 +1,48 @@
# pwpattern.list -*- default-generic -*-
#
# This is an example for a pattern file as used by gpg-check-pattern.
# The file is line based with comment lines beginning on the *first*
# position with a '#'. Empty lines and lines with just spaces are
# ignored. The other lines may be verbatim patterns and match as they
# are (trailing spaces are ignored) or extended regular expressions
# indicated by a / in the first column and terminated by another / or
# end of line. All comparisons are case insensitive.
# Reject the usual metavariables. Usual not required because
# gpg-agent can be used to reject all passphrases shorter than 8
# charactes.
foo
bar
baz
# As well as very common passwords. Note that gpg-agent can be used
# to reject them due to missing non-alpha characters.
password
passwort
passphrase
mantra
test
abc
egal
# German number plates.
/^[A-Z]{1,3}[ ]*-[ ]*[A-Z]{1,2}[ ]*[0-9]+/
# Dates (very limited, only ISO dates). */
/^[012][0-9][0-9][0-9]-[012][0-9]-[0123][0-9]$/
# Arbitrary strings
the quick brown fox jumps over the lazy dogs back
no-password
no password
12345678
123456789
1234567890
87654321
987654321
0987654321
qwertyuiop
qwertzuiop
asdfghjkl
zxcvbnm

View File

@ -334,11 +334,38 @@ Set the maximum time a cache entry used for SSH keys is valid to @var{n}
seconds. After this time a cache entry will get expired even if it has
been accessed recently. The default are 2 hours (7200 seconds).
@item --enforce-passphrase-constraints
@opindex enforce-passphrase-constraints
Enforce the passphrase constraints by not allowing the user to bypass
them using the ``Take it anyway'' button.
@item --min-passphrase-len @var{n}
@opindex min-passphrase-len
Set the minimal length of a passphrase. When entering a new passphrase
shorter than this value a warning will be displayed. Defaults to 8.
@item --min-passphrase-nonalpha @var{n}
@opindex min-passphrase-nonalpha
Set the minimal number of digits or special characters required in a
passphrase. When entering a new passphrase with less than this number
of digits or special characters a warning will be displayed. Defaults
to 1.
@item --check-passphrase-pattern @var{file}
@opindex check-passphrase-pattern
Check the passphrase against the pattern given in @var{file}. When
entering a new passphrase matching one of these pattern a warning will
be displayed. @var{file} should be an absolute filename. The default is
not to use any pattern file.
Security note: It is known that checking a passphrase against a list of
pattern or even against a complete dictionary is not very effective to
enforce good passphrases. Users will soon figure up ways to bypass such
a policy. A better policy is to educate users on good security
behavior and optional to run a passphrase cracker regularly on all
users passphrases t catch the very simple ones.
@item --pinentry-program @var{filename}
@opindex pinentry-program
Use program @var{filename} as the PIN entry. The default is installation

View File

@ -1,3 +1,7 @@
2007-08-27 Werner Koch <wk@g10code.com>
* trustdb.c (USE_INTERNAL_REGEX): Remove support.
2007-08-24 Werner Koch <wk@g10code.com>
* keyring.c (keyring_register_filename): Use same_file_p().

View File

@ -26,11 +26,7 @@
#ifndef DISABLE_REGEX
#include <sys/types.h>
#ifdef USE_INTERNAL_REGEX
#include "_regex.h"
#else
#include <regex.h>
#endif
#endif /* !DISABLE_REGEX */
#include "gpg.h"

View File

@ -339,7 +339,7 @@ make_filename( const char *first_part, ... )
/* Compare whether the filenames are identical. This is a
specialversion of strcmp() taking the semantics of filenames in
special version of strcmp() taking the semantics of filenames in
account. Note that this function works only on the supplied names
without considereing any context like the current directory. See
also same_file_p(). */

View File

@ -1,3 +1,7 @@
2007-08-24 Werner Koch <wk@g10code.com>
* Makefile.am (common_libs): Swap libkeybox and jnlib.
2007-08-23 Werner Koch <wk@g10code.com>
* certlist.c (gpgsm_certs_identical_p): New.

View File

@ -52,7 +52,7 @@ gpgsm_SOURCES = \
qualified.c
common_libs = $(libcommon) ../jnlib/libjnlib.a ../kbx/libkeybox.a \
common_libs = $(libcommon) ../kbx/libkeybox.a ../jnlib/libjnlib.a \
../gl/libgnu.a
gpgsm_LDADD = $(common_libs) ../common/libgpgrl.a \

View File

@ -1,3 +1,15 @@
2007-08-27 Werner Koch <wk@g10code.com>
* gpg-check-pattern.c: New
* Makefile.am (libexec_PROGRAMS): Add unless DISABLE_REGEX.
2007-08-24 Werner Koch <wk@g10code.com>
* gpgconf-comp.c <gpg-agent>: Add options --check-passphrase-pattern,
--min-passphrase-nonalpha and --enforce-passphrase-constraints and
move them into a new "passphrase policy" group.
(gc_component) [W32]: Enable dirmngr.
2007-08-21 Werner Koch <wk@g10code.com>
* gpgkey2ssh.c (key_to_blob): Use gnupg_tmpfile().

View File

@ -46,6 +46,10 @@ if !HAVE_W32_SYSTEM
bin_PROGRAMS += watchgnupg gpgparsemail
endif
if !DISABLE_REGEX
libexec_PROGRAMS = gpg-check-pattern
endif
noinst_PROGRAMS = clean-sat mk-tdata make-dns-cert gpgsplit
common_libs = $(libcommon) ../jnlib/libjnlib.a ../gl/libgnu.a
@ -86,6 +90,13 @@ gpgkey2ssh_LDADD = $(common_libs) \
$(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
if !DISABLE_REGEX
gpg_check_pattern_SOURCES = gpg-check-pattern.c
gpg_check_pattern_CFLAGS = $(LIBGCRYPT_CFLAGS) $(GPG_ERROR_CFLAGS)
gpg_check_pattern_LDADD = $(common_libs) $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(LIBICONV) $(W32SOCKLIBS)
endif
# Make sure that all libs are build before we use them. This is
# important for things like make -j2.
$(PROGRAMS): $(common_libs) $(pwquery_libs)

504
tools/gpg-check-pattern.c Normal file
View File

@ -0,0 +1,504 @@
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
* Copyright (C) 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
# include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <regex.h>
#include <ctype.h>
#define JNLIB_NEED_LOG_LOGV
#include "util.h"
#include "i18n.h"
#include "sysutils.h"
enum cmd_and_opt_values
{ aNull = 0,
oVerbose = 'v',
oArmor = 'a',
oPassphrase = 'P',
oProtect = 'p',
oUnprotect = 'u',
oNull = '0',
oNoVerbose = 500,
oCheck,
oHomedir
};
/* The list of commands and options. */
static ARGPARSE_OPTS opts[] = {
{ 301, NULL, 0, N_("@Options:\n ") },
{ oVerbose, "verbose", 0, "verbose" },
{ oHomedir, "homedir", 2, "@" },
{ oCheck, "check", 0, "run only a syntax check on the patternfile" },
{ oNull, "null", 0, "input is expected to be null delimited" },
{0}
};
/* Global options are accessed through the usual OPT structure. */
static struct
{
int verbose;
const char *homedir;
int checkonly;
int null;
} opt;
enum {
PAT_NULL, /* Indicates end of the array. */
PAT_STRING, /* The pattern is a simple string. */
PAT_REGEX /* The pattern is an extended regualr expression. */
};
/* An object to decibe an item of our pattern table. */
struct pattern_s
{
int type;
unsigned int lineno; /* Line number of the pattern file. */
union {
struct {
const char *string; /* Pointer to the actual string (nul termnated). */
size_t length; /* The length of this string (strlen). */
} s; /*PAT_STRING*/
struct {
/* We allocate the regex_t because this type is larger than what
we need for PAT_STRING and we expect only a few regex in a
patternfile. It would be a waste of core to have so many
unused stuff in the table. */
regex_t *regex;
} r; /*PAT_REGEX*/
} u;
};
typedef struct pattern_s pattern_t;
/*** Local prototypes ***/
static char *read_file (const char *fname, size_t *r_length);
static pattern_t *parse_pattern_file (char *data, size_t datalen);
static void process (FILE *fp, pattern_t *patarray);
/* Info function for usage(). */
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 11: p = "gpg-check-pattern (GnuPG)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40:
p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
break;
case 41:
p = _("Syntax: gpg-check-pattern [options] patternfile\n"
"Check a passphrase given on stdin against the patternfile\n");
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv )
{
ARGPARSE_ARGS pargs;
char *raw_pattern;
size_t raw_pattern_length;
pattern_t *patternarray;
set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-check-pattern", 1);
/* Make sure that our subsystems are ready. */
init_common_subsystems ();
i18n_init ();
/* We need Libgcrypt for hashing. */
if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
{
log_fatal ( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
}
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
opt.homedir = default_homedir ();
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= 1; /* (do not remove the args) */
while (arg_parse (&pargs, opts) )
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oHomedir: opt.homedir = pargs.r.ret_str; break;
case oCheck: opt.checkonly = 1; break;
case oNull: opt.null = 1; break;
default : pargs.err = 2; break;
}
}
if (log_get_errorcount(0))
exit (2);
if (argc != 1)
usage (1);
/* We read the entire pattern file into our memory and parse it
using a separate function. This allows us to eventual do the
reading while running setuid so that the pattern file can be
hidden from regular users. I am not sure whether this makes
sense, but lets be prepared for it. */
raw_pattern = read_file (*argv, &raw_pattern_length);
if (!raw_pattern)
exit (2);
patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
if (!patternarray)
exit (1);
if (opt.checkonly)
return 0;
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (stdin) , O_BINARY );
#endif
process (stdin, patternarray);
return log_get_errorcount(0)? 1 : 0;
}
/* Read a file FNAME into a buffer and return that malloced buffer.
Caller must free the buffer. On error NULL is returned, on success
the valid length of the buffer is stored at R_LENGTH. The returned
buffer is guarnteed to be nul terminated. */
static char *
read_file (const char *fname, size_t *r_length)
{
FILE *fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = stdin;
#ifdef HAVE_DOSISH_SYSTEM
setmode ( fileno(fp) , O_BINARY );
#endif
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize+1);
else
buf = xrealloc (buf, bufsize+1);
nread = fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && ferror (fp))
{
log_error ("error reading `[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = fopen (fname, "rb");
if (!fp)
{
log_error ("can't open `%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (fileno(fp), &st))
{
log_error ("can't stat `%s': %s\n", fname, strerror (errno));
fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading `%s': %s\n", fname, strerror (errno));
fclose (fp);
xfree (buf);
return NULL;
}
fclose (fp);
}
buf[buflen] = 0;
*r_length = buflen;
return buf;
}
static char *
get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
regerror (errcode, compiled, buffer, length);
return buffer;
}
/* Parse the pattern given in the memory aread DATA/DATALEN and return
a new pattern array. The end of the array is indicated by a NULL
entry. On error an error message is printed and the fucntion
returns NULL. Note that the function modifies DATA and assumes
that data is nul terminated (even if this is one byte past
DATALEN). */
static pattern_t *
parse_pattern_file (char *data, size_t datalen)
{
char *p, *p2;
size_t n;
pattern_t *array;
size_t arraysize, arrayidx;
unsigned int lineno = 0;
/* Estimate the number of entries by counting the non-comment lines. */
arraysize = 0;
p = data;
for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
if (*p != '#')
arraysize++;
arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
array = xcalloc (arraysize, sizeof *array);
arrayidx = 0;
/* Loop over all lines. */
while (datalen && data)
{
lineno++;
p = data;
p2 = data = memchr (p, '\n', datalen);
if (p2)
{
*data++ = 0;
datalen -= data - p;
}
else
p2 = p + datalen;
assert (!*p2);
p2--;
while (isascii (*p) && isspace (*p))
p++;
if (*p == '#')
continue;
while (p2 > p && isascii (*p2) && isspace (*p2))
*p2-- = 0;
if (!*p)
continue;
assert (arrayidx < arraysize);
array[arrayidx].lineno = lineno;
if (*p == '/')
{
int rerr;
p++;
array[arrayidx].type = PAT_REGEX;
if (*p && p[strlen(p)-1] == '/')
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
rerr = regcomp (array[arrayidx].u.r.regex, p,
REG_ICASE|REG_NOSUB|REG_EXTENDED);
if (rerr)
{
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
xfree (rerrbuf);
if (!opt.checkonly)
exit (1);
}
}
else
{
array[arrayidx].type = PAT_STRING;
array[arrayidx].u.s.string = p;
array[arrayidx].u.s.length = strlen (p);
}
arrayidx++;
}
assert (arrayidx < arraysize);
array[arrayidx].type = PAT_NULL;
return array;
}
/* Check whether string macthes any of the pattern in PATARRAY and
returns the matching pattern item or NULL. */
static pattern_t *
match_p (const char *string, pattern_t *patarray)
{
pattern_t *pat;
if (!*string)
{
if (opt.verbose)
log_info ("zero length input line - ignored\n");
return NULL;
}
for (pat = patarray; pat->type != PAT_NULL; pat++)
{
if (pat->type == PAT_STRING)
{
if (!strcasecmp (pat->u.s.string, string))
return pat;
}
else if (pat->type == PAT_REGEX)
{
int rerr;
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
if (!rerr)
return pat;
else if (rerr != REG_NOMATCH)
{
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
log_error ("matching r.e. failed: %s\n", rerrbuf);
xfree (rerrbuf);
return pat; /* Better indicate a match on error. */
}
}
else
BUG ();
}
return NULL;
}
/* Actual processing of the input. This fucntion does not return an
error code but exits as soon as a match has been found. */
static void
process (FILE *fp, pattern_t *patarray)
{
char buffer[2048];
size_t idx;
int c;
unsigned long lineno = 0;
pattern_t *pat;
idx = 0;
c = 0;
while (idx < sizeof buffer -1 && c != EOF )
{
if ((c = getc (fp)) != EOF)
buffer[idx] = c;
if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
{
lineno++;
if (!opt.null)
{
while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
idx--;
}
buffer[idx] = 0;
pat = match_p (buffer, patarray);
if (pat)
{
if (opt.verbose)
log_error ("input line %lu matches pattern at line %u"
" - rejected\n",
lineno, pat->lineno);
exit (1);
}
idx = 0;
}
else
idx++;
}
if (c != EOF)
{
log_error ("input line %lu too long - rejected\n", lineno+1);
exit (1);
}
if (ferror (fp))
{
log_error ("input read error at line %lu: %s - rejected\n",
lineno+1, strerror (errno));
exit (1);
}
if (opt.verbose)
log_info ("no input line matches the pattern - accepted\n");
}

View File

@ -505,14 +505,30 @@ static gc_option_t gc_options_gpg_agent[] =
{ "allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg", "allow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not grab keyboard and mouse",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "Passphrase policy",
GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
"gnupg", N_("Options enforcing a passphrase policy") },
{ "enforce-passphrases-constraints", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("do not allow to bypass the passphrase policy"),
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_ADVANCED, "gnupg",
N_("|N|set minimal required length for new passphrases to N"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT, "gnupg",
N_("|N|require at least N non-alpha characters for a new passphrase"),
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
GC_LEVEL_EXPERT,
"gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON },
GC_OPTION_NULL
};
@ -915,9 +931,7 @@ static struct
{ "gpg-agent", NULL, "GPG Agent", gc_options_gpg_agent },
{ "scdaemon", NULL, "Smartcard Daemon", gc_options_scdaemon },
{ "gpgsm", NULL, "GPG for S/MIME", gc_options_gpgsm },
#ifndef HAVE_W32_SYSTEM
{ "dirmngr", NULL, "Directory Manager", gc_options_dirmngr }
#endif
};