Implemented more gpg-agen options to support certain passphrase policies.
authorWerner Koch <wk@gnupg.org>
Mon, 27 Aug 2007 18:10:27 +0000 (18:10 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 27 Aug 2007 18:10:27 +0000 (18:10 +0000)
New tool gpg-check-pattern.

28 files changed:
ChangeLog
NEWS
agent/ChangeLog
agent/agent.h
agent/call-pinentry.c
agent/genkey.c
agent/gpg-agent.c
agent/trustlist.c
autogen.sh
common/ChangeLog
common/exechelp.c
common/exechelp.h
common/homedir.c
common/util.h
configure.ac
doc/ChangeLog
doc/Makefile.am
doc/examples/pwpattern.list [new file with mode: 0644]
doc/gpg-agent.texi
g10/ChangeLog
g10/trustdb.c
jnlib/stringhelp.c
sm/ChangeLog
sm/Makefile.am
tools/ChangeLog
tools/Makefile.am
tools/gpg-check-pattern.c [new file with mode: 0644]
tools/gpgconf-comp.c

index f20bd26..e6569f9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
diff --git a/NEWS b/NEWS
index d360334..34e1174 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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)
index 4c7df8b..4b8d5d3 100644 (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.
index 22225f6..8531f39 100644 (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);
index 4631ead..f29d3c4 100644 (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,
 }
 
 
+\f
+/* 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)
index e160f45..09cd9f7 100644 (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;
     }
index cb0a67b..453e9a9 100644 (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",
index deb0d95..a154da8 100644 (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;
 }
 
index ad1464c..5aa6b89 100755 (executable)
@@ -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
index dfa366f..dd38733 100644 (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.
index 2de4f75..2a65970 100644 (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:
index 9e482b7..ff7e59d 100644 (file)
    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);
 
 
index 5220619..85506b8 100644 (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 ();
     }
index ad398ac..9379183 100644 (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);
 
 
index 562929c..578b7c1 100644 (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)
-
-  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
+  _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
-     AC_CACHE_CHECK([whether your system's regexp library is broken],
+  # 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_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
-  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 ])
+      if test $gnupg_cv_regex_broken = yes; then
+        AC_MSG_WARN([your regex is broken - disabling regex use])
+        use_regex=no
+      fi
+    fi
   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
 
index d4ade07..10c9f1b 100644 (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
index 3d84bb8..1c5dd40 100644 (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 \
diff --git a/doc/examples/pwpattern.list b/doc/examples/pwpattern.list
new file mode 100644 (file)
index 0000000..251c2d4
--- /dev/null
@@ -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
index 156fe53..9751eee 100644 (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
index 90501d0..4dc9110 100644 (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().
index 4630967..c5caf28 100644 (file)
 
 #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"
index e7fd0ce..2f52043 100644 (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(). */
index 6805969..f0d80e2 100644 (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.
index e551bbd..2bbdd1b 100644 (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 \
index 06398c1..552ef92 100644 (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().
index 1d9a865..a9004a7 100644 (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)
diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
new file mode 100644 (file)
index 0000000..a2926b9
--- /dev/null
@@ -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);
+
+
+
+\f
+/* 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");
+}
+
index 096aa4d..466da32 100644 (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
   };
 
 \f