mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
tools: Extend gpg-check-pattern.
* tools/gpg-check-pattern.c: Major rewrite. -- Signed-off-by: Werner Koch <wk@gnupg.org> Here is a simple pattern file: ==================== # Pattern to reject passwords which do not comply to # - at least 1 uppercase letter # - at least 1 lowercase letter # - at least one number # - at least one special character # and a few extra things to show the reject mode # Reject is the default mode, ignore case is the default #[reject] #[icase] # If the password starts with "foo" (case insensitive) it is rejected. /foo.*/ [case] # If the password starts with "bar" (case sensitive) it is rejected. /bar.*/ # Switch to accept mode: Only if all patterns up to the next "accept" # or "reject" tag or EOF match, the password is accepted. Otherwise # the password is rejected. [accept] /[A-Z]+/ /[a-z]+/ /[0-9]+/ /[^A-Za-z0-9]+/ ================= Someone™ please write regression tests.
This commit is contained in:
parent
5c8124b8b9
commit
73c03e0232
@ -2081,6 +2081,51 @@ gpgtar --list-archive test1
|
|||||||
@command{gpg-check-pattern} checks a passphrase given on stdin against
|
@command{gpg-check-pattern} checks a passphrase given on stdin against
|
||||||
a specified pattern file.
|
a specified pattern file.
|
||||||
|
|
||||||
|
The pattern file is line based with comment lines beginning on the
|
||||||
|
@emph{first} position with a @code{#}. Empty lines and lines with
|
||||||
|
only white spaces are ignored. The actual pattern lines may either be
|
||||||
|
verbatim string pattern and match as they are (trailing spaces are
|
||||||
|
ignored) or extended regular expressions indicated by a @code{/} or
|
||||||
|
@code{!/} in the first column and terminated by another @code{/} or
|
||||||
|
end of line. If a regular expression starts with @code{!/} the match
|
||||||
|
result is reversed. By default all comparisons are case insensitive.
|
||||||
|
|
||||||
|
Tag lines may be used to further control the operation of this tool.
|
||||||
|
The currently defined tags are:
|
||||||
|
|
||||||
|
@table @code
|
||||||
|
@item [icase]
|
||||||
|
Switch to case insensitive comparison for all further patterns. This
|
||||||
|
is the default.
|
||||||
|
|
||||||
|
@item [case]
|
||||||
|
Switch to case sensitive comparison for all further patterns.
|
||||||
|
|
||||||
|
@item [reject]
|
||||||
|
Switch to reject mode. This is the default mode.
|
||||||
|
|
||||||
|
@item [accept]
|
||||||
|
Switch to accept mode.
|
||||||
|
@end table
|
||||||
|
|
||||||
|
In the future more tags may be introduced and thus it is advisable not to
|
||||||
|
start a plain pattern string with an open bracket. The tags must be
|
||||||
|
given verbatim on the line with no spaces to the left or any non white
|
||||||
|
space characters to the right.
|
||||||
|
|
||||||
|
In reject mode the program exits on the first match with an exit code
|
||||||
|
of 1 (failure). If at the end of the pattern list the reject mode is
|
||||||
|
still active the program exits with code 0 (success).
|
||||||
|
|
||||||
|
In accept mode blocks of patterns are used. A block starts at the
|
||||||
|
next pattern after an "accept" tag and ends with the last pattern
|
||||||
|
before the next "accept" or "reject" tag or at the end of the pattern
|
||||||
|
list. If all patterns in a block match the program exits with an exit
|
||||||
|
code of 0 (success). If any pattern in a block do not match the next
|
||||||
|
pattern block is evaluated. If at the end of the pattern list the
|
||||||
|
accept mode is still active the program exits with code 1 (failure).
|
||||||
|
|
||||||
|
|
||||||
@mansect options
|
@mansect options
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
@ -2102,6 +2147,6 @@ Input is expected to be null delimited.
|
|||||||
|
|
||||||
@mansect see also
|
@mansect see also
|
||||||
@ifset isman
|
@ifset isman
|
||||||
@command{gpg}(1),
|
@command{gpg-agent}(1),
|
||||||
@end ifset
|
@end ifset
|
||||||
@include see-also-note.texi
|
@include see-also-note.texi
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
|
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
|
||||||
|
* Copyright (C) 2021 g10 Code GmbH
|
||||||
* Copyright (C) 2007 Free Software Foundation, Inc.
|
* Copyright (C) 2007 Free Software Foundation, Inc.
|
||||||
*
|
*
|
||||||
* This file is part of GnuPG.
|
* This file is part of GnuPG.
|
||||||
@ -26,7 +27,6 @@
|
|||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
|
||||||
#ifdef HAVE_LOCALE_H
|
#ifdef HAVE_LOCALE_H
|
||||||
# include <locale.h>
|
# include <locale.h>
|
||||||
#endif
|
#endif
|
||||||
@ -50,11 +50,7 @@
|
|||||||
enum cmd_and_opt_values
|
enum cmd_and_opt_values
|
||||||
{ aNull = 0,
|
{ aNull = 0,
|
||||||
oVerbose = 'v',
|
oVerbose = 'v',
|
||||||
oArmor = 'a',
|
|
||||||
oPassphrase = 'P',
|
|
||||||
|
|
||||||
oProtect = 'p',
|
|
||||||
oUnprotect = 'u',
|
|
||||||
oNull = '0',
|
oNull = '0',
|
||||||
|
|
||||||
oNoVerbose = 500,
|
oNoVerbose = 500,
|
||||||
@ -101,6 +97,10 @@ struct pattern_s
|
|||||||
{
|
{
|
||||||
int type;
|
int type;
|
||||||
unsigned int lineno; /* Line number of the pattern file. */
|
unsigned int lineno; /* Line number of the pattern file. */
|
||||||
|
unsigned int newblock; /* First pattern in a new block. */
|
||||||
|
unsigned int icase:1; /* Case insensitive match. */
|
||||||
|
unsigned int accept:1; /* In accept mode. */
|
||||||
|
unsigned int reverse:1; /* Reverse the outcome of a regexp match. */
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
const char *string; /* Pointer to the actual string (nul termnated). */
|
const char *string; /* Pointer to the actual string (nul termnated). */
|
||||||
@ -200,7 +200,7 @@ main (int argc, char **argv )
|
|||||||
gpgrt_usage (1);
|
gpgrt_usage (1);
|
||||||
|
|
||||||
/* We read the entire pattern file into our memory and parse it
|
/* We read the entire pattern file into our memory and parse it
|
||||||
using a separate function. This allows us to eventual do the
|
using a separate function. This allows us to eventually do the
|
||||||
reading while running setuid so that the pattern file can be
|
reading while running setuid so that the pattern file can be
|
||||||
hidden from regular users. I am not sure whether this makes
|
hidden from regular users. I am not sure whether this makes
|
||||||
sense, but lets be prepared for it. */
|
sense, but lets be prepared for it. */
|
||||||
@ -219,7 +219,7 @@ main (int argc, char **argv )
|
|||||||
#endif
|
#endif
|
||||||
process (stdin, patternarray);
|
process (stdin, patternarray);
|
||||||
|
|
||||||
return log_get_errorcount(0)? 1 : 0;
|
return 4; /*NOTREACHED*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -310,6 +310,7 @@ get_regerror (int errcode, regex_t *compiled)
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Parse the pattern given in the memory aread DATA/DATALEN and return
|
/* 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
|
a new pattern array. The end of the array is indicated by a NULL
|
||||||
entry. On error an error message is printed and the function
|
entry. On error an error message is printed and the function
|
||||||
@ -324,6 +325,9 @@ parse_pattern_file (char *data, size_t datalen)
|
|||||||
pattern_t *array;
|
pattern_t *array;
|
||||||
size_t arraysize, arrayidx;
|
size_t arraysize, arrayidx;
|
||||||
unsigned int lineno = 0;
|
unsigned int lineno = 0;
|
||||||
|
unsigned int icase_mode = 1;
|
||||||
|
unsigned int accept_mode = 0;
|
||||||
|
unsigned int newblock = 1; /* The first implict block. */
|
||||||
|
|
||||||
/* Estimate the number of entries by counting the non-comment lines. */
|
/* Estimate the number of entries by counting the non-comment lines. */
|
||||||
arraysize = 0;
|
arraysize = 0;
|
||||||
@ -349,7 +353,7 @@ parse_pattern_file (char *data, size_t datalen)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
p2 = p + datalen;
|
p2 = p + datalen;
|
||||||
assert (!*p2);
|
log_assert (!*p2);
|
||||||
p2--;
|
p2--;
|
||||||
while (isascii (*p) && isspace (*p))
|
while (isascii (*p) && isspace (*p))
|
||||||
p++;
|
p++;
|
||||||
@ -359,23 +363,57 @@ parse_pattern_file (char *data, size_t datalen)
|
|||||||
*p2-- = 0;
|
*p2-- = 0;
|
||||||
if (!*p)
|
if (!*p)
|
||||||
continue;
|
continue;
|
||||||
assert (arrayidx < arraysize);
|
if (!strcmp (p, "[case]"))
|
||||||
|
{
|
||||||
|
icase_mode = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp (p, "[icase]"))
|
||||||
|
{
|
||||||
|
icase_mode = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp (p, "[accept]"))
|
||||||
|
{
|
||||||
|
accept_mode = 1;
|
||||||
|
newblock = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!strcmp (p, "[reject]"))
|
||||||
|
{
|
||||||
|
accept_mode = 0;
|
||||||
|
newblock = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_assert (arrayidx < arraysize);
|
||||||
array[arrayidx].lineno = lineno;
|
array[arrayidx].lineno = lineno;
|
||||||
if (*p == '/')
|
array[arrayidx].icase = icase_mode;
|
||||||
|
array[arrayidx].accept = accept_mode;
|
||||||
|
array[arrayidx].reverse = 0;
|
||||||
|
array[arrayidx].newblock = newblock;
|
||||||
|
newblock = 0;
|
||||||
|
|
||||||
|
if (*p == '/' || (*p == '!' && p[1] == '/'))
|
||||||
{
|
{
|
||||||
int rerr;
|
int rerr;
|
||||||
|
int reverse;
|
||||||
|
|
||||||
|
reverse = (*p == '!');
|
||||||
p++;
|
p++;
|
||||||
|
if (reverse)
|
||||||
|
p++;
|
||||||
array[arrayidx].type = PAT_REGEX;
|
array[arrayidx].type = PAT_REGEX;
|
||||||
if (*p && p[strlen(p)-1] == '/')
|
if (*p && p[strlen(p)-1] == '/')
|
||||||
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
|
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
|
||||||
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
|
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
|
||||||
|
array[arrayidx].reverse = reverse;
|
||||||
rerr = regcomp (array[arrayidx].u.r.regex, p,
|
rerr = regcomp (array[arrayidx].u.r.regex, p,
|
||||||
REG_ICASE|REG_EXTENDED);
|
(array[arrayidx].icase? REG_ICASE:0)|REG_EXTENDED);
|
||||||
if (rerr)
|
if (rerr)
|
||||||
{
|
{
|
||||||
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
|
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
|
||||||
log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
|
log_error ("invalid regexp at line %u: %s\n", lineno, rerrbuf);
|
||||||
xfree (rerrbuf);
|
xfree (rerrbuf);
|
||||||
if (!opt.checkonly)
|
if (!opt.checkonly)
|
||||||
exit (1);
|
exit (1);
|
||||||
@ -383,25 +421,44 @@ parse_pattern_file (char *data, size_t datalen)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (*p == '[')
|
||||||
|
{
|
||||||
|
static int shown;
|
||||||
|
|
||||||
|
if (!shown)
|
||||||
|
{
|
||||||
|
log_info ("future warning: do no start a string with '['"
|
||||||
|
" but use a regexp (line %u)\n", lineno);
|
||||||
|
shown = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
array[arrayidx].type = PAT_STRING;
|
array[arrayidx].type = PAT_STRING;
|
||||||
array[arrayidx].u.s.string = p;
|
array[arrayidx].u.s.string = p;
|
||||||
array[arrayidx].u.s.length = strlen (p);
|
array[arrayidx].u.s.length = strlen (p);
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayidx++;
|
arrayidx++;
|
||||||
}
|
}
|
||||||
assert (arrayidx < arraysize);
|
log_assert (arrayidx < arraysize);
|
||||||
array[arrayidx].type = PAT_NULL;
|
array[arrayidx].type = PAT_NULL;
|
||||||
|
|
||||||
|
if (lineno && newblock)
|
||||||
|
log_info ("warning: pattern list ends with a singleton"
|
||||||
|
" accept or reject tag\n");
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Check whether string macthes any of the pattern in PATARRAY and
|
/* Check whether string matches any of the pattern in PATARRAY and
|
||||||
returns the matching pattern item or NULL. */
|
returns the matching pattern item or NULL. */
|
||||||
static pattern_t *
|
static pattern_t *
|
||||||
match_p (const char *string, pattern_t *patarray)
|
match_p (const char *string, pattern_t *patarray)
|
||||||
{
|
{
|
||||||
pattern_t *pat;
|
pattern_t *pat;
|
||||||
|
int match;
|
||||||
|
int accept_match; /* Tracks matchinf state in an accept block. */
|
||||||
|
int accept_skip; /* Skip remaining patterns in an accept block. */
|
||||||
|
|
||||||
if (!*string)
|
if (!*string)
|
||||||
{
|
{
|
||||||
@ -410,30 +467,84 @@ match_p (const char *string, pattern_t *patarray)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accept_match = 0;
|
||||||
|
accept_skip = 0;
|
||||||
for (pat = patarray; pat->type != PAT_NULL; pat++)
|
for (pat = patarray; pat->type != PAT_NULL; pat++)
|
||||||
{
|
{
|
||||||
|
match = 0;
|
||||||
|
if (pat->newblock)
|
||||||
|
accept_match = accept_skip = 0;
|
||||||
|
|
||||||
if (pat->type == PAT_STRING)
|
if (pat->type == PAT_STRING)
|
||||||
{
|
{
|
||||||
if (!strcasecmp (pat->u.s.string, string))
|
if (pat->icase)
|
||||||
return pat;
|
{
|
||||||
|
if (!strcasecmp (pat->u.s.string, string))
|
||||||
|
match = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!strcmp (pat->u.s.string, string))
|
||||||
|
match = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (pat->type == PAT_REGEX)
|
else if (pat->type == PAT_REGEX)
|
||||||
{
|
{
|
||||||
int rerr;
|
int rerr;
|
||||||
|
|
||||||
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
|
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
|
||||||
|
if (pat->reverse)
|
||||||
|
{
|
||||||
|
if (!rerr)
|
||||||
|
rerr = REG_NOMATCH;
|
||||||
|
else if (rerr == REG_NOMATCH)
|
||||||
|
rerr = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!rerr)
|
if (!rerr)
|
||||||
return pat;
|
match = 1;
|
||||||
else if (rerr != REG_NOMATCH)
|
else if (rerr != REG_NOMATCH)
|
||||||
{
|
{
|
||||||
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
|
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
|
||||||
log_error ("matching r.e. failed: %s\n", rerrbuf);
|
log_error ("matching regexp failed: %s\n", rerrbuf);
|
||||||
xfree (rerrbuf);
|
xfree (rerrbuf);
|
||||||
return pat; /* Better indicate a match on error. */
|
if (pat->accept)
|
||||||
|
match = 0; /* Better indicate no match on error. */
|
||||||
|
else
|
||||||
|
match = 1; /* Better indicate a match on error. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
BUG ();
|
BUG ();
|
||||||
|
|
||||||
|
if (pat->accept)
|
||||||
|
{
|
||||||
|
/* Accept mode: all patterns in the accept block must match.
|
||||||
|
* Thus we need to check whether the next pattern has a
|
||||||
|
* transition and act only then. */
|
||||||
|
if (match && !accept_skip)
|
||||||
|
accept_match = 1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accept_match = 0;
|
||||||
|
accept_skip = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pat[1].type == PAT_NULL || pat[1].newblock)
|
||||||
|
{
|
||||||
|
/* Transition detected. Note that this also handles the
|
||||||
|
* end of pattern loop case. */
|
||||||
|
if (accept_match)
|
||||||
|
return pat;
|
||||||
|
/* The next is not really but we do it for clarity. */
|
||||||
|
accept_match = accept_skip = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else /* Reject mode: Return true on the first match. */
|
||||||
|
{
|
||||||
|
if (match)
|
||||||
|
return pat;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -449,6 +560,7 @@ process (FILE *fp, pattern_t *patarray)
|
|||||||
int c;
|
int c;
|
||||||
unsigned long lineno = 0;
|
unsigned long lineno = 0;
|
||||||
pattern_t *pat;
|
pattern_t *pat;
|
||||||
|
int last_is_accept;
|
||||||
|
|
||||||
idx = 0;
|
idx = 0;
|
||||||
c = 0;
|
c = 0;
|
||||||
@ -468,17 +580,28 @@ process (FILE *fp, pattern_t *patarray)
|
|||||||
pat = match_p (buffer, patarray);
|
pat = match_p (buffer, patarray);
|
||||||
if (pat)
|
if (pat)
|
||||||
{
|
{
|
||||||
|
/* Note that the accept mode works correctly only with
|
||||||
|
* one input line. */
|
||||||
if (opt.verbose)
|
if (opt.verbose)
|
||||||
log_error ("input line %lu matches pattern at line %u"
|
log_info ("input line %lu matches pattern at line %u"
|
||||||
" - rejected\n",
|
" - %s\n",
|
||||||
lineno, pat->lineno);
|
lineno, pat->lineno,
|
||||||
exit (1);
|
pat->accept? "accepted":"rejected");
|
||||||
}
|
}
|
||||||
idx = 0;
|
idx = 0;
|
||||||
|
wipememory (buffer, sizeof buffer);
|
||||||
|
if (pat)
|
||||||
|
{
|
||||||
|
if (pat->accept)
|
||||||
|
exit (0);
|
||||||
|
else
|
||||||
|
exit (1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
idx++;
|
idx++;
|
||||||
}
|
}
|
||||||
|
wipememory (buffer, sizeof buffer);
|
||||||
if (c != EOF)
|
if (c != EOF)
|
||||||
{
|
{
|
||||||
log_error ("input line %lu too long - rejected\n", lineno+1);
|
log_error ("input line %lu too long - rejected\n", lineno+1);
|
||||||
@ -490,6 +613,20 @@ process (FILE *fp, pattern_t *patarray)
|
|||||||
lineno+1, strerror (errno));
|
lineno+1, strerror (errno));
|
||||||
exit (1);
|
exit (1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check last pattern to see whether we are in accept mode. */
|
||||||
|
last_is_accept = 0;
|
||||||
|
for (pat = patarray; pat->type != PAT_NULL; pat++)
|
||||||
|
last_is_accept = pat->accept;
|
||||||
|
|
||||||
if (opt.verbose)
|
if (opt.verbose)
|
||||||
log_info ("no input line matches the pattern - accepted\n");
|
log_info ("no input line matches the pattern - %s\n",
|
||||||
|
last_is_accept? "rejected":"accepted");
|
||||||
|
|
||||||
|
if (log_get_errorcount(0))
|
||||||
|
exit (2); /* Ooops - reject. */
|
||||||
|
else if (last_is_accept)
|
||||||
|
exit (1); /* Reject */
|
||||||
|
else
|
||||||
|
exit (0); /* Accept */
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user