+/* Copyright (C) 2014 2015 Jason Self <j@jxself.org>
+
+This file is part of skeinsum.
+
+skeinsum is free software: you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+skeinsum is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with skeinsum. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <getopt.h>
+#include <malloc.h>
+#include <math.h>
+#include <glob.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include "SHA3api_ref.h"
+
+extern const int hashbitlen;
+
+#define MaxNmberFiles 10
+#define skeinVersion "1.3"
+const size_t input_minbufsize = 32 * 1024;
+const size_t input_maxbufsize = 32 * 1024 * 1024;
+
+
+char invalidOption = 0;
+
+enum
+{
+ QUIET_OPTION = 11,
+ STATUS_OPTION,
+ TAG_OPTION,
+ HELP_OPTION,
+ VERSION_OPTION
+};
+
+static struct option const long_options[] =
+{
+ { "binary", no_argument, NULL, 'b' },
+ { "check", no_argument, NULL, 'c' },
+ { "quiet", no_argument, NULL, QUIET_OPTION },
+ { "status", no_argument, NULL, STATUS_OPTION },
+ { "text", no_argument, NULL, 't' },
+ { "warn", no_argument, NULL, 'w' },
+ { "tag", no_argument, NULL, TAG_OPTION },
+ { "help", no_argument, NULL, HELP_OPTION },
+ { "version", no_argument, NULL, VERSION_OPTION },
+ { NULL, 0, NULL, 0 }
+};
+
+void printHexMsg(unsigned char *ptr, int size, char result[])
+{
+ int i = 0;
+ int j = 0;
+ char High, Low;
+ for (i = 0 ; i < size ; i ++)
+ {
+ High = ptr[i]>>4;
+ Low = ptr[i]&0x0F;
+
+ if (High >= 0xA && High <= 0xF)
+ High += 0x37;
+ else if(High >= 0x0 && High <= 0x9)
+ High += 0x30;
+
+ if (Low >= 0xA && Low <= 0xF)
+ Low += 0x37;
+ else if(Low >= 0x0 && Low <= 0x9)
+ Low += 0x30;
+
+ result[j++] = High;
+ result[j++] = Low;
+ }
+ result[i] = 0;
+}
+
+int HashWithMode(char file_name[], char MsgDigest[], char mode)
+{
+ /* Try to get file info */
+ struct stat st;
+ if (stat(file_name, &st) < 0) {
+ printf("skein%dsum: %s: STAT FAILED: %s\n", hashbitlen, file_name, strerror(errno));
+ return -1;
+ }
+
+ /* Get filesize */
+ size_t fsize = st.st_size;
+ if (fsize != st.st_size) {
+ printf("skein%dsum: %s: SIZE WARNING: filesize %llu is too big for reading into memory!\n",
+ hashbitlen, file_name, (long long unsigned)st.st_size);
+ }
+
+ /* Open file */
+ FILE *fp_in = fopen(file_name, (mode == 't' ? "r" : "rb") );
+ if (!fp_in) {
+ printf("skein%dsum: %s: OPEN FAILED: %s\n", hashbitlen, file_name, strerror(errno));
+ return -1;
+ }
+
+ /* Allocate buffer */
+ size_t bufsize = fsize + 1;
+ if (bufsize < input_minbufsize)
+ bufsize = input_minbufsize;
+
+ char *readbuf = malloc (bufsize);
+ if (!readbuf && bufsize > input_maxbufsize) { /* ..Try to get contents by smaller portions */
+ bufsize = input_maxbufsize;
+ readbuf = malloc (bufsize);
+ }
+
+ if (!readbuf) {
+ printf("skein%dsum: %s: MEM FAILED: %s\n", hashbitlen, file_name, strerror(errno));
+ fclose(fp_in);
+ return -1;
+ }
+
+ /* Read contents */
+ size_t readpos = 0, readed, total_readed = 0;
+
+ while (1) {
+ size_t maxread = bufsize - readpos;
+ readed = fread(readbuf, 1, maxread, fp_in);
+ if (readed > 0 && readed <= maxread)
+ total_readed += readed;
+ if (readed != maxread)
+ break;
+ if (getenv("SKEIN_DEBUG"))
+ printf("DEBUG: bufsize=%u (0x%x), readpos=%u (0x%x), maxread=%u (0x%x), total=%u (0x%x)\n",
+ bufsize, bufsize, readpos, readpos, maxread, maxread, total_readed, total_readed);
+ char *newbuf = NULL;
+ if (bufsize * 2 > bufsize) /* ..check overflow */
+ newbuf = realloc(readbuf, bufsize * 2);
+ if (!newbuf) {
+ printf("skein%dsum: %s: MEM WARNING: %u bytes only readed from %llu\n",
+ hashbitlen, file_name, total_readed, (long long unsigned)st.st_size);
+ break;
+ }
+ readbuf = newbuf;
+ readpos += readed;
+ bufsize *= 2;
+ }
+
+ if (total_readed < st.st_size && total_readed < bufsize) {
+ printf("skein%dsum: %s: READ WARNING: filesize=%llu, readed=%u, %s\n",
+ hashbitlen, file_name, (long long unsigned)st.st_size, total_readed, strerror(errno));
+ }
+
+ fclose(fp_in);
+
+ unsigned char output[hashbitlen/4];
+ Hash(hashbitlen, (unsigned char*) readbuf, total_readed, output);
+
+ free(readbuf);
+
+ printHexMsg(output,hashbitlen/4,MsgDigest);
+
+ return 1;
+}
+
+int HashTextMode(char file_name[], char MsgDigest[])
+{
+ return HashWithMode(file_name, MsgDigest, 't');
+}
+
+int HashBinaryMode(char file_name[], char MsgDigest[])
+{
+ return HashWithMode(file_name, MsgDigest, 'b');
+}
+
+int HashStringMode(char Msg[],char MsgDigest[])
+{
+ unsigned char output[hashbitlen/4];
+ Hash(hashbitlen,(unsigned char*) Msg,sizeof(Msg),output);
+ printHexMsg(output,hashbitlen/4,MsgDigest);
+ return 1;
+}
+
+int isProper(char MsgDigest[])
+{
+ if ((strlen(MsgDigest) - hashbitlen/4) != 0)
+ return 0;
+ int index = 0;
+ char c = 0;
+ for(index = 0; index < strlen(MsgDigest);index++)
+ {
+ c = MsgDigest[index];
+ if(!(( c >= '0' && c <= '9' ) || ( c >= 'A' && c <= 'F')))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int decomposeHashLine(char hash[], char MsgDigest_tmp[], char file_tmp[])
+{
+ char c = 0;
+ int i = 0 , j =0;
+ int isTagFile = 0;
+ char alg[20];
+ char tmp[1000];
+ while(((c = hash[i])!=' ')&&((c = hash[i])!='_'))
+ {
+ tmp[i] = hash[i];
+ i++;
+ }
+ tmp[i] = 0;
+
+ sprintf(alg,"skein%d",hashbitlen);
+ if(!strcmp(alg,tmp))
+ {
+ isTagFile = 1;
+ }
+
+ if(isTagFile == 0)
+ {
+ strcpy(MsgDigest_tmp,tmp);
+ i++;
+ while((c = hash[i])!= '\n')
+ {
+ file_tmp[j] = hash[i];
+ i++;
+ j++;
+ }
+ file_tmp[j] = 0;
+ }
+ else if((hash[i]=='_')&&(hash[i+1]=='v'))
+ {
+ i = i + 2;
+ j = 0;
+ char version[5];
+ while((c = hash[i])!=' ')
+ {
+ version[j] = hash[i];
+ i++;
+ j++;
+ }
+ version[i] = 0;
+ float vers = 0, skeinVers = 0;
+ sscanf(version,"%f",&vers);
+ sscanf(skeinVersion,"%f",&skeinVers);
+
+ j = 0;
+ i = i + 2;
+ while((c = hash[i])!=')')
+ {
+ file_tmp[j] = hash[i];
+ i++;
+ j++;
+ }
+ file_tmp[j] = 0;
+
+ i = i + 4;
+ j = 0;
+ while((c = hash[i])!='\n')
+ {
+ MsgDigest_tmp[j] = hash[i];
+ i++;
+ j++;
+ }
+ MsgDigest_tmp[j] = 0;
+
+ if(skeinVers < vers)
+ {//version newer than mine
+ return (-1);
+ }
+ else if (skeinVers > vers)
+ {//going to use older version than mine
+ return (0) ;
+ }
+ else
+ { //versions match
+ return (1);
+ }
+ }
+ return 1;
+
+}
+
+void print_version(void)
+{
+ printf("skein%dsum 1.0\n", hashbitlen);
+ printf("License GPLv3+: GNU GPL version 3 or later\n");
+ printf("<http://gnu.org/licenses/gpl.html>\n");
+ printf("This is free software: you are free to change and redistribute it.\n");
+ printf("There is NO WARRANTY, to the extent permitted by law.\n");
+ exit(1);
+}
+
+void print_usage(void)
+{
+ printf("Usage: skein%dsum [OPTION]... [FILE]...\n",hashbitlen);
+ printf("Print or check skein (%d-bit) checksums.\n",hashbitlen);
+ printf("With no FILE, or when FILE is -, read standard input.\n");
+ printf("\n");
+ printf("-b, --binary read in binary mode\n");
+ printf("-c, --check read skein sums from the FILEs and check them\n");
+ printf("--tag create a BSD-style checksum\n");
+ printf("-t, --text read in text mode (default)\n");
+ printf("\n");
+ printf("The following three options are useful only when verifying checksums:\n");
+ printf("--quiet don't print OK for each successfully verified file\n");
+ printf("--status don't output anything, status code shows success\n");
+ printf("-w, --warn warn about improperly formatted checksum lines\n");
+ printf("\n");
+ printf("--strict with --check, exit non-zero for any invalid input\n");
+ printf("--help display this help and exit\n");
+ printf("--version output version information and exit\n");
+ printf("\n");
+ printf("The sums are computed as described in version 1.3 of the Skein\n");
+ printf("specification. When checking, the input should be a former output of\n");
+ printf("this program. The default mode is to print a line with checksum, a\n");
+ printf("character indicating input mode ('*' for binary, space for text), and\n");
+ printf("name for each FILE.\n");
+ exit(1);
+}
+
+
+
+int main(int argc, char** argv)
+{
+ char MsgDigest[hashbitlen/2];
+ char *list_files[MaxNmberFiles];
+ int number_files = 0;
+ int binary = -1,
+ check = -1,
+ quiet = -1,
+ warn = -1,
+ status = -1,
+ tag = -1,
+ hashString = -1;
+
+ int errorFound = 0;
+ int opt = 0;
+/*****************************************************************************************
+ ************************************* GETTING DATA ***********************************
+ *****************************************************************************************/
+ while ((opt = getopt_long (argc, argv, "bctw", long_options, NULL)) != -1)
+ {
+ switch (opt) {
+ case 0 : hashString = 1; break;
+ case 'b' : binary = 1; break;
+ case 't' : binary = 0; break;
+ case 'c' : check = 1; break;
+ case 'w' : warn = 1; break;
+ case QUIET_OPTION : quiet = 1; break;
+ case STATUS_OPTION : status = 1; break;
+ case TAG_OPTION : tag = 1; break;
+
+ case HELP_OPTION : print_usage(); /* ..never returns */
+ case VERSION_OPTION : print_version(); /* ..never returns */
+
+ default:
+ printf("Try `skein%dsum --help' for more information.\n",hashbitlen);
+ exit(1);
+ }
+ }
+
+ for (; optind < argc; ++optind)
+ {
+ struct stat s;
+ if (stat(argv[optind], &s) < 0)
+ {
+ printf("skein%dsum: %s: no such file or directory\n", hashbitlen, argv[optind]);
+ errorFound = 1;
+ }
+ else if (s.st_mode & (S_IFREG|S_IFBLK)) /* ..regular file or block device? */
+ {
+ if (number_files < MaxNmberFiles) {
+ list_files[number_files++] = argv[optind];
+ } else {
+ printf("skein%dsum: %s: ignore because filelist is too long\n",
+ hashbitlen, argv[optind]);
+ }
+ }
+ else if (s.st_mode & S_IFDIR)
+ {
+ printf("skein%dsum: %s: is a directory\n",hashbitlen,argv[optind]);
+ errorFound = 1;
+ }
+ else
+ {
+ printf("skein%dsum: %s: wrong filetype 0x%Xu\n",
+ hashbitlen, argv[optind], s.st_mode);
+ errorFound = 1;
+ }
+ }
+
+/*****************************************************************************************
+ ************************************* PROCESSING DATA ***********************************
+ *****************************************************************************************/
+ if(argc > 1 && binary == 1)
+ {
+ if (check == 1 || quiet == 1 || warn == 1 || status == 1)
+ {
+ printf("skein%dsum: the --binary and --text options are meaningless when verifying checksums\n",hashbitlen);
+ printf("Try 'skein%dsum --help' for more information.\n",hashbitlen);
+ exit(0);
+ }
+ if(number_files > 0)
+ {
+ int index_files = 0;
+ while(index_files < number_files)
+ {
+ if(hashString == 1)
+ {
+ printf("skein%dsum: %s: No such file or directory\n",hashbitlen,list_files[index_files]);
+ index_files++;
+ continue;
+ }
+ if(HashBinaryMode(list_files[index_files],MsgDigest)!=-1)
+ {
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (%s) = %s\n",hashbitlen,skeinVersion,list_files[index_files],MsgDigest);
+ }
+ else
+ {
+ printf("%s ",MsgDigest);
+ printf("*%s\n",list_files[index_files]);
+ }
+ }
+ index_files++;
+ }
+ }
+ else if(errorFound != 1)
+ { // read stdin for strings
+ char stri[100];
+ char longstring[1000];
+ longstring[0] = 0;
+ while((fgets(stri,100,stdin))!=NULL)
+ {
+ strcat(longstring,stri);
+ }
+ HashStringMode(longstring,MsgDigest);
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (-) = %s\n",hashbitlen,skeinVersion,MsgDigest);
+ }
+ else
+ {
+ printf("%s *-\n",MsgDigest);
+ }
+ }
+ }
+
+ else if (argc > 1 && binary == 0) // Text Mode
+ {
+ if (check == 1 || quiet == 1 || warn == 1 || status == 1)
+ {
+ printf("skein%dsum: the --binary and --text options are meaningless when verifying checksums\n",hashbitlen);
+ printf("Try 'skein%dsum --help' for more information.\n",hashbitlen);
+ exit(0);
+ }
+ if(number_files > 0)
+ {
+ int index_files = 0;
+ while(index_files < number_files)
+ {
+ if(HashTextMode(list_files[index_files],MsgDigest)!=-1)
+ {
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (%s) = %s\n",hashbitlen,skeinVersion,list_files[index_files],MsgDigest);
+ }
+ else
+ {
+ printf("%s ",MsgDigest);
+ printf("%s\n",list_files[index_files]);
+ }
+ }
+ index_files++;
+ }
+ }
+ else if(errorFound != 1)
+ { // read stdin for strings
+ char stri[100];
+ char longstring[1000];
+ longstring[0] = 0;
+ while((fgets(stri,100,stdin))!=NULL)
+ {
+ strcat(longstring,stri);
+ }
+ HashStringMode(longstring,MsgDigest);
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (-) = %s\n",hashbitlen,skeinVersion,MsgDigest);
+ }
+ else
+ {
+ printf("%s -\n",MsgDigest);
+ }
+ }
+ }
+
+ else if (argc > 1 && binary == -1)
+ {
+ if(check == -1)
+ {// hashing stdin entries
+ if(quiet == 1 || status == 1 || warn == 1)
+ {
+ if(quiet == 1)
+ printf("skein%dsum: the --quiet option is meaningful only when verifying checksums\n",hashbitlen);
+ if(status ==1)
+ printf("skein%dsum: the --status option is meaningful only when verifying checksums\n",hashbitlen);
+ if(warn == 1)
+ printf("skein%dsum: the --warn option is meaningful only when verifying checksums\n",hashbitlen);
+
+ printf("Try 'skein%dsum --help' for more information.\n",hashbitlen);
+ exit(1);
+ }
+ if(number_files > 0)
+ {// hashing files
+ int index_files = 0;
+ while(index_files < number_files)
+ {
+ if(HashTextMode(list_files[index_files],MsgDigest)!=-1)
+ {
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (%s) = %s\n",hashbitlen,skeinVersion,list_files[index_files],MsgDigest);
+ }
+ else
+ {
+ printf("%s ",MsgDigest);
+ printf("%s\n",list_files[index_files]);
+ }
+ }
+ index_files++;
+ }
+ }
+ else if(errorFound != 1)
+ { // hasing strings read from stdin
+ char stri[100];
+ char longstring[1000]; /* TODO!!! Fix buffer overflow!!! */
+ longstring[0] = 0;
+ while((fgets(stri,100,stdin))!=NULL)
+ {
+ strcat(longstring,stri);
+ }
+ HashStringMode(longstring,MsgDigest);
+ if(tag == 1)
+ {
+ printf("skein%d_v%s (-) = %s\n",hashbitlen,skeinVersion,MsgDigest);
+ }
+ else
+ {
+ printf("%s -\n",MsgDigest);
+ }
+ }
+ }
+ else if(check == 1)
+ {
+ if(tag == 1)
+ {
+ printf("skein%dsum: the --tag option is meaningless when verifying checksums\n",hashbitlen);
+ printf("Try 'skein%dsum --help' for more information\n",hashbitlen);
+ exit(1);
+ }
+ int index_files = 0;
+ while(index_files < number_files)
+ {
+ FILE *fp;
+ char hash[500], file_name[100], file_tmp[100], MsgDigest_tmp[hashbitlen/2];
+ int NoMatch = 0, NotProper = 0, Computed = 0;
+ strcpy(file_name,list_files[index_files]);
+ //show everything
+ int line = 0;
+ fp = fopen(file_name,"r");
+ if(fp!=NULL)
+ {
+ while(fgets(hash,500,fp)!=NULL)
+ {
+ line ++;
+ Computed++;
+ unsigned int hashVersion = decomposeHashLine(hash,MsgDigest_tmp,file_tmp);
+ if(hashVersion == -1)
+ {
+ printf("skein%d: %s is using newer version of skein%d algorithm\n",hashbitlen,file_tmp,hashbitlen);
+ printf("You should update your algorithm\n");
+ continue;
+ }
+ else if( hashVersion == 0)
+ {
+ printf("skein%d: %s is using an older version of skein%d algorithm\n",hashbitlen,file_tmp,hashbitlen);
+ printf("You should use the older algorithm\n");
+ continue;
+ }
+ else
+ {
+ if(!isProper(MsgDigest_tmp))
+ {
+ if(status != 1 && warn == 1)
+ {
+ printf("skein%dsum: %s: %d: improperly formatted skein%d checksum line\n",hashbitlen,file_tmp,line,hashbitlen);
+ }
+ NotProper ++;
+ NoMatch ++;
+ continue;
+ }
+ if(file_tmp[0] == '*')
+ {
+ int index_file_tmp = 0;
+ for( ; file_tmp[index_file_tmp+1]!=0 ; index_file_tmp++)
+ {
+ file_tmp[index_file_tmp] = file_tmp[index_file_tmp + 1];
+ }
+ file_tmp[index_file_tmp] = 0;
+ HashBinaryMode(file_tmp,MsgDigest);
+ }
+ else
+ {
+ HashTextMode(file_tmp,MsgDigest);
+ }
+
+ if(!strcmp(MsgDigest,MsgDigest_tmp))
+ {
+ if(quiet != 1)
+ {
+ printf("%s: OK\n",file_tmp);
+ }
+ }
+ else
+ {
+ printf("%s: FAILED\n",file_tmp);
+ NoMatch ++;
+ }
+ }
+ }
+ if(NoMatch)
+ {
+ printf("skein%dsum: WARNING: %d of %d computed checksums did NOT match\n",
+ hashbitlen,NoMatch,Computed);
+ }
+ if(NotProper)
+ {
+ printf("skein%dsum: WARNING: %d line is improperly formatted\n",hashbitlen,NotProper);
+ }
+ }
+ else
+ {
+ printf("skein%dsum: %s: No such file or directory\n",hashbitlen,file_name);
+ }
+ index_files++;
+
+ }
+
+ if(number_files == 0)
+ {
+ char longstring[1000];
+ char file_name[30];
+ char MsgDigest_tmp[hashbitlen/2];
+ int Computed = 0, NotProper = 0, NoMatch = 0 , line = 0;
+ while((fgets(longstring,1000,stdin))!=NULL)
+ {
+ Computed++;
+ line ++;
+ decomposeHashLine(longstring,MsgDigest_tmp,file_name);
+ if(!isProper(MsgDigest_tmp))
+ {
+ if(status != 1 && warn == 1)
+ {
+ printf("skein%dsum: %s: %d: improperly formatted skein%d checksum line\n",hashbitlen,file_name,line,hashbitlen);
+ }
+ NotProper ++;
+ NoMatch ++;
+ continue;
+ }
+ if(file_name[0] == '*')
+ {
+ int index_file_tmp = 0;
+ for( ; file_name[index_file_tmp+1]!=0 ; index_file_tmp++)
+ {
+ file_name[index_file_tmp] = file_name[index_file_tmp + 1];
+ }
+ file_name[index_file_tmp] = 0;
+ HashBinaryMode(file_name,MsgDigest);
+ }
+ else
+ {
+ HashTextMode(file_name,MsgDigest);
+ }
+
+ if(!strcmp(MsgDigest,MsgDigest_tmp))
+ {
+ if(quiet != 1)
+ {
+ printf("%s: OK\n",file_name);
+ }
+ }
+ else
+ {
+ printf("%s: FAILED\n",file_name);
+ NoMatch ++;
+ }
+ }
+ if(NoMatch)
+ {
+ printf("skein%dsum: WARNING: %d of %d computed checksums did NOT match\n",
+ hashbitlen,NoMatch,Computed);
+ }
+ if(NotProper)
+ {
+ printf("skein%dsum: WARNING: %d line is improperly formatted\n",hashbitlen,NotProper);
+ }
+
+ }
+
+
+ }
+ }
+
+ if ((errorFound != 1) && (argc == 1 || (hashString == 1 && number_files == 0)))
+ {
+ char stri[100];
+ char longstring[1000];
+ longstring[0] = 0;
+ while((fgets(stri,100,stdin))!=NULL)
+ {
+ strcat(longstring,stri);
+ }
+ HashStringMode(longstring,MsgDigest);
+ printf("%s -\n",MsgDigest);
+ }
+
+ return 1;
+}