d761fd79b652d4f93bc8cd093d38661ff1700526
[skeinsum.git] / skein_cli.c
1 /* Copyright (C) 2014 2015 Jason Self <j@jxself.org>
2
3 This file is part of skeinsum.
4
5 skeinsum is free software: you can redistribute it and/or modify it 
6 under the terms of the GNU General Public License as published by 
7 the Free Software Foundation, either version 3 of the License, or 
8 (at your option) any later version.
9
10 skeinsum is distributed in the hope that it will be useful, but 
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with skeinsum. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "config.h"
20
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <dirent.h>
26 #include <getopt.h>
27 #include <malloc.h>
28 #include <math.h>
29 #include <glob.h>
30 #include <sys/stat.h>
31 #include <errno.h>
32 #include "SHA3api_ref.h"
33
34 #define WARN(msg, ...) fprintf(stderr, "skein%dsum: " msg, hashbitlen, ##__VA_ARGS__)
35
36 #define TRYHELP_GOODBYE() do { printf("Try 'skein%dsum --help' for more information.\n", hashbitlen); exit(1); } while(0)
37
38 typedef long long unsigned LLU;
39
40 extern const int hashbitlen;
41
42 #define skeinVersion "1.3"
43
44 const size_t input_minbufsize = 32 * 1024;
45 const size_t input_maxbufsize = 32 * 1024 * 1024;
46
47
48 enum
49 {
50   QUIET_OPTION = 11,
51   STATUS_OPTION,
52   TAG_OPTION
53 };
54
55 static struct option const long_options[] =
56 {
57   { "binary",  no_argument, NULL, 'b' },
58   { "check",   no_argument, NULL, 'c' },
59   { "quiet",   no_argument, NULL, QUIET_OPTION },
60   { "status",  no_argument, NULL, STATUS_OPTION },
61   { "text",    no_argument, NULL, 't' },
62   { "warn",    no_argument, NULL, 'w' },
63   { "tag",     no_argument, NULL, TAG_OPTION },
64   { "help",    no_argument, NULL, 'h' },
65   { "version", no_argument, NULL, 'V' },
66   { NULL, 0, NULL, 0 }
67 };
68
69 void hash2hexstr(unsigned char hash[], char str[])
70 {
71         int i;
72         for (i = 0; i < hashbitlen / 8; i++) {
73                 sprintf(&str[i * 2], "%02X", hash[i]);
74         }
75         str[i * 2 + 1] = '\0';
76 }
77
78 int HashFile(const char file_name[], char MsgDigest[], char mode)
79 {
80         int is_stdin = (strcmp(file_name, "-") == 0);
81
82         /* Try to get file info */
83         struct stat st;
84         st.st_size = 0;  /* ..needed when reading from stdio */
85         if (!is_stdin && stat(file_name, &st) < 0) {
86                 WARN("%s: cannot stat: %s\n", file_name, strerror(errno));
87                 return -1;
88         }
89
90         /* Get filesize */
91         size_t fsize = st.st_size;
92         if (fsize != st.st_size) {
93                 WARN("%s: SIZE WARNING: filesize %llu is too big for reading into memory!\n",
94                         file_name, (long long unsigned)st.st_size);
95         }
96
97         /* Open file */
98         FILE *fp_in = is_stdin ? stdin : fopen(file_name, (mode == 't' ? "r" : "rb") );
99         if (!fp_in) {
100                 WARN("%s: cannot open: %s\n", file_name, strerror(errno));
101                 return -1;
102         }
103
104         /* Allocate buffer */
105         size_t bufsize = fsize + 1;
106         if (bufsize < input_minbufsize)
107                 bufsize = input_minbufsize;
108
109         char *readbuf = malloc (bufsize);
110         if (!readbuf && bufsize > input_maxbufsize) {  /* ..Try to get contents by smaller portions */
111                 bufsize = input_maxbufsize;
112                 readbuf = malloc (bufsize);
113         }
114
115         if (!readbuf) {
116                 WARN("%s: MEM FAILED: error %s\n", file_name, strerror(errno));
117                 if (!is_stdin)
118                         fclose(fp_in);
119                 return -1;
120         }
121
122         /* Read contents */
123         size_t readpos = 0, total_readed = 0;
124
125         while (1) {
126                 size_t maxread = bufsize - readpos;
127                 size_t readed = fread(readbuf + readpos, 1, maxread, fp_in);
128                 if (readed > 0 && readed <= maxread)
129                         total_readed += readed;
130                 if (readed != maxread)
131                         break;
132                 if (getenv("SKEIN_DEBUG"))
133                         printf("DEBUG: bufsize=%llu (0x%llx), readpos=%llu (0x%llx), maxread=%llu (0x%llx), total=%llu (0x%llx)\n",
134                                 (LLU)bufsize, (LLU)bufsize, (LLU)readpos, (LLU)readpos,
135                                 (LLU)maxread, (LLU)maxread, (LLU)total_readed, (LLU)total_readed);
136                 char *newbuf = NULL;
137                 if (bufsize * 2 > bufsize)  /* ..check overflow */
138                         newbuf = realloc(readbuf, bufsize * 2);
139                 if (!newbuf) {
140                         if (total_readed < st.st_size) {
141                                 WARN("%s: MEM WARNING: %llu bytes only readed from %llu\n",
142                                         file_name, (LLU)total_readed, (LLU)st.st_size);
143                         } else {
144                                 WARN("%s: MEM WARNING: %llu bytes only readed.\n",
145                                         file_name, (LLU)total_readed);
146                         }
147                         break;
148                 }
149                 readbuf = newbuf;
150                 readpos += readed;
151                 bufsize *= 2;
152         }
153
154         if (!is_stdin)
155                 fclose(fp_in);
156
157         if (!is_stdin && total_readed < st.st_size && total_readed < bufsize) {
158                 WARN("%s: READ WARNING: filesize=%llu, readed=%llu, error %d, %s\n",
159                         file_name, (LLU)st.st_size, (LLU)total_readed, errno, strerror(errno));
160         }
161
162         if (getenv("SKEIN_DUMP")) {
163                 printf("DEBUG: file=%s, bufsize=%llu (0x%llx), total_readed=%llu, buf:\n",
164                         file_name, (LLU)bufsize, (LLU)bufsize, (LLU)total_readed);
165                 fwrite(readbuf, 1, total_readed, stdout);
166         }
167
168         unsigned char output[hashbitlen/4];
169         Hash(hashbitlen, (unsigned char*) readbuf, total_readed, output);
170
171         free(readbuf);
172
173         hash2hexstr(output, MsgDigest);
174
175         return 1;
176 }
177
178 int PrintFileHash(const char filename[], int tag, char mode)
179 {
180         char MsgDigest[hashbitlen/2];
181         if (HashFile(filename, MsgDigest, mode) < 0)
182                 return -1;
183         if (tag == 1) {
184                 printf("skein%d_v%s (%s) = %s\n", hashbitlen, skeinVersion, filename, MsgDigest);
185         } else {
186                 printf("%s %s%s\n", MsgDigest, (mode == 'b' ? "*" : ""), filename);
187         }
188         return 1;
189 }
190
191 /* Return: -1 = Error, 0 = Mismatch, 1 = Match */
192 int HashMatch(const char StoredDigest[], const char *filename, int quiet)
193 {
194         char mode = 't';
195         if (filename[0] == '*') {
196                 filename++;
197                 mode = 'b';
198         }
199         
200         char MsgDigest[hashbitlen/2];
201         if (HashFile(filename, MsgDigest, mode) < 0)
202                 return -1;
203
204         if (strcmp(MsgDigest, StoredDigest)) {
205                 printf("%s: FAILED\n", filename);
206                 return 0;
207         }
208
209         if (quiet > 0)
210                 printf("%s: OK\n", filename);
211         return 1;
212 }
213
214 int isProper(const char MsgDigest[])
215 {
216         int len = strlen(MsgDigest);
217         if (len != (hashbitlen / 4))
218                 return 0;
219         int index = 0;
220         for (index = 0; index < len; index++)
221         {
222                 char c = MsgDigest[index];
223                 if (c >= '0' && c <= '9') continue;
224                 if (c >= 'A' && c <= 'F') continue;
225                 return 0;
226         }
227
228         return 1;
229 }
230
231
232 int decomposeHashLine(char hash[], char MsgDigest_tmp[], char file_tmp[])
233 {
234         char c = 0;
235         int i = 0 , j =0;
236         int isTagFile = 0;
237         char alg[20];
238         char tmp[1000];
239         while(((c = hash[i])!=' ')&&((c = hash[i])!='_'))
240         {
241                 tmp[i] = hash[i];
242                 i++;
243         }
244         tmp[i] = 0;
245
246         sprintf(alg,"skein%d",hashbitlen);
247         if(!strcmp(alg,tmp))
248         {
249                 isTagFile = 1;
250         }
251
252         if(isTagFile == 0)
253         {
254                 strcpy(MsgDigest_tmp,tmp);
255                 i++;
256                 while((c = hash[i])!= '\n')
257                 {
258                         file_tmp[j] = hash[i];
259                         i++;
260                         j++;
261                 }
262                 file_tmp[j] = 0;
263         }
264         else if((hash[i]=='_')&&(hash[i+1]=='v'))
265         {
266                 i = i + 2;
267                 j = 0;
268                 char version[5];
269                 while((c = hash[i])!=' ')
270                 {
271                         version[j] = hash[i];
272                         i++;
273                         j++;
274                 }
275                 version[i] = 0;
276                 float vers = 0, skeinVers = 0;
277                 sscanf(version,"%f",&vers);
278                 sscanf(skeinVersion,"%f",&skeinVers);
279
280                 j = 0;
281                 i = i + 2;
282                 while((c = hash[i])!=')')
283                 {
284                         file_tmp[j] = hash[i];
285                         i++;
286                         j++;
287                 }
288                 file_tmp[j] = 0;
289
290                 i = i + 4;
291                 j = 0;
292                 while((c = hash[i])!='\n')
293                 {
294                         MsgDigest_tmp[j] = hash[i];
295                         i++;
296                         j++;
297                 }
298                 MsgDigest_tmp[j] = 0;
299
300                 if(skeinVers < vers)
301                 {//version newer than mine
302                         return (-1);
303                 }
304                 else if (skeinVers > vers)
305                 {//going to use older version than mine
306                         return (0) ;
307                 }
308                 else
309                 { //versions match
310                         return (1);
311                 }
312         }
313         return 1;
314
315 }
316
317 /* Return: -1 = some errors/mismatches, 1 = all ok */
318 int VerifyHashesFromFile(FILE *fp, int status, int warn, int quiet)
319 {
320         char hash[PATH_MAX + hashbitlen/4 + 4];
321         char MsgDigest_tmp[hashbitlen/2];
322         int NoMatch = 0, NotProper = 0, Computed = 0;
323         int line = 0;
324
325         while (fgets(hash, sizeof(hash)-1, fp))
326         {
327                 char file_tmp[PATH_MAX];
328                 line ++;
329                 Computed++;
330                 int hashVersion = decomposeHashLine(hash,MsgDigest_tmp,file_tmp);
331                 if (hashVersion == -1)
332                 {
333                         WARN("%s is using newer version of skein%d algorithm\n"
334                              "You should update your algorithm\n",
335                              file_tmp, hashbitlen);
336                         continue;
337                 }
338                 else if (hashVersion == 0)
339                 {
340                         WARN("%s is using an older version of skein%d algorithm\n"
341                              "You should use the older algorithm\n",
342                              file_tmp, hashbitlen);
343                         continue;
344                 }
345                 else if (!isProper(MsgDigest_tmp))
346                 {
347                         if(status != 1 && warn == 1)
348                                 WARN("%s: %d: improperly formatted skein%d checksum line\n",file_tmp,line,hashbitlen);
349                         NotProper ++;
350                         NoMatch ++;
351                 }
352                 else if (HashMatch(MsgDigest_tmp, file_tmp, quiet) <= 0)
353                 {
354                         NoMatch++;
355                 }
356         }
357         if(NoMatch)
358         {
359                 WARN("WARNING: %d of %d computed checksums did NOT match\n",
360                         NoMatch, Computed);
361         }
362         if(NotProper)
363         {
364                 WARN("WARNING: %d line is improperly formatted\n", NotProper);
365         }
366         return (NotProper || NoMatch) ? -1 : 1;
367 }
368
369 void PrintStringHash(const char *s)
370 {
371         unsigned char output[hashbitlen/4];
372         char digest[hashbitlen/4 + 1];
373         Hash(hashbitlen, s, strlen(s), output);
374         hash2hexstr(output, digest);
375         printf("%s -%s\n", digest, s);
376 }
377
378 void print_version(void)
379 {
380         printf("skein%dsum 1.0\n", hashbitlen);
381         printf("License GPLv3+: GNU GPL version 3 or later\n");
382         printf("<http://gnu.org/licenses/gpl.html>\n");
383         printf("This is free software: you are free to change and redistribute it.\n");
384         printf("There is NO WARRANTY, to the extent permitted by law.\n");
385         exit(1);
386 }
387
388 void print_usage(void)
389 {
390         printf("Usage: skein%dsum [OPTION]... [FILE]...\n",hashbitlen);
391         printf("Print or check skein (%d-bit) checksums.\n",hashbitlen);
392         printf("With no FILE, or when FILE is -, read standard input.\n"
393                "\n"
394                "-b, --binary     read in binary mode\n"
395                "-c, --check      read skein sums from the FILEs and check them\n"
396                "--tag            create a BSD-style checksum\n"
397                "-t, --text       read in text mode (default)\n"
398                "-0               hash strings from command line\n"
399                "\n"
400                "The following three options are useful only when verifying checksums:\n"
401                "--quiet          don't print OK for each successfully verified file\n"
402                "--status         don't output anything, status code shows success\n"
403                "-w, --warn       warn about improperly formatted checksum lines\n"
404                "\n"
405                "--strict         with --check, exit non-zero for any invalid input\n"
406                "-h, --help       display this help and exit\n"
407                "-V, --version    output version information and exit\n"
408                "\n"
409                "The sums are computed as described in version 1.3 of the Skein\n"
410                "specification. When checking, the input should be a former output of\n"
411                "this program. The default mode is to print a line with checksum, a\n"
412                "character indicating input mode ('*' for binary, space for text), and\n"
413                "name for each FILE.\n");
414         exit(1);
415 }
416
417 int is_goodfile(const char filename[])
418 {
419         if (!strcmp(filename, "-"))
420                 return 1;
421
422         struct stat s;
423
424         if (stat(filename, &s) < 0) {
425                 WARN("%s: no such file or directory\n", filename);
426                 return 0;
427         }
428
429         if (S_ISREG (s.st_mode)) return 1;
430         if (S_ISCHR (s.st_mode)) return 1;
431         if (S_ISBLK (s.st_mode)) return 1;
432         if (S_ISLNK (s.st_mode)) return 1;
433         if (S_ISFIFO(s.st_mode)) return 1;
434
435         if (S_ISDIR (s.st_mode)) {
436                 WARN("%s: is a directory\n", filename);
437                 return 0;
438         }
439
440         WARN("%s: WARNING: unknown filetype 0x%Xu\n", filename, s.st_mode);
441         return 1;  /* try it as good */
442 }
443
444
445 int main(int argc, char** argv)
446 {
447         int first_file = argc;
448         int binary = -1,
449                  check = -1,
450                  quiet = -1,
451                  warn = -1,
452                  status = -1,
453                  tag = -1,
454                  hashString = -1;
455
456         int errorFound = 0;
457         int opt = 0;
458 /*****************************************************************************************
459  ************************************* GETTING DATA ***********************************
460  *****************************************************************************************/
461         while ((opt = getopt_long (argc, argv, "hVbctw0", long_options, NULL)) != -1)
462         {
463                 switch (opt) {
464                         case '0'            : hashString = 1;  break;
465                         case 'b'            : binary     = 1;  break;
466                         case 't'            : binary     = 0;  break;
467                         case 'c'            : check      = 1;  break;
468                         case 'w'            : warn       = 1;  break;
469                         case QUIET_OPTION   : quiet      = 1;  break;
470                         case STATUS_OPTION  : status     = 1;  break;
471                         case TAG_OPTION     : tag        = 1;  break;
472                         
473                         case 'h'            : print_usage();   /* ..never returns */
474                         case 'V'            : print_version(); /* ..never returns */
475
476                         default: TRYHELP_GOODBYE();
477                 }
478         }
479
480         first_file = optind;
481
482 /*****************************************************************************************
483  ************************************* PROCESSING DATA ***********************************
484  *****************************************************************************************/
485
486         if (hashString > 0)
487         {
488                 int n = first_file;
489                 if (n >= argc) {
490                         WARN("command line should contain strings for hashing\n");
491                         TRYHELP_GOODBYE();
492                 }
493                 while (n < argc)
494                         PrintStringHash(argv[n++]);
495                 return 0;
496         }
497
498         if (check < 0)   /* READ FILES, GENERATE CHECKSUMS AND PRINT TO STDOUT.. */
499         {
500                 if (quiet == 1 || warn == 1 || status == 1)
501                 {
502                         if(quiet == 1)
503                                 WARN("the --quiet option is meaningful only when verifying checksums\n");
504                         if(status ==1)
505                                 WARN("the --status option is meaningful only when verifying checksums\n");
506                         if(warn == 1)
507                                 WARN("the --warn option is meaningful only when verifying checksums\n");
508                         TRYHELP_GOODBYE();
509                 }
510
511                 char mode = binary > 0 ? 'b' : 't';
512
513                 int file_index;
514                 for (file_index = first_file; file_index < argc; file_index++)
515                 {
516                         const char *filename = argv[file_index];
517                         if (!is_goodfile(filename)) {
518                                 errorFound++;
519                                 continue;
520                         }
521
522                         if (binary > 0 && hashString == 1) {
523                                 WARN("%s: No such file or directory\n", filename);
524                                 continue;
525                         }
526
527                         if (PrintFileHash(filename, tag, mode) < 0)
528                                 errorFound++;
529                 }
530
531                 if (first_file >= argc)  /* no files in cmdline? hash stdin.. */
532                         if (PrintFileHash("-", tag, mode) < 0)
533                                 errorFound++;
534
535                 return (errorFound ? 1 : 0);
536         }
537
538         if (check == 1)   /* READ LISTFILES, GENERATE HASHES, COMPARE WITH STORED, PRINT RESULT */
539         {
540                 if (tag == 1 || binary >= 0)
541                 {
542                         if (tag == 1)
543                                 WARN("the --tag option is meaningless when verifying checksums\n");
544                         if (binary >= 0)
545                                 WARN("the --text and --binary options are meaningless when verifying checksums\n");
546                         TRYHELP_GOODBYE();
547                 }
548
549                 int file_index;
550                 for (file_index = first_file; file_index < argc; file_index++)
551                 {
552                         const char *filename = argv[file_index];
553                         if (!is_goodfile(filename)) {
554                                 errorFound++;
555                                 continue;
556                         }
557
558                         FILE *fp = (strcmp(filename, "-") == 0) ? stdin : fopen(filename, "r");
559                         if (!fp) {
560                                 errorFound++;
561                                 WARN("%s: %s\n", filename, strerror(errno));
562                                 continue;
563                         }
564                         if (VerifyHashesFromFile(fp, status, warn, quiet) < 0)
565                                 errorFound++;
566                         if (fp != stdin)
567                                 fclose(fp);
568                 }
569
570                 if (first_file >= argc)  /* no files in cmdline? verify stdin.. */
571                         if (VerifyHashesFromFile(stdin, status, warn, quiet) < 0)
572                                 errorFound++;
573
574                 return (errorFound ? 1 : 0);
575         }
576
577         return 1;
578 }