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