Fix spelling and grammar.
[gnupg.git] / tools / gpg-check-pattern.c
1 /* gpg-check-pattern.c - A tool to check passphrases against pattern.
2  * Copyright (C) 2007 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stddef.h>
25 #include <stdarg.h>
26 #include <string.h>
27 #include <errno.h>
28 #include <assert.h>
29 #ifdef HAVE_LOCALE_H
30 # include <locale.h>
31 #endif
32 #ifdef HAVE_LANGINFO_CODESET
33 # include <langinfo.h>
34 #endif
35 #ifdef HAVE_DOSISH_SYSTEM
36 # include <fcntl.h> /* for setmode() */
37 #endif
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <regex.h>
41 #include <ctype.h>
42
43 #include "util.h"
44 #include "i18n.h"
45 #include "sysutils.h"
46 #include "../common/init.h"
47
48
49 enum cmd_and_opt_values
50 { aNull = 0,
51   oVerbose        = 'v',
52   oArmor          = 'a',
53   oPassphrase     = 'P',
54
55   oProtect        = 'p',
56   oUnprotect      = 'u',
57   oNull           = '0',
58
59   oNoVerbose = 500,
60   oCheck,
61
62   oHomedir
63 };
64
65
66 /* The list of commands and options.  */
67 static ARGPARSE_OPTS opts[] = {
68
69   { 301, NULL, 0, N_("@Options:\n ") },
70
71   { oVerbose, "verbose",   0, "verbose" },
72
73   { oHomedir, "homedir", 2, "@" },
74   { oCheck,   "check", 0,  "run only a syntax check on the patternfile" },
75   { oNull,    "null", 0,   "input is expected to be null delimited" },
76
77   {0}
78 };
79
80
81 /* Global options are accessed through the usual OPT structure. */
82 static struct
83 {
84   int verbose;
85   const char *homedir;
86   int checkonly;
87   int null;
88 } opt;
89
90
91 enum {
92   PAT_NULL,    /* Indicates end of the array.  */
93   PAT_STRING,  /* The pattern is a simple string.  */
94   PAT_REGEX    /* The pattern is an extended regualr expression. */
95 };
96
97
98 /* An object to decibe an item of our pattern table. */
99 struct pattern_s
100 {
101   int type;
102   unsigned int lineno;     /* Line number of the pattern file.  */
103   union {
104     struct {
105       const char *string;  /* Pointer to the actual string (nul termnated).  */
106       size_t length;       /* The length of this string (strlen).  */
107     } s; /*PAT_STRING*/
108     struct {
109       /* We allocate the regex_t because this type is larger than what
110          we need for PAT_STRING and we expect only a few regex in a
111          patternfile.  It would be a waste of core to have so many
112          unused stuff in the table.  */
113       regex_t *regex;
114     } r; /*PAT_REGEX*/
115   } u;
116 };
117 typedef struct pattern_s pattern_t;
118
119
120
121 /*** Local prototypes ***/
122 static char *read_file (const char *fname, size_t *r_length);
123 static pattern_t *parse_pattern_file (char *data, size_t datalen);
124 static void process (FILE *fp, pattern_t *patarray);
125
126
127
128 \f
129 /* Info function for usage().  */
130 static const char *
131 my_strusage (int level)
132 {
133   const char *p;
134   switch (level)
135     {
136     case 11: p = "gpg-check-pattern (@GnuPG@)";
137       break;
138     case 13: p = VERSION; break;
139     case 17: p = PRINTABLE_OS_NAME; break;
140     case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
141
142     case 1:
143     case 40:
144       p =  _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
145       break;
146     case 41:
147       p =  _("Syntax: gpg-check-pattern [options] patternfile\n"
148              "Check a passphrase given on stdin against the patternfile\n");
149     break;
150
151     default: p = NULL;
152     }
153   return p;
154 }
155
156
157 int
158 main (int argc, char **argv )
159 {
160   ARGPARSE_ARGS pargs;
161   char *raw_pattern;
162   size_t raw_pattern_length;
163   pattern_t *patternarray;
164
165   early_system_init ();
166   set_strusage (my_strusage);
167   gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
168   log_set_prefix ("gpg-check-pattern", 1);
169
170   /* Make sure that our subsystems are ready.  */
171   i18n_init ();
172   init_common_subsystems (&argc, &argv);
173
174   /* We need Libgcrypt for hashing.  */
175   if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
176     {
177       log_fatal ( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
178                   NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
179     }
180
181   setup_libgcrypt_logging ();
182   gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
183
184   pargs.argc = &argc;
185   pargs.argv = &argv;
186   pargs.flags=  1;  /* (do not remove the args) */
187   while (arg_parse (&pargs, opts) )
188     {
189       switch (pargs.r_opt)
190         {
191         case oVerbose: opt.verbose++; break;
192         case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
193         case oCheck: opt.checkonly = 1; break;
194         case oNull: opt.null = 1; break;
195
196         default : pargs.err = 2; break;
197         }
198     }
199   if (log_get_errorcount(0))
200     exit (2);
201
202   if (argc != 1)
203     usage (1);
204
205   /* We read the entire pattern file into our memory and parse it
206      using a separate function.  This allows us to eventual do the
207      reading while running setuid so that the pattern file can be
208      hidden from regular users.  I am not sure whether this makes
209      sense, but lets be prepared for it.  */
210   raw_pattern = read_file (*argv, &raw_pattern_length);
211   if (!raw_pattern)
212     exit (2);
213
214   patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
215   if (!patternarray)
216     exit (1);
217   if (opt.checkonly)
218     return 0;
219
220 #ifdef HAVE_DOSISH_SYSTEM
221   setmode (fileno (stdin) , O_BINARY );
222 #endif
223   process (stdin, patternarray);
224
225   return log_get_errorcount(0)? 1 : 0;
226 }
227
228
229
230 /* Read a file FNAME into a buffer and return that malloced buffer.
231    Caller must free the buffer.  On error NULL is returned, on success
232    the valid length of the buffer is stored at R_LENGTH.  The returned
233    buffer is guarnteed to be nul terminated.  */
234 static char *
235 read_file (const char *fname, size_t *r_length)
236 {
237   FILE *fp;
238   char *buf;
239   size_t buflen;
240
241   if (!strcmp (fname, "-"))
242     {
243       size_t nread, bufsize = 0;
244
245       fp = stdin;
246 #ifdef HAVE_DOSISH_SYSTEM
247       setmode ( fileno(fp) , O_BINARY );
248 #endif
249       buf = NULL;
250       buflen = 0;
251 #define NCHUNK 8192
252       do
253         {
254           bufsize += NCHUNK;
255           if (!buf)
256             buf = xmalloc (bufsize+1);
257           else
258             buf = xrealloc (buf, bufsize+1);
259
260           nread = fread (buf+buflen, 1, NCHUNK, fp);
261           if (nread < NCHUNK && ferror (fp))
262             {
263               log_error ("error reading '[stdin]': %s\n", strerror (errno));
264               xfree (buf);
265               return NULL;
266             }
267           buflen += nread;
268         }
269       while (nread == NCHUNK);
270 #undef NCHUNK
271
272     }
273   else
274     {
275       struct stat st;
276
277       fp = fopen (fname, "rb");
278       if (!fp)
279         {
280           log_error ("can't open '%s': %s\n", fname, strerror (errno));
281           return NULL;
282         }
283
284       if (fstat (fileno(fp), &st))
285         {
286           log_error ("can't stat '%s': %s\n", fname, strerror (errno));
287           fclose (fp);
288           return NULL;
289         }
290
291       buflen = st.st_size;
292       buf = xmalloc (buflen+1);
293       if (fread (buf, buflen, 1, fp) != 1)
294         {
295           log_error ("error reading '%s': %s\n", fname, strerror (errno));
296           fclose (fp);
297           xfree (buf);
298           return NULL;
299         }
300       fclose (fp);
301     }
302   buf[buflen] = 0;
303   *r_length = buflen;
304   return buf;
305 }
306
307
308
309 static char *
310 get_regerror (int errcode, regex_t *compiled)
311 {
312   size_t length = regerror (errcode, compiled, NULL, 0);
313   char *buffer = xmalloc (length);
314   regerror (errcode, compiled, buffer, length);
315   return buffer;
316 }
317
318 /* Parse the pattern given in the memory aread DATA/DATALEN and return
319    a new pattern array.  The end of the array is indicated by a NULL
320    entry.  On error an error message is printed and the function
321    returns NULL.  Note that the function modifies DATA and assumes
322    that data is nul terminated (even if this is one byte past
323    DATALEN).  */
324 static pattern_t *
325 parse_pattern_file (char *data, size_t datalen)
326 {
327   char *p, *p2;
328   size_t n;
329   pattern_t *array;
330   size_t arraysize, arrayidx;
331   unsigned int lineno = 0;
332
333   /* Estimate the number of entries by counting the non-comment lines.  */
334   arraysize = 0;
335   p = data;
336   for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
337     if (*p != '#')
338       arraysize++;
339   arraysize += 2; /* For the terminating NULL and a last line w/o a LF.  */
340
341   array = xcalloc (arraysize, sizeof *array);
342   arrayidx = 0;
343
344   /* Loop over all lines.  */
345   while (datalen && data)
346     {
347       lineno++;
348       p = data;
349       p2 = data = memchr (p, '\n', datalen);
350       if (p2)
351         {
352           *data++ = 0;
353           datalen -= data - p;
354         }
355       else
356         p2 = p + datalen;
357       assert (!*p2);
358       p2--;
359       while (isascii (*p) && isspace (*p))
360         p++;
361       if (*p == '#')
362         continue;
363       while (p2 > p && isascii (*p2) && isspace (*p2))
364         *p2-- = 0;
365       if (!*p)
366         continue;
367       assert (arrayidx < arraysize);
368       array[arrayidx].lineno = lineno;
369       if (*p == '/')
370         {
371           int rerr;
372
373           p++;
374           array[arrayidx].type = PAT_REGEX;
375           if (*p && p[strlen(p)-1] == '/')
376             p[strlen(p)-1] = 0;  /* Remove optional delimiter.  */
377           array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
378           rerr = regcomp (array[arrayidx].u.r.regex, p,
379                           REG_ICASE|REG_NOSUB|REG_EXTENDED);
380           if (rerr)
381             {
382               char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
383               log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
384               xfree (rerrbuf);
385               if (!opt.checkonly)
386                 exit (1);
387             }
388         }
389       else
390         {
391           array[arrayidx].type = PAT_STRING;
392           array[arrayidx].u.s.string = p;
393           array[arrayidx].u.s.length = strlen (p);
394         }
395       arrayidx++;
396     }
397   assert (arrayidx < arraysize);
398   array[arrayidx].type = PAT_NULL;
399
400   return array;
401 }
402
403
404 /* Check whether string macthes any of the pattern in PATARRAY and
405    returns the matching pattern item or NULL.  */
406 static pattern_t *
407 match_p (const char *string, pattern_t *patarray)
408 {
409   pattern_t *pat;
410
411   if (!*string)
412     {
413       if (opt.verbose)
414         log_info ("zero length input line - ignored\n");
415       return NULL;
416     }
417
418   for (pat = patarray; pat->type != PAT_NULL; pat++)
419     {
420       if (pat->type == PAT_STRING)
421         {
422           if (!strcasecmp (pat->u.s.string, string))
423             return pat;
424         }
425       else if (pat->type == PAT_REGEX)
426         {
427           int rerr;
428
429           rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
430           if (!rerr)
431             return pat;
432           else if (rerr != REG_NOMATCH)
433             {
434               char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
435               log_error ("matching r.e. failed: %s\n", rerrbuf);
436               xfree (rerrbuf);
437               return pat;  /* Better indicate a match on error.  */
438             }
439         }
440       else
441         BUG ();
442     }
443   return NULL;
444 }
445
446
447 /* Actual processing of the input.  This function does not return an
448    error code but exits as soon as a match has been found.  */
449 static void
450 process (FILE *fp, pattern_t *patarray)
451 {
452   char buffer[2048];
453   size_t idx;
454   int c;
455   unsigned long lineno = 0;
456   pattern_t *pat;
457
458   idx = 0;
459   c = 0;
460   while (idx < sizeof buffer -1 && c != EOF )
461     {
462       if ((c = getc (fp)) != EOF)
463         buffer[idx] = c;
464       if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
465         {
466           lineno++;
467           if (!opt.null)
468             {
469               while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
470                 idx--;
471             }
472           buffer[idx] = 0;
473           pat = match_p (buffer, patarray);
474           if (pat)
475             {
476               if (opt.verbose)
477                 log_error ("input line %lu matches pattern at line %u"
478                            " - rejected\n",
479                            lineno, pat->lineno);
480               exit (1);
481             }
482           idx = 0;
483         }
484       else
485         idx++;
486     }
487   if (c != EOF)
488     {
489       log_error ("input line %lu too long - rejected\n", lineno+1);
490       exit (1);
491     }
492   if (ferror (fp))
493     {
494       log_error ("input read error at line %lu: %s - rejected\n",
495                  lineno+1, strerror (errno));
496       exit (1);
497     }
498   if (opt.verbose)
499     log_info ("no input line matches the pattern - accepted\n");
500 }
501