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