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