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