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