1 /* vim: set ts=8 sts=4 sw=4 tw=80 noet: */
2 /*======================================================================
3 Copyright (C) 2004,2005,2009,2013 Walter Doekes <walter+tthsum@wjd.nu>
4 This file is part of tthsum.
5 
6 tthsum is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 tthsum is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with tthsum.  If not, see <http://www.gnu.org/licenses/>.
18 ======================================================================*/
19 #include "tthsum.h"
20 
21 #include "base32.h"
22 #include "escape.h"
23 #include "read.h"
24 #include "thex.h"
25 #include "types.h"
26 #include "utf8.h"
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 
31 #ifdef USE_TEXTS
32 #   include "texts.h"
33 #else /* !USE_TEXTS */
34 #   define get_error() "Fail"
35 #endif /* !USE_TEXTS */
36 
37 #define MY_PATH_MAX 32768
38 
39 
40 static void tthsum_progress(uint64_t current_pos, uint64_t end_pos);
41 static struct rofile* tthsum_rofopen(const char* filename,
42 	const struct tthsum_options* options);
43 
tthsum_generate_root(const char * filename,const struct tthsum_options * opt)44 static int tthsum_generate_root(const char* filename,
45 	const struct tthsum_options* opt) {
46     struct rofile* stream;
47     uint64_t hash[3];
48     char hash_base32[40];
49     /* unicode will be at most equal sized to the multibyte charset */
50     wchar_t filename_uni[MY_PATH_MAX + 1];
51     /* utf8/C-escaping will be at most 4 times the mbcs equivalent */
52     char filename_buf[MY_PATH_MAX * 4 + 1];
53 
54     if (filename != NULL && strlen(filename) > MY_PATH_MAX) {
55 #ifdef USE_TEXTS
56 	set_error("tthsum_generate_root", TTHSUM_FILENAME_TOO_LARGE);
57 #endif /* USE_TEXTS */
58 	return -1;
59     }
60 
61     if ((stream = tthsum_rofopen(filename, opt)) == NULL
62 	    || thex_tiger_root(stream, hash,
63 		    opt->progress_every_mib ? tthsum_progress : NULL) == -1) {
64 	if (stream != NULL)
65 	    rofclose(stream);
66 	return -1;
67     }
68     rofclose(stream);
69 
70     if (uint64tobase32(hash_base32, hash, 3) == -1)
71 	return -1;
72     if (filename != NULL) {
73 	/* the order of calling to_utf8 or to_ctrlesc doesn't matter,
74 	   as they operate on chars 0-127 and 128-255 respectively. */
75 #ifdef _WIN32
76 	/* change all \\ to / on Windows */
77 	char filename_tmp[MY_PATH_MAX + 1];
78 	char* p;
79 	for (p = filename_tmp; *filename != '\0'; ++filename)
80 	    if (*filename == '\\')
81 		*p++ = '/';
82 	    else
83 		*p++ = *filename;
84 	*p = '\0';
85 	strtoctrlesc(filename_buf, filename_tmp);
86 #else /* !_WIN32 */
87 	strtoctrlesc(filename_buf, filename);
88 #endif /* !_WIN32 */
89 	if (mbstowcs(filename_uni, filename_buf, MY_PATH_MAX + 1)
90 		== (size_t)-1) {
91 	    if (!opt->has_locale)
92 		fprintf(stderr, "tthsum: warning: Locale settings are C/POSIX. "
93 			"See the tthsum manpage for help.\n");
94 #ifdef USE_TEXTS
95 	    set_error("mbstowcs", ERROR_FROM_OS);
96 #endif /* USE_TEXTS */
97 	    return -1;
98 	}
99 	if (wcstoutf8(filename_buf, filename_uni, MY_PATH_MAX * 4 + 1)
100 		== (size_t)-1)
101 	    return -1;
102     } else {
103 	strcpy(filename_buf, "-");
104     }
105 
106     /* print our root hash with utf8/escaped filename */
107     printf("%s  %s\n", hash_base32, filename_buf);
108     return 0;
109 }
110 
tthsum_generate_roots(const char * filenames[],int files,const struct tthsum_options * opt)111 int tthsum_generate_roots(const char* filenames[], int files,
112 	const struct tthsum_options* opt) {
113     int i;
114     for (i = 0; i < files; ++i) {
115 	if (tthsum_generate_root(filenames[i], opt) == -1)
116 #ifdef USE_TEXTS
117 	    fprintf(stderr, "tthsum: `%s': %s\n",
118 		    filenames[i] ? filenames[i] : "-", get_error());
119 #else /* !USE_TEXTS */
120 	    fprintf(stderr, "tthsum: `%s': Fail\n",
121 		    filenames[i] ? filenames[i] : "-");
122 #endif /* !USE_TEXTS */
123     }
124     return 0;
125 }
126 
tthsum_check_digest_line(char * line,unsigned line_len,const struct tthsum_options * opt)127 static int tthsum_check_digest_line(char* line,
128 	unsigned line_len, const struct tthsum_options* opt) {
129     /* 39char base32, 2 spaces, MY_PATH_MAX utf8/C-esc file, 1 terminating \0 */
130     wchar_t filename_uni[MY_PATH_MAX * 4 + 1];
131     char filename_mbs[MY_PATH_MAX * 4 + 1];
132     char filename[MY_PATH_MAX * 4 + 1];
133     struct rofile* stream;
134     uint64_t hash_line[3] = {_ULL(0), _ULL(0), _ULL(0)};
135     uint64_t hash_file[3];
136     char* linep = line;
137     char tmp;
138 
139     /* must be at least 39 base32 + 2 spaces + 1 filename long */
140     if (line_len < 42 || line_len > (MY_PATH_MAX * 4 + 39 + 2)) {
141 #ifdef USE_TEXTS
142 	set_error("tthsum_check_digest_line", TTHSUM_LINE_CORRUPT);
143 #endif /* USE_TEXTS */
144 	return -1;
145     }
146     /* read base32 hash */
147     if (base32touint64(hash_line, linep, 3) == -1)
148 	return -1;
149     /* check the two spaces */
150     linep += 39;
151     if (*linep != ' ' || *++linep != ' ') {
152 #ifdef USE_TEXTS
153 	set_error("tthsum_check_digest_line", TTHSUM_LINE_CORRUPT);
154 #endif /* USE_TEXTS */
155 	return -1;
156     }
157     ++linep;
158     /* fetch filename and convert to local codepage.
159      * unfortunately our windows utf8 routine doesn't return success unless
160      * it has found a terminating null, sigh */
161     tmp = *(line + line_len);
162     *(line + line_len) = '\0';
163     if (utf8towcs(filename_uni, linep, MY_PATH_MAX * 4 + 1)
164 	    == (size_t)-1) {
165 	*(line + line_len) = tmp;
166 	return -1;
167     }
168     *(line + line_len) = tmp; /* yes, uncorrupting the crap */
169     filename_uni[MY_PATH_MAX * 4] = '\0'; /* make sure we're terminated */
170     /* proceed */
171     if (wcstombs(filename_mbs, filename_uni, MY_PATH_MAX * 4 + 1)
172 	    == (size_t)-1) {
173 #ifdef USE_TEXTS
174 	set_error("wcstombs", ERROR_FROM_OS);
175 #endif /* USE_TEXTS */
176 	return -1;
177     }
178     filename_mbs[MY_PATH_MAX * 4] = '\0'; /* make sure we're terminated */
179     if (ctrlesctostr(filename, filename_mbs) == -1)
180 	return -1;
181 
182     /* w00t, got filename. go check TTH on it */
183     if ((stream = tthsum_rofopen(filename, opt)) == NULL
184 	    || thex_tiger_root(stream, hash_file,
185 		    opt->progress_every_mib ? tthsum_progress : NULL) == -1) {
186 	if (opt->verbose)
187 	    fprintf(stderr, "%-14s FAILED (%s)\n", filename, get_error());
188 	else
189 	    fprintf(stderr, "tthsum: `%s': %s\n", filename, get_error());
190 	if (stream != NULL)
191 	    rofclose(stream);
192 	return 0;
193     }
194     rofclose(stream);
195 
196     /* compare tth's */
197     if (memcmp(hash_line, hash_file, 3 * 8) == 0) {
198 	if (opt->verbose)
199 	    printf("%-14s OK\n", filename);
200 	return 1;
201     }
202 
203     /* not equal, print an error */
204     if (!opt->verbose) {
205 #ifdef USE_TEXTS
206 	fprintf(stderr, "tthsum: %s `%s'\n", get_text(TTHSUM_MISMATCHED_TTH),
207 		filename);
208 #else /* !USE_TEXTS */
209 	fprintf(stderr, "tthsum: TTH check failed for `%s'\n", filename);
210 #endif /* !USE_TEXTS */
211     } else {
212 	printf("%-14s FAILED\n", filename);
213     }
214     return 0;
215 }
216 
tthsum_check_digest(const char * filename,const struct tthsum_options * opt)217 int tthsum_check_digest(const char* filename,
218 	const struct tthsum_options* opt) {
219     unsigned filesize;
220     char* file_buf = rof_readall(
221 	filename == NULL ? rofopen_sysfd_stdin() : rofopen_sysfile(filename),
222 	&filesize
223     );
224     char* file_buf_end;
225     char* s;
226     char* e;
227     unsigned line_no = 1;
228     unsigned lines_correct = 0;
229     unsigned lines_incorrect = 0;
230     unsigned files_correct = 0;
231 
232     if (filename == NULL)
233 	filename = "-";
234     if (file_buf == NULL) {
235 #ifdef USE_TEXTS
236 	fprintf(stderr, "tthsum: `%s': %s\n", filename, get_error());
237 #else /* !USE_TEXTS */
238 	fprintf(stderr, "tthsum: `%s': Fail\n", filename);
239 #endif /* !USE_TEXTS */
240 	return 1;
241     }
242     /* Operate on a line by line basis.. we would like to get the most out
243      * of corrupt or un-decodable files (esp. if the decoding fails for some
244      * files only) */
245     file_buf_end = file_buf + filesize;
246     s = e = file_buf;
247     while (1) {
248 	int ret;
249 	/* find \r or \n, or really, any ctrl-character will do, as they're all
250 	   escaped */
251 	while (e != file_buf_end && (unsigned char)*e >= 0x20)
252 	    ++e;
253 	/* test the line */
254 	ret = tthsum_check_digest_line(s, e - s, opt);
255 	if (ret == 1) {
256 	    ++lines_correct;
257 	    ++files_correct;
258 	} else if (ret == 0) {
259 	    ++lines_correct;
260 	} else {
261 	    if (opt->warn)
262 #ifdef USE_TEXTS
263 		fprintf(stderr, "tthsum: `%s': %u: %s\n", filename, line_no,
264 			get_error());
265 #else /* !USE_TEXTS */
266 		fprintf(stderr, "tthsum: `%s': %u: Fail\n", filename, line_no);
267 #endif /* !USE_TEXTS */
268 	    ++lines_incorrect;
269 	}
270 	/* find the only true line separator, the \n */
271 	s = e;
272 	while (s != file_buf_end && *s != '\n')
273 	    ++s;
274 	if (s == file_buf_end || s + 1 == file_buf_end) /* expect \n at eof */
275 	    break;
276 	/* start on the next line */
277 	e = ++s;
278 	++line_no;
279     }
280     /* free our readfile data */
281     free(file_buf);
282 
283     if (files_correct == 0 && lines_correct == 0) {
284 	fprintf(stderr, "tthsum: `%s': No files checked\n", filename);
285 	return -1;
286     } else if (lines_correct != files_correct) {
287 	if (opt->verbose)
288 	    fprintf(stderr, "tthsum: %u of %u file(s) failed TTH check\n",
289 		    lines_correct - files_correct, lines_correct);
290 	return -1;
291     }
292     return 0;
293 }
294 
tthsum_progress(uint64_t current_pos,uint64_t end_pos)295 static void tthsum_progress(uint64_t current_pos, uint64_t end_pos) {
296     if (end_pos != (uint64_t)-1)
297 	fprintf(stderr, "(%0.1f%%)\r",
298 		100.0 * (float)current_pos / (float)end_pos);
299     else
300 	fprintf(stderr, "(%" PRIu64 ")\r", current_pos);
301 }
302 
tthsum_rofopen(const char * filename,const struct tthsum_options * options)303 static struct rofile* tthsum_rofopen(const char* filename,
304 	const struct tthsum_options* options) {
305     if (filename == NULL)
306 	return rofopen_sysfd_stdin();
307     else if (!options->use_mmap)
308 	return rofopen_sysfile(filename);
309     return rofopen_mmap(filename);
310 }
311