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