Add a hook to be called right after main.
[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   early_system_init ();
168   set_strusage (my_strusage);
169   gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
170   log_set_prefix ("gpg-check-pattern", 1);
171
172   /* Make sure that our subsystems are ready.  */
173   i18n_init ();
174   init_common_subsystems (&argc, &argv);
175
176   /* We need Libgcrypt for hashing.  */
177   if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
178     {
179       log_fatal ( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
180                   NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
181     }
182
183   setup_libgcrypt_logging ();
184   gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
185
186   opt.homedir = default_homedir ();
187
188   pargs.argc = &argc;
189   pargs.argv = &argv;
190   pargs.flags=  1;  /* (do not remove the args) */
191   while (arg_parse (&pargs, opts) )
192     {
193       switch (pargs.r_opt)
194         {
195         case oVerbose: opt.verbose++; break;
196         case oHomedir: opt.homedir = pargs.r.ret_str; break;
197         case oCheck: opt.checkonly = 1; break;
198         case oNull: opt.null = 1; break;
199
200         default : pargs.err = 2; break;
201         }
202     }
203   if (log_get_errorcount(0))
204     exit (2);
205
206   if (argc != 1)
207     usage (1);
208
209   /* We read the entire pattern file into our memory and parse it
210      using a separate function.  This allows us to eventual do the
211      reading while running setuid so that the pattern file can be
212      hidden from regular users.  I am not sure whether this makes
213      sense, but lets be prepared for it.  */
214   raw_pattern = read_file (*argv, &raw_pattern_length);
215   if (!raw_pattern)
216     exit (2);
217
218   patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
219   if (!patternarray)
220     exit (1);
221   if (opt.checkonly)
222     return 0;
223
224 #ifdef HAVE_DOSISH_SYSTEM
225   setmode (fileno (stdin) , O_BINARY );
226 #endif
227   process (stdin, patternarray);
228
229   return log_get_errorcount(0)? 1 : 0;
230 }
231
232
233
234 /* Read a file FNAME into a buffer and return that malloced buffer.
235    Caller must free the buffer.  On error NULL is returned, on success
236    the valid length of the buffer is stored at R_LENGTH.  The returned
237    buffer is guarnteed to be nul terminated.  */
238 static char *
239 read_file (const char *fname, size_t *r_length)
240 {
241   FILE *fp;
242   char *buf;
243   size_t buflen;
244
245   if (!strcmp (fname, "-"))
246     {
247       size_t nread, bufsize = 0;
248
249       fp = stdin;
250 #ifdef HAVE_DOSISH_SYSTEM
251       setmode ( fileno(fp) , O_BINARY );
252 #endif
253       buf = NULL;
254       buflen = 0;
255 #define NCHUNK 8192
256       do
257         {
258           bufsize += NCHUNK;
259           if (!buf)
260             buf = xmalloc (bufsize+1);
261           else
262             buf = xrealloc (buf, bufsize+1);
263
264           nread = fread (buf+buflen, 1, NCHUNK, fp);
265           if (nread < NCHUNK && ferror (fp))
266             {
267               log_error ("error reading '[stdin]': %s\n", strerror (errno));
268               xfree (buf);
269               return NULL;
270             }
271           buflen += nread;
272         }
273       while (nread == NCHUNK);
274 #undef NCHUNK
275
276     }
277   else
278     {
279       struct stat st;
280
281       fp = fopen (fname, "rb");
282       if (!fp)
283         {
284           log_error ("can't open '%s': %s\n", fname, strerror (errno));
285           return NULL;
286         }
287
288       if (fstat (fileno(fp), &st))
289         {
290           log_error ("can't stat '%s': %s\n", fname, strerror (errno));
291           fclose (fp);
292           return NULL;
293         }
294
295       buflen = st.st_size;
296       buf = xmalloc (buflen+1);
297       if (fread (buf, buflen, 1, fp) != 1)
298         {
299           log_error ("error reading '%s': %s\n", fname, strerror (errno));
300           fclose (fp);
301           xfree (buf);
302           return NULL;
303         }
304       fclose (fp);
305     }
306   buf[buflen] = 0;
307   *r_length = buflen;
308   return buf;
309 }
310
311
312
313 static char *
314 get_regerror (int errcode, regex_t *compiled)
315 {
316   size_t length = regerror (errcode, compiled, NULL, 0);
317   char *buffer = xmalloc (length);
318   regerror (errcode, compiled, buffer, length);
319   return buffer;
320 }
321
322 /* Parse the pattern given in the memory aread DATA/DATALEN and return
323    a new pattern array.  The end of the array is indicated by a NULL
324    entry.  On error an error message is printed and the fucntion
325    returns NULL.  Note that the function modifies DATA and assumes
326    that data is nul terminated (even if this is one byte past
327    DATALEN).  */
328 static pattern_t *
329 parse_pattern_file (char *data, size_t datalen)
330 {
331   char *p, *p2;
332   size_t n;
333   pattern_t *array;
334   size_t arraysize, arrayidx;
335   unsigned int lineno = 0;
336
337   /* Estimate the number of entries by counting the non-comment lines.  */
338   arraysize = 0;
339   p = data;
340   for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
341     if (*p != '#')
342       arraysize++;
343   arraysize += 2; /* For the terminating NULL and a last line w/o a LF.  */
344
345   array = xcalloc (arraysize, sizeof *array);
346   arrayidx = 0;
347
348   /* Loop over all lines.  */
349   while (datalen && data)
350     {
351       lineno++;
352       p = data;
353       p2 = data = memchr (p, '\n', datalen);
354       if (p2)
355         {
356           *data++ = 0;
357           datalen -= data - p;
358         }
359       else
360         p2 = p + datalen;
361       assert (!*p2);
362       p2--;
363       while (isascii (*p) && isspace (*p))
364         p++;
365       if (*p == '#')
366         continue;
367       while (p2 > p && isascii (*p2) && isspace (*p2))
368         *p2-- = 0;
369       if (!*p)
370         continue;
371       assert (arrayidx < arraysize);
372       array[arrayidx].lineno = lineno;
373       if (*p == '/')
374         {
375           int rerr;
376
377           p++;
378           array[arrayidx].type = PAT_REGEX;
379           if (*p && p[strlen(p)-1] == '/')
380             p[strlen(p)-1] = 0;  /* Remove optional delimiter.  */
381           array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
382           rerr = regcomp (array[arrayidx].u.r.regex, p,
383                           REG_ICASE|REG_NOSUB|REG_EXTENDED);
384           if (rerr)
385             {
386               char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
387               log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
388               xfree (rerrbuf);
389               if (!opt.checkonly)
390                 exit (1);
391             }
392         }
393       else
394         {
395           array[arrayidx].type = PAT_STRING;
396           array[arrayidx].u.s.string = p;
397           array[arrayidx].u.s.length = strlen (p);
398         }
399       arrayidx++;
400     }
401   assert (arrayidx < arraysize);
402   array[arrayidx].type = PAT_NULL;
403
404   return array;
405 }
406
407
408 /* Check whether string macthes any of the pattern in PATARRAY and
409    returns the matching pattern item or NULL.  */
410 static pattern_t *
411 match_p (const char *string, pattern_t *patarray)
412 {
413   pattern_t *pat;
414
415   if (!*string)
416     {
417       if (opt.verbose)
418         log_info ("zero length input line - ignored\n");
419       return NULL;
420     }
421
422   for (pat = patarray; pat->type != PAT_NULL; pat++)
423     {
424       if (pat->type == PAT_STRING)
425         {
426           if (!strcasecmp (pat->u.s.string, string))
427             return pat;
428         }
429       else if (pat->type == PAT_REGEX)
430         {
431           int rerr;
432
433           rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
434           if (!rerr)
435             return pat;
436           else if (rerr != REG_NOMATCH)
437             {
438               char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
439               log_error ("matching r.e. failed: %s\n", rerrbuf);
440               xfree (rerrbuf);
441               return pat;  /* Better indicate a match on error.  */
442             }
443         }
444       else
445         BUG ();
446     }
447   return NULL;
448 }
449
450
451 /* Actual processing of the input.  This fucntion does not return an
452    error code but exits as soon as a match has been found.  */
453 static void
454 process (FILE *fp, pattern_t *patarray)
455 {
456   char buffer[2048];
457   size_t idx;
458   int c;
459   unsigned long lineno = 0;
460   pattern_t *pat;
461
462   idx = 0;
463   c = 0;
464   while (idx < sizeof buffer -1 && c != EOF )
465     {
466       if ((c = getc (fp)) != EOF)
467         buffer[idx] = c;
468       if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
469         {
470           lineno++;
471           if (!opt.null)
472             {
473               while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
474                 idx--;
475             }
476           buffer[idx] = 0;
477           pat = match_p (buffer, patarray);
478           if (pat)
479             {
480               if (opt.verbose)
481                 log_error ("input line %lu matches pattern at line %u"
482                            " - rejected\n",
483                            lineno, pat->lineno);
484               exit (1);
485             }
486           idx = 0;
487         }
488       else
489         idx++;
490     }
491   if (c != EOF)
492     {
493       log_error ("input line %lu too long - rejected\n", lineno+1);
494       exit (1);
495     }
496   if (ferror (fp))
497     {
498       log_error ("input read error at line %lu: %s - rejected\n",
499                  lineno+1, strerror (errno));
500       exit (1);
501     }
502   if (opt.verbose)
503     log_info ("no input line matches the pattern - accepted\n");
504 }
505