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