(main): Reverse order of `check' and `compute sums' blocks in if-else
[platform/upstream/coreutils.git] / src / md5sum.c
1 /* Compute MD5 checksum of files or strings according to the definition
2    of MD5 in RFC 1321 from April 1992.
3    Copyright (C) 1995 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 /* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <getopt.h>
26 #include <stdio.h>
27 #include <sys/types.h>
28
29 #include "long-options.h"
30 #include "md5.h"
31 #include "getline.h"
32 #include "system.h"
33 #include "error.h"
34 #include "version.h"
35
36 /* Most systems do not distinguish between external and internal
37    text representations.  */
38 #if UNIX || __UNIX__ || unix || __unix__ || _POSIX_VERSION
39 # define OPENOPTS(BINARY) "r"
40 #else
41 # ifdef MSDOS
42 #  define TEXT1TO1 "rb"
43 #  define TEXTCNVT "r"
44 # else
45 #  if defined VMS
46 #   define TEXT1TO1 "rb", "ctx=stm"
47 #   define TEXTCNVT "r", "ctx=stm"
48 #  else
49     /* The following line is intended to evoke an error.
50        Using #error is not portable enough.  */
51     "Cannot determine system type."
52 #  endif
53 # endif
54 # define OPENOPTS(BINARY) ((BINARY) != 0 ? TEXT1TO1 : TEXTCNVT)
55 #endif
56
57 #if _LIBC || STDC_HEADERS
58 # define TOLOWER(c) tolower (c)
59 #else
60 # define TOLOWER(c) (ISUPPER (c) ? tolower (c) : (c))
61 #endif
62
63 /* Nonzero if any of the files read were the standard input. */
64 static int have_read_stdin;
65
66 /* With --check, don't generate any output.
67    The exit code indicates success or failure.  */
68 static int status_only = 0;
69
70 /* With --check, print a message to standard error warning about each
71    improperly formatted MD5 checksum line.  */
72 static int warn = 0;
73
74 /* The name this program was run with.  */
75 char *program_name;
76
77 static const struct option long_options[] =
78 {
79   { "binary", no_argument, 0, 'b' },
80   { "check", no_argument, 0, 'c' },
81   { "status", no_argument, 0, 2 },
82   { "string", required_argument, 0, 1 },
83   { "text", no_argument, 0, 't' },
84   { "warn", no_argument, 0, 'w' },
85   { NULL, 0, NULL, 0 }
86 };
87
88 char *xmalloc ();
89
90 static void
91 usage (int status)
92 {
93   if (status != 0)
94     fprintf (stderr, _("Try `%s --help' for more information.\n"),
95              program_name);
96   else
97     printf (_("\
98 Usage: %s [OPTION] [FILE]...\n\
99   or:  %s [OPTION] --check [FILE]\n\
100   or:  %s [OPTION] --string=STRING ...\n\
101 Print or check MD5 checksums.\n\
102 With no FILE, or when FILE is -, read standard input.\n\
103 \n\
104   -b, --binary            read files in binary mode\n\
105   -t, --text              read files in text mode (default)\n\
106   -c, --check             check MD5 sums against given list\n\
107 \n\
108 The following two options are useful only when verifying checksums:\n\
109       --status            don't output anything, status code shows success\n\
110   -w, --warn              warn about improperly formated MD5 checksum lines\n\
111 \n\
112       --string=STRING     compute checksum for STRING\n\
113       --help              display this help and exit\n\
114       --version           output version information and exit\n\
115 \n\
116 The sums are computed as described in RFC 1321.  When checking, the input\n\
117 should be a former output of this program.  The default mode is to print\n\
118 a line with checksum, a character indicating type (`*' for binary, ` ' for\n\
119 text), and name for each FILE.  The --status and --warn options are\n\
120 meaningful only when verifying checksums.\n"),
121             program_name, program_name, program_name);
122
123   exit (status);
124 }
125
126 /* FIXME: this format loses with filenames containing newline.  */
127
128 static int
129 split_3 (char *s, char **u, int *binary, char **w)
130 {
131   size_t i;
132
133 #define ISWHITE(c) ((c) == ' ' || (c) == '\t')
134
135   i = 0;
136   while (ISWHITE (s[i]))
137     ++i;
138
139   /* The line has to be at least 35 characters long to contain correct
140      message digest information.  */
141   if (strlen (&s[i]) >= 35)
142     {
143       *u = &s[i];
144
145       /* The first field has to be the 32-character hexadecimal
146          representation of the message digest.  If it not immediately
147          followed by a white space it's an error.  */
148       i += 32;
149       if (!ISWHITE (s[i]))
150         return 1;
151
152       s[i++] = '\0';
153
154       if (s[i] != ' ' && s[i] != '*')
155         return 1;
156       *binary = s[i++] == '*';
157
158       /* All characters between the type indicator and end of line are
159          significant -- that includes leading and trailing white space.  */
160       *w = &s[i];
161
162       /* So this line is valid as long as there is at least one character
163          for the filename.  */
164       return (**w ? 0 : 1);
165     }
166   return 1;
167 }
168
169 /* FIXME: use strcspn.  */
170
171 static int
172 hex_digits (const char *s)
173 {
174   while (*s)
175     {
176       if (!ISXDIGIT (*s))
177         return 0;
178       ++s;
179     }
180   return 1;
181 }
182
183 /* FIXME: allow newline in filename by encoding it. */
184 /* FIXME: distinguish between file open/read failure and inconsistent
185    checksum. */
186
187 static int
188 md5_file (const char *filename, int binary, unsigned char *md5_result)
189 {
190   FILE *fp;
191   int err;
192
193   if (strcmp (filename, "-") == 0)
194     {
195       have_read_stdin = 1;
196       fp = stdin;
197     }
198   else
199     {
200       /* OPENOPTS is a macro.  It varies with the system.
201          Some systems distinguish between internal and
202          external text representations.  */
203
204       fp = fopen (filename, OPENOPTS (binary));
205       if (fp == NULL)
206         {
207           error (0, errno, "%s", filename);
208           return 1;
209         }
210     }
211
212   err = md5_stream (fp, md5_result);
213   if (err)
214     {
215       error (0, errno, "%s", filename);
216       if (fp != stdin)
217         fclose (fp);
218       return 1;
219     }
220
221   if (fp != stdin && fclose (fp) == EOF)
222     {
223       error (0, errno, "%s", filename);
224       return 1;
225     }
226
227   return 0;
228 }
229
230 static int
231 md5_check (const char *checkfile_name, int binary)
232 {
233   FILE *checkfile_stream;
234   int n_tests = 0;
235   int n_tests_failed = 0;
236   unsigned char md5buffer[16];
237   size_t line_number;
238   char *line;
239   size_t line_chars_allocated;
240
241   if (strcmp (checkfile_name, "-") == 0)
242     {
243       have_read_stdin = 1;
244       checkfile_name = _("standard input");
245       checkfile_stream = stdin;
246     }
247   else
248     {
249       checkfile_stream = fopen (checkfile_name, "r");
250       if (checkfile_stream == NULL)
251         {
252           error (0, errno, "%s", checkfile_name);
253           return 1;
254         }
255     }
256
257   line_number = 0;
258   line = NULL;
259   line_chars_allocated = 0;
260   do
261     {
262       char *filename;
263       int type_flag;
264       char *md5num;
265       int err;
266       int line_length;
267
268       ++line_number;
269
270       line_length = getline (&line, &line_chars_allocated, checkfile_stream);
271       if (line_length <= 0)
272         break;
273
274       /* Ignore comment lines, which begin with a '#' character.  */
275       if (line[0] == '#')
276         continue;
277
278       /* Remove any trailing newline.  */
279       if (line[line_length - 1] == '\n')
280         line[--line_length] = '\0';
281
282       err = split_3 (line, &md5num, &type_flag, &filename);
283       if (err || !hex_digits (md5num))
284         {
285           if (warn)
286             {
287               error (0, 0,
288                      _("%s: %lu: improperly formatted MD5 checksum line"),
289                      checkfile_name, (unsigned long) line_number);
290             }
291         }
292       else
293         {
294           static const char bin2hex[] = { '0', '1', '2', '3',
295                                           '4', '5', '6', '7',
296                                           '8', '9', 'a', 'b',
297                                           'c', 'd', 'e', 'f' };
298           int fail;
299
300           ++n_tests;
301
302           fail = md5_file (filename, binary, md5buffer);
303
304           if (!fail)
305             {
306               size_t cnt;
307               /* Compare generated binary number with text representation
308                  in check file.  Ignore case of hex digits.  */
309               for (cnt = 0; cnt < 16; ++cnt)
310                 {
311                   if (TOLOWER (md5num[2 * cnt]) != bin2hex[md5buffer[cnt] >> 4]
312                       || (TOLOWER (md5num[2 * cnt + 1])
313                           != (bin2hex[md5buffer[cnt] & 0xf])))
314                     break;
315                 }
316               if (cnt != 16)
317                 fail = 1;
318             }
319
320           if (fail)
321             ++n_tests_failed;
322
323           if (!status_only)
324             {
325               printf ("%s: %s\n", filename, (fail ? _("FAILED") : _("OK")));
326               fflush (stdout);
327             }
328         }
329     }
330   while (!feof (checkfile_stream) && !ferror (checkfile_stream));
331
332   if (line)
333     free (line);
334
335   if (ferror (checkfile_stream))
336     {
337       error (0, 0, "%s: read error", checkfile_name);
338       return 1;
339     }
340
341   if (checkfile_stream != stdin && fclose (checkfile_stream) == EOF)
342     {
343       error (0, errno, "%s", checkfile_name);
344       return 1;
345     }
346
347   if (n_tests == 0)
348     {
349       /* Warn if no tests are found.  */
350       error (0, 0, _("%s: no properly formatted MD5 checksum lines found"),
351              checkfile_name);
352     }
353   else
354     {
355       if (!status_only)
356         {
357           if (n_tests_failed == 0)
358             {
359               printf (n_tests == 1
360                       ? _("the single computed checksum matched\n")
361                       : _("all %d computed checksums matched\n"), n_tests);
362             }
363           else
364             {
365               printf (n_tests == 1
366               ? _("WARNING: the single computed checksum did NOT match\n")
367               : _("WARNING: %d out of %d computed checksums did NOT match\n"),
368                       n_tests_failed, n_tests);
369             }
370         }
371     }
372
373   return ((n_tests > 0 && n_tests_failed == 0) ? 0 : 1);
374 }
375
376 int
377 main (int argc, char **argv)
378 {
379   unsigned char md5buffer[16];
380   int do_check = 0;
381   int do_version = 0;
382   int opt;
383   char **string = NULL;
384   char n_strings = 0;
385   size_t i;
386   size_t err = 0;
387
388   /* Text is default of the Plumb/Lankester format.  */
389   int binary = 0;
390
391   /* Setting values of global variables.  */
392   program_name = argv[0];
393
394   parse_long_options (argc, argv, "md5sum", version_string, usage);
395
396   while ((opt = getopt_long (argc, argv, "bctw", long_options, NULL))
397          != EOF)
398     switch (opt)
399       {
400       case 0:                   /* long option */
401         break;
402       case 1: /* --string */
403         {
404           if (string == NULL)
405             string = (char **) xmalloc ((argc - 1) * sizeof (char *));
406
407           if (optarg == NULL)
408             optarg = "";
409           string[n_strings++] = optarg;
410         }
411         break;
412       case 'b':
413         binary = 1;
414         break;
415       case 'c':
416         do_check = 1;
417         break;
418       case 2:
419         status_only = 1;
420         warn = 0;
421         break;
422       case 't':
423         binary = 0;
424         break;
425       case 'w':
426         status_only = 0;
427         warn = 1;
428         break;
429       default:
430         usage (EXIT_FAILURE);
431       }
432
433   if (do_version)
434     {
435       printf ("md5sum - %s\n", version_string);
436       exit (EXIT_SUCCESS);
437     }
438
439   if (n_strings > 0 && do_check)
440     {
441       error (0, 0,
442              _("the --string and --check options are mutually exclusive"));
443       usage (EXIT_FAILURE);
444     }
445
446   if (status_only && !do_check)
447     {
448       error (0, 0,
449        _("the --status option is meaningful only when verifying checksums"));
450       usage (EXIT_FAILURE);
451     }
452
453   if (warn && !do_check)
454     {
455       error (0, 0,
456        _("the --warn option is meaningful only when verifying checksums"));
457       usage (EXIT_FAILURE);
458     }
459
460   if (n_strings > 0)
461     {
462       if (optind < argc)
463         {
464           error (0, 0, _("no files may be specified when using --string"));
465           usage (EXIT_FAILURE);
466         }
467       for (i = 0; i < n_strings; ++i)
468         {
469           size_t cnt;
470           md5_buffer (string[i], strlen (string[i]), md5buffer);
471
472           for (cnt = 0; cnt < 16; ++cnt)
473             printf ("%02x", md5buffer[cnt]);
474
475           printf ("  \"%s\"\n", string[i]);
476         }
477     }
478   else if (do_check)
479     {
480       if (optind + 1 < argc)
481         {
482           error (0, 0,
483                  _("only one argument may be specified when using --check"));
484           usage (EXIT_FAILURE);
485         }
486
487       err = md5_check ((optind == argc) ? "-" : argv[optind], binary);
488     }
489   else
490     {
491       if (optind == argc)
492         argv[argc++] = "-";
493
494       for (; optind < argc; ++optind)
495         {
496           size_t i;
497           int fail;
498
499           fail = md5_file (argv[optind], binary, md5buffer);
500           err |= fail;
501           if (!fail)
502             {
503               for (i = 0; i < 16; ++i)
504                 printf ("%02x", md5buffer[i]);
505
506               printf (" %c%s\n", binary ? '*' : ' ', argv[optind]);
507             }
508         }
509     }
510
511   if (fclose (stdout) == EOF)
512     error (EXIT_FAILURE, errno, _("write error"));
513
514   if (have_read_stdin && fclose (stdin) == EOF)
515     error (EXIT_FAILURE, errno, _("standard input"));
516
517   exit (err == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
518 }