Line data Source code
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 :
129 : /* Info function for usage(). */
130 : static const char *
131 0 : my_strusage (int level)
132 : {
133 : const char *p;
134 0 : switch (level)
135 : {
136 0 : case 11: p = "gpg-check-pattern (@GnuPG@)";
137 0 : break;
138 0 : case 13: p = VERSION; break;
139 0 : case 17: p = PRINTABLE_OS_NAME; break;
140 0 : case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
141 :
142 : case 1:
143 : case 40:
144 0 : p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
145 0 : break;
146 : case 41:
147 0 : p = _("Syntax: gpg-check-pattern [options] patternfile\n"
148 : "Check a passphrase given on stdin against the patternfile\n");
149 0 : break;
150 :
151 0 : default: p = NULL;
152 : }
153 0 : return p;
154 : }
155 :
156 :
157 : int
158 0 : 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 0 : early_system_init ();
166 0 : set_strusage (my_strusage);
167 0 : gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
168 0 : log_set_prefix ("gpg-check-pattern", 1);
169 :
170 : /* Make sure that our subsystems are ready. */
171 0 : i18n_init ();
172 0 : init_common_subsystems (&argc, &argv);
173 :
174 : /* We need Libgcrypt for hashing. */
175 0 : if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
176 : {
177 0 : log_fatal ( _("%s is too old (need %s, have %s)\n"), "libgcrypt",
178 : NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
179 : }
180 :
181 0 : setup_libgcrypt_logging ();
182 0 : gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
183 :
184 0 : opt.homedir = default_homedir ();
185 :
186 0 : pargs.argc = &argc;
187 0 : pargs.argv = &argv;
188 0 : pargs.flags= 1; /* (do not remove the args) */
189 0 : while (arg_parse (&pargs, opts) )
190 : {
191 0 : switch (pargs.r_opt)
192 : {
193 0 : case oVerbose: opt.verbose++; break;
194 0 : case oHomedir: opt.homedir = pargs.r.ret_str; break;
195 0 : case oCheck: opt.checkonly = 1; break;
196 0 : case oNull: opt.null = 1; break;
197 :
198 0 : default : pargs.err = 2; break;
199 : }
200 : }
201 0 : if (log_get_errorcount(0))
202 0 : exit (2);
203 :
204 0 : if (argc != 1)
205 0 : 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 0 : raw_pattern = read_file (*argv, &raw_pattern_length);
213 0 : if (!raw_pattern)
214 0 : exit (2);
215 :
216 0 : patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
217 0 : if (!patternarray)
218 0 : exit (1);
219 0 : if (opt.checkonly)
220 0 : return 0;
221 :
222 : #ifdef HAVE_DOSISH_SYSTEM
223 : setmode (fileno (stdin) , O_BINARY );
224 : #endif
225 0 : process (stdin, patternarray);
226 :
227 0 : 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 0 : read_file (const char *fname, size_t *r_length)
238 : {
239 : FILE *fp;
240 : char *buf;
241 : size_t buflen;
242 :
243 0 : if (!strcmp (fname, "-"))
244 : {
245 0 : size_t nread, bufsize = 0;
246 :
247 0 : fp = stdin;
248 : #ifdef HAVE_DOSISH_SYSTEM
249 : setmode ( fileno(fp) , O_BINARY );
250 : #endif
251 0 : buf = NULL;
252 0 : buflen = 0;
253 : #define NCHUNK 8192
254 : do
255 : {
256 0 : bufsize += NCHUNK;
257 0 : if (!buf)
258 0 : buf = xmalloc (bufsize+1);
259 : else
260 0 : buf = xrealloc (buf, bufsize+1);
261 :
262 0 : nread = fread (buf+buflen, 1, NCHUNK, fp);
263 0 : if (nread < NCHUNK && ferror (fp))
264 : {
265 0 : log_error ("error reading '[stdin]': %s\n", strerror (errno));
266 0 : xfree (buf);
267 0 : return NULL;
268 : }
269 0 : buflen += nread;
270 : }
271 0 : while (nread == NCHUNK);
272 : #undef NCHUNK
273 :
274 : }
275 : else
276 : {
277 : struct stat st;
278 :
279 0 : fp = fopen (fname, "rb");
280 0 : if (!fp)
281 : {
282 0 : log_error ("can't open '%s': %s\n", fname, strerror (errno));
283 0 : return NULL;
284 : }
285 :
286 0 : if (fstat (fileno(fp), &st))
287 : {
288 0 : log_error ("can't stat '%s': %s\n", fname, strerror (errno));
289 0 : fclose (fp);
290 0 : return NULL;
291 : }
292 :
293 0 : buflen = st.st_size;
294 0 : buf = xmalloc (buflen+1);
295 0 : if (fread (buf, buflen, 1, fp) != 1)
296 : {
297 0 : log_error ("error reading '%s': %s\n", fname, strerror (errno));
298 0 : fclose (fp);
299 0 : xfree (buf);
300 0 : return NULL;
301 : }
302 0 : fclose (fp);
303 : }
304 0 : buf[buflen] = 0;
305 0 : *r_length = buflen;
306 0 : return buf;
307 : }
308 :
309 :
310 :
311 : static char *
312 0 : get_regerror (int errcode, regex_t *compiled)
313 : {
314 0 : size_t length = regerror (errcode, compiled, NULL, 0);
315 0 : char *buffer = xmalloc (length);
316 0 : regerror (errcode, compiled, buffer, length);
317 0 : 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 fucntion
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 0 : 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 0 : unsigned int lineno = 0;
334 :
335 : /* Estimate the number of entries by counting the non-comment lines. */
336 0 : arraysize = 0;
337 0 : p = data;
338 0 : for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
339 0 : if (*p != '#')
340 0 : arraysize++;
341 0 : arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
342 :
343 0 : array = xcalloc (arraysize, sizeof *array);
344 0 : arrayidx = 0;
345 :
346 : /* Loop over all lines. */
347 0 : while (datalen && data)
348 : {
349 0 : lineno++;
350 0 : p = data;
351 0 : p2 = data = memchr (p, '\n', datalen);
352 0 : if (p2)
353 : {
354 0 : *data++ = 0;
355 0 : datalen -= data - p;
356 : }
357 : else
358 0 : p2 = p + datalen;
359 0 : assert (!*p2);
360 0 : p2--;
361 0 : while (isascii (*p) && isspace (*p))
362 0 : p++;
363 0 : if (*p == '#')
364 0 : continue;
365 0 : while (p2 > p && isascii (*p2) && isspace (*p2))
366 0 : *p2-- = 0;
367 0 : if (!*p)
368 0 : continue;
369 0 : assert (arrayidx < arraysize);
370 0 : array[arrayidx].lineno = lineno;
371 0 : if (*p == '/')
372 : {
373 : int rerr;
374 :
375 0 : p++;
376 0 : array[arrayidx].type = PAT_REGEX;
377 0 : if (*p && p[strlen(p)-1] == '/')
378 0 : p[strlen(p)-1] = 0; /* Remove optional delimiter. */
379 0 : array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
380 0 : rerr = regcomp (array[arrayidx].u.r.regex, p,
381 : REG_ICASE|REG_NOSUB|REG_EXTENDED);
382 0 : if (rerr)
383 : {
384 0 : char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
385 0 : log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
386 0 : xfree (rerrbuf);
387 0 : if (!opt.checkonly)
388 0 : exit (1);
389 : }
390 : }
391 : else
392 : {
393 0 : array[arrayidx].type = PAT_STRING;
394 0 : array[arrayidx].u.s.string = p;
395 0 : array[arrayidx].u.s.length = strlen (p);
396 : }
397 0 : arrayidx++;
398 : }
399 0 : assert (arrayidx < arraysize);
400 0 : array[arrayidx].type = PAT_NULL;
401 :
402 0 : 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 0 : match_p (const char *string, pattern_t *patarray)
410 : {
411 : pattern_t *pat;
412 :
413 0 : if (!*string)
414 : {
415 0 : if (opt.verbose)
416 0 : log_info ("zero length input line - ignored\n");
417 0 : return NULL;
418 : }
419 :
420 0 : for (pat = patarray; pat->type != PAT_NULL; pat++)
421 : {
422 0 : if (pat->type == PAT_STRING)
423 : {
424 0 : if (!strcasecmp (pat->u.s.string, string))
425 0 : return pat;
426 : }
427 0 : else if (pat->type == PAT_REGEX)
428 : {
429 : int rerr;
430 :
431 0 : rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
432 0 : if (!rerr)
433 0 : return pat;
434 0 : else if (rerr != REG_NOMATCH)
435 : {
436 0 : char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
437 0 : log_error ("matching r.e. failed: %s\n", rerrbuf);
438 0 : xfree (rerrbuf);
439 0 : return pat; /* Better indicate a match on error. */
440 : }
441 : }
442 : else
443 0 : BUG ();
444 : }
445 0 : return NULL;
446 : }
447 :
448 :
449 : /* Actual processing of the input. This fucntion does not return an
450 : error code but exits as soon as a match has been found. */
451 : static void
452 0 : process (FILE *fp, pattern_t *patarray)
453 : {
454 : char buffer[2048];
455 : size_t idx;
456 : int c;
457 0 : unsigned long lineno = 0;
458 : pattern_t *pat;
459 :
460 0 : idx = 0;
461 0 : c = 0;
462 0 : while (idx < sizeof buffer -1 && c != EOF )
463 : {
464 0 : if ((c = getc (fp)) != EOF)
465 0 : buffer[idx] = c;
466 0 : if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
467 : {
468 0 : lineno++;
469 0 : if (!opt.null)
470 : {
471 0 : while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
472 0 : idx--;
473 : }
474 0 : buffer[idx] = 0;
475 0 : pat = match_p (buffer, patarray);
476 0 : if (pat)
477 : {
478 0 : if (opt.verbose)
479 0 : log_error ("input line %lu matches pattern at line %u"
480 : " - rejected\n",
481 : lineno, pat->lineno);
482 0 : exit (1);
483 : }
484 0 : idx = 0;
485 : }
486 : else
487 0 : idx++;
488 : }
489 0 : if (c != EOF)
490 : {
491 0 : log_error ("input line %lu too long - rejected\n", lineno+1);
492 0 : exit (1);
493 : }
494 0 : if (ferror (fp))
495 : {
496 0 : log_error ("input read error at line %lu: %s - rejected\n",
497 0 : lineno+1, strerror (errno));
498 0 : exit (1);
499 : }
500 0 : if (opt.verbose)
501 0 : log_info ("no input line matches the pattern - accepted\n");
502 0 : }
503 :
|