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 <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 :
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", GPGRT_LOG_WITH_PREFIX);
169 :
170 : /* Make sure that our subsystems are ready. */
171 0 : i18n_init ();
172 0 : init_common_subsystems (&argc, &argv);
173 :
174 0 : setup_libgcrypt_logging ();
175 0 : gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
176 :
177 0 : pargs.argc = &argc;
178 0 : pargs.argv = &argv;
179 0 : pargs.flags= 1; /* (do not remove the args) */
180 0 : while (arg_parse (&pargs, opts) )
181 : {
182 0 : switch (pargs.r_opt)
183 : {
184 0 : case oVerbose: opt.verbose++; break;
185 0 : case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
186 0 : case oCheck: opt.checkonly = 1; break;
187 0 : case oNull: opt.null = 1; break;
188 :
189 0 : default : pargs.err = 2; break;
190 : }
191 : }
192 0 : if (log_get_errorcount(0))
193 0 : exit (2);
194 :
195 0 : if (argc != 1)
196 0 : 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 0 : raw_pattern = read_file (*argv, &raw_pattern_length);
204 0 : if (!raw_pattern)
205 0 : exit (2);
206 :
207 0 : patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
208 0 : if (!patternarray)
209 0 : exit (1);
210 0 : if (opt.checkonly)
211 0 : return 0;
212 :
213 : #ifdef HAVE_DOSISH_SYSTEM
214 : setmode (fileno (stdin) , O_BINARY );
215 : #endif
216 0 : process (stdin, patternarray);
217 :
218 0 : 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 0 : read_file (const char *fname, size_t *r_length)
229 : {
230 : FILE *fp;
231 : char *buf;
232 : size_t buflen;
233 :
234 0 : if (!strcmp (fname, "-"))
235 : {
236 0 : size_t nread, bufsize = 0;
237 :
238 0 : fp = stdin;
239 : #ifdef HAVE_DOSISH_SYSTEM
240 : setmode ( fileno(fp) , O_BINARY );
241 : #endif
242 0 : buf = NULL;
243 0 : buflen = 0;
244 : #define NCHUNK 8192
245 : do
246 : {
247 0 : bufsize += NCHUNK;
248 0 : if (!buf)
249 0 : buf = xmalloc (bufsize+1);
250 : else
251 0 : buf = xrealloc (buf, bufsize+1);
252 :
253 0 : nread = fread (buf+buflen, 1, NCHUNK, fp);
254 0 : if (nread < NCHUNK && ferror (fp))
255 : {
256 0 : log_error ("error reading '[stdin]': %s\n", strerror (errno));
257 0 : xfree (buf);
258 0 : return NULL;
259 : }
260 0 : buflen += nread;
261 : }
262 0 : while (nread == NCHUNK);
263 : #undef NCHUNK
264 :
265 : }
266 : else
267 : {
268 : struct stat st;
269 :
270 0 : fp = fopen (fname, "rb");
271 0 : if (!fp)
272 : {
273 0 : log_error ("can't open '%s': %s\n", fname, strerror (errno));
274 0 : return NULL;
275 : }
276 :
277 0 : if (fstat (fileno(fp), &st))
278 : {
279 0 : log_error ("can't stat '%s': %s\n", fname, strerror (errno));
280 0 : fclose (fp);
281 0 : return NULL;
282 : }
283 :
284 0 : buflen = st.st_size;
285 0 : buf = xmalloc (buflen+1);
286 0 : if (fread (buf, buflen, 1, fp) != 1)
287 : {
288 0 : log_error ("error reading '%s': %s\n", fname, strerror (errno));
289 0 : fclose (fp);
290 0 : xfree (buf);
291 0 : return NULL;
292 : }
293 0 : fclose (fp);
294 : }
295 0 : buf[buflen] = 0;
296 0 : *r_length = buflen;
297 0 : return buf;
298 : }
299 :
300 :
301 :
302 : static char *
303 0 : get_regerror (int errcode, regex_t *compiled)
304 : {
305 0 : size_t length = regerror (errcode, compiled, NULL, 0);
306 0 : char *buffer = xmalloc (length);
307 0 : regerror (errcode, compiled, buffer, length);
308 0 : 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 0 : 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 0 : unsigned int lineno = 0;
325 :
326 : /* Estimate the number of entries by counting the non-comment lines. */
327 0 : arraysize = 0;
328 0 : p = data;
329 0 : for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
330 0 : if (*p != '#')
331 0 : arraysize++;
332 0 : arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
333 :
334 0 : array = xcalloc (arraysize, sizeof *array);
335 0 : arrayidx = 0;
336 :
337 : /* Loop over all lines. */
338 0 : while (datalen && data)
339 : {
340 0 : lineno++;
341 0 : p = data;
342 0 : p2 = data = memchr (p, '\n', datalen);
343 0 : if (p2)
344 : {
345 0 : *data++ = 0;
346 0 : datalen -= data - p;
347 : }
348 : else
349 0 : p2 = p + datalen;
350 0 : assert (!*p2);
351 0 : p2--;
352 0 : while (isascii (*p) && isspace (*p))
353 0 : p++;
354 0 : if (*p == '#')
355 0 : continue;
356 0 : while (p2 > p && isascii (*p2) && isspace (*p2))
357 0 : *p2-- = 0;
358 0 : if (!*p)
359 0 : continue;
360 0 : assert (arrayidx < arraysize);
361 0 : array[arrayidx].lineno = lineno;
362 0 : if (*p == '/')
363 : {
364 : int rerr;
365 :
366 0 : p++;
367 0 : array[arrayidx].type = PAT_REGEX;
368 0 : if (*p && p[strlen(p)-1] == '/')
369 0 : p[strlen(p)-1] = 0; /* Remove optional delimiter. */
370 0 : array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
371 0 : rerr = regcomp (array[arrayidx].u.r.regex, p,
372 : REG_ICASE|REG_NOSUB|REG_EXTENDED);
373 0 : if (rerr)
374 : {
375 0 : char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
376 0 : log_error ("invalid r.e. at line %u: %s\n", lineno, rerrbuf);
377 0 : xfree (rerrbuf);
378 0 : if (!opt.checkonly)
379 0 : exit (1);
380 : }
381 : }
382 : else
383 : {
384 0 : array[arrayidx].type = PAT_STRING;
385 0 : array[arrayidx].u.s.string = p;
386 0 : array[arrayidx].u.s.length = strlen (p);
387 : }
388 0 : arrayidx++;
389 : }
390 0 : assert (arrayidx < arraysize);
391 0 : array[arrayidx].type = PAT_NULL;
392 :
393 0 : 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 0 : match_p (const char *string, pattern_t *patarray)
401 : {
402 : pattern_t *pat;
403 :
404 0 : if (!*string)
405 : {
406 0 : if (opt.verbose)
407 0 : log_info ("zero length input line - ignored\n");
408 0 : return NULL;
409 : }
410 :
411 0 : for (pat = patarray; pat->type != PAT_NULL; pat++)
412 : {
413 0 : if (pat->type == PAT_STRING)
414 : {
415 0 : if (!strcasecmp (pat->u.s.string, string))
416 0 : return pat;
417 : }
418 0 : else if (pat->type == PAT_REGEX)
419 : {
420 : int rerr;
421 :
422 0 : rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
423 0 : if (!rerr)
424 0 : return pat;
425 0 : else if (rerr != REG_NOMATCH)
426 : {
427 0 : char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
428 0 : log_error ("matching r.e. failed: %s\n", rerrbuf);
429 0 : xfree (rerrbuf);
430 0 : return pat; /* Better indicate a match on error. */
431 : }
432 : }
433 : else
434 0 : BUG ();
435 : }
436 0 : 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 0 : process (FILE *fp, pattern_t *patarray)
444 : {
445 : char buffer[2048];
446 : size_t idx;
447 : int c;
448 0 : unsigned long lineno = 0;
449 : pattern_t *pat;
450 :
451 0 : idx = 0;
452 0 : c = 0;
453 0 : while (idx < sizeof buffer -1 && c != EOF )
454 : {
455 0 : if ((c = getc (fp)) != EOF)
456 0 : buffer[idx] = c;
457 0 : if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
458 : {
459 0 : lineno++;
460 0 : if (!opt.null)
461 : {
462 0 : while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
463 0 : idx--;
464 : }
465 0 : buffer[idx] = 0;
466 0 : pat = match_p (buffer, patarray);
467 0 : if (pat)
468 : {
469 0 : if (opt.verbose)
470 0 : log_error ("input line %lu matches pattern at line %u"
471 : " - rejected\n",
472 : lineno, pat->lineno);
473 0 : exit (1);
474 : }
475 0 : idx = 0;
476 : }
477 : else
478 0 : idx++;
479 : }
480 0 : if (c != EOF)
481 : {
482 0 : log_error ("input line %lu too long - rejected\n", lineno+1);
483 0 : exit (1);
484 : }
485 0 : if (ferror (fp))
486 : {
487 0 : log_error ("input read error at line %lu: %s - rejected\n",
488 0 : lineno+1, strerror (errno));
489 0 : exit (1);
490 : }
491 0 : if (opt.verbose)
492 0 : log_info ("no input line matches the pattern - accepted\n");
493 0 : }
494 :
|