blob: 0a7636d2b99ab083d7f1cf3d52332553632cc857 [file] [log] [blame]
Alexandre Lisionddd731e2014-01-31 11:50:08 -05001// Copyright (C) 2010 David Sugar, Tycho Softworks.
2//
3// This file is part of GNU uCommon C++.
4//
5// GNU uCommon C++ is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Lesser General Public License as published
7// by the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// GNU uCommon C++ is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public License
16// along with GNU uCommon C++. If not, see <http://www.gnu.org/licenses/>.
17
18#include <ucommon/secure.h>
19#include <sys/stat.h>
20
21using namespace UCOMMON_NAMESPACE;
22
23static shell::flagopt helpflag('h',"--help", _TEXT("display this list"));
24static shell::flagopt althelp('?', NULL, NULL);
25static shell::stringopt tag('t', "--tag", _TEXT("tag annotation"), "text", "");
26static shell::stringopt algo('c', "--cipher", _TEXT("cipher method (aes256)"), "method", "aes256");
27static shell::flagopt decode('d', "--decode", _TEXT("decode archive"));
28static shell::stringopt hash('h', "--digest", _TEXT("digest method (sha)"), "method", "sha");
29static shell::flagopt noheader('n', "--no-header", _TEXT("without wrapper"));
30static shell::stringopt out('o', "--output", _TEXT("output file"), "filename", "-");
31static shell::flagopt quiet('q', "--quiet", _TEXT("quiet operation"));
32static shell::flagopt recursive('R', "--recursive", _TEXT("recursive directory scan"));
33static shell::flagopt altrecursive('r', NULL, NULL);
34static shell::flagopt hidden('s', "--hidden", _TEXT("include hidden files"));
35static shell::flagopt yes('y', "--overwrite", _TEXT("overwrite existing files"));
36
37static bool binary = false;
38static int exit_code = 0;
39static const char *argv0 = "car";
40static unsigned char frame[48], cbuf[48];
41static cipher_t cipher;
42static FILE *output = stdout;
43static enum {d_text, d_file, d_scan, d_init} decoder = d_init;
44static unsigned frames;
45
46static void report(const char *path, int code)
47{
48 const char *err = _TEXT("i/o error");
49
50 switch(code) {
51 case EACCES:
52 case EPERM:
53 err = _TEXT("permission denied");
54 break;
55 case EROFS:
56 err = _TEXT("read-only file system");
57 break;
58 case ENODEV:
59 case ENOENT:
60 err = _TEXT("no such file or directory");
61 break;
62 case ENOTDIR:
63 err = _TEXT("not a directory");
64 break;
65 case ENOTEMPTY:
66 err = _TEXT("directory not empty");
67 break;
68 case ENOSPC:
69 err = _TEXT("no space left on device");
70 break;
71 case EBADF:
72 case ENAMETOOLONG:
73 err = _TEXT("bad file path");
74 break;
75 case EBUSY:
76 case EINPROGRESS:
77 err = _TEXT("file or directory busy");
78 break;
79 case EINTR:
80 err = _TEXT("operation interupted");
81 break;
82 case EISDIR:
83 err = _TEXT("is a directory");
84 break;
85#ifdef ELOOP
86 case ELOOP:
87 err = _TEXT("too many sym links");
88 break;
89#endif
90 }
91
92 if(path)
93 shell::printf("%s: %s: %s\n", argv0, path, err);
94 else
95 shell::errexit(1, "*** %s: %s\n", argv0, err);
96
97 exit_code = 1;
98}
99
100static bool encode(const char *path, FILE *fp, size_t offset = 0)
101{
102 memset(frame, 0, sizeof(frame));
103
104 size_t count = fread(frame + offset, 1, sizeof(frame) - offset, fp);
105 char buffer[128];
106
107 if(ferror(fp)) {
108 report(path, errno);
109 return false;
110 }
111
112 if(count)
113 count += offset;
114
115 // add padd value for last frame...
116 if(count < sizeof(frame))
117 frame[sizeof(frame) - 1] = (char)(count - offset);
118
119 // cipher it...
120 size_t encoded = cipher.put(frame, sizeof(frame));
121 if(encoded != sizeof(frame)) {
122 report(path, EINTR);
123 return false;
124 }
125
126 if(binary)
127 fwrite(cbuf, sizeof(cbuf), 1, output);
128 else {
129 String::b64encode(buffer, cbuf, 48);
130 fprintf(output, "%s\n", buffer);
131 }
132
133 // return status
134 if(count == sizeof(frame))
135 return true;
136
137 return false;
138}
139
140static void encodestream(void)
141{
142
143 size_t offset = 6;
144
145 if(fsys::is_tty(shell::input()))
146 fputs("car: type your message\n", stderr);
147
148 memset(frame, 0, sizeof(frame));
149
150 for(;;) {
151 if(!encode("-", stdin, offset))
152 break;
153 offset = 0;
154 }
155
156 if(!binary && !is(noheader))
157 fprintf(output, "-----END CAR STREAM-----\n");
158}
159
160static void header(void)
161{
162 binary = true;
163
164 memset(frame, 0, sizeof(frame));
165 String::set((char *)frame, 6, ".car");
166 frame[5] = 1;
167 frame[4] = 0xff;
168 String::set((char *)frame + 6, sizeof(frame) - 6, *tag);
169 fwrite(frame, sizeof(frame), 1, output);
170}
171
172static void encodefile(const char *path, const char *name)
173{
174 char buffer[128];
175
176 fsys::fileinfo_t ino;
177
178 fsys::info(path, &ino);
179
180 FILE *fp = fopen(path, "r");
181 if(!fp) {
182 report(name, errno);
183 return;
184 }
185
186 lsb_setlong(frame, ino.st_size);
187 frame[4] = 1;
188 frame[5] = 0;
189 String::set((char *)(frame + 6), sizeof(frame) - 6, name);
190
191 cipher.put(frame, sizeof(frame));
192
193 if(binary)
194 fwrite(cbuf, sizeof(cbuf), 1, output);
195 else {
196 String::b64encode(buffer, cbuf, 48);
197 fprintf(output, "%s\n", buffer);
198 }
199
200 for(;;) {
201 if(!encode(name, fp))
202 break;
203 }
204}
205
206static void final(void)
207{
208 size_t size;
209
210 switch(decoder) {
211 case d_init:
212 return;
213 case d_scan:
214 cipher.put(frame, sizeof(frame));
215 if(cbuf[4] > 0)
216 return;
217 size = cbuf[sizeof(frame) - 1];
218 if(size)
219 fwrite(cbuf + 6, size, 1, output);
220 break;
221 default:
222 decoder = d_scan;
223 cipher.put(frame, sizeof(frame));
224 size = cbuf[sizeof(frame) - 1];
225 if(size)
226 fwrite(cbuf, size, 1, output);
227 }
228}
229
230static void process(void)
231{
232 string_t path;
233 int key;
234 char *cp;
235
236 switch(decoder) {
237 case d_init:
238 decoder = d_scan;
239 return;
240 case d_scan:
241 cipher.put(frame, sizeof(frame));
242 if(cbuf[4] == 0xff) // header...
243 break;
244 if(!cbuf[4]) { // if message
245 fwrite(cbuf + 6, sizeof(frame) - 6, 1, output);
246 decoder = d_text;
247 break;
248 }
249 decoder = d_file;
250 frames = lsb_getlong(cbuf) / sizeof(frame);
251 path = str((char *)(cbuf + 6));
252 cp = strrchr((char *)(cbuf + 6), '/');
253 if(cp) {
254 *cp = 0;
255 dir::create((char *)(cbuf + 6), 0640);
256 }
257 if(fsys::is_dir(*path))
258 shell::errexit(8, "*** %s: %s: %s\n",
259 argv0, *path, _TEXT("output is directory"));
260
261 if(fsys::is_file(*path) && !is(yes)) {
262 string_t prompt = str("overwrite ") + path + " <y/n>? ";
263 if(is(quiet))
264 key = 0;
265 else
266 key = shell::inkey(prompt);
267 switch(key)
268 {
269 case 'y':
270 case 'Y':
271 printf("y\n");
272 break;
273 default:
274 printf("n\n");
275 case 0:
276 shell::errexit(8, "*** %s: %s: %s\n",
277 argv0, *path, _TEXT("will not overwrite"));
278 }
279 }
280
281 output = fopen(*path, "w");
282 if(!output)
283 shell::errexit(8, "*** %s: %s: %s\n",
284 argv0, *path, _TEXT("cannot create"));
285 if(!is(quiet))
286 printf("decoding %s...\n", *path);
287 break;
288 case d_file:
289 if(!frames) {
290 final();
291 break;
292 }
293 --frames;
294 default:
295 cipher.put(frame, sizeof(frame));
296 fwrite(cbuf, sizeof(frame), 1, output);
297 }
298}
299
300static void binarydecode(FILE *fp, const char *path)
301{
302 char buffer[48];
303
304 memset(frame, 0, sizeof(frame));
305 memset(buffer, 0, sizeof(buffer));
306 if(fread(frame, sizeof(frame), 1, fp) < 1)
307 shell::errexit(6, "*** %s: %s: %s\n",
308 argv0, path, _TEXT("cannot read archive"));
309
310 if(!eq((char *)frame, ".car", 4) || (frame[4] != 0xff))
311 shell::errexit(6, "*** %s: %s: %s\n",
312 argv0, path, _TEXT("not a cryptographic archive"));
313
314 for(;;) {
315 if(feof(fp)) {
316 final();
317 return;
318 }
319
320 if(ferror(fp)) {
321 exit_code = errno;
322 report(path, exit_code);
323 return;
324 }
325
326 if(fread(buffer, sizeof(buffer), 1, fp) < 1)
327 continue;
328 process();
329 memcpy(frame, buffer, sizeof(frame));
330 }
331}
332
333static void streamdecode(FILE *fp, const char *path)
334{
335 char buffer[128];
336 for(;;) {
337 if(NULL == fgets(buffer, sizeof(buffer), fp) || feof(fp)) {
338 shell::errexit(5, "*** %s: %s: %s\n",
339 argv0, path, _TEXT("no archive found"));
340 }
341 if(ferror(fp)) {
342 exit_code = errno;
343 report(path, exit_code);
344 return;
345 }
346 if(eq("-----BEGIN CAR STREAM-----\n", buffer))
347 break;
348 }
349 for(;;) {
350 if(feof(fp)) {
351 final();
352 return;
353 }
354
355 if(ferror(fp)) {
356 exit_code = errno;
357 report(path, exit_code);
358 return;
359 }
360
361 if(fgets(buffer, sizeof(buffer), fp) == NULL)
362 continue;
363
364 if(eq("-----END CAR STREAM-----\n", buffer)) {
365 final();
366 return;
367 }
368
369 // ignore extra headers...
370 if(strstr(buffer, ": "))
371 continue;
372
373 process();
374 unsigned len = String::b64decode(frame, buffer, 48);
375 if(len < 48) {
376 report(path, EINTR);
377 return;
378 }
379 }
380}
381
382static void scan(string_t path, string_t prefix)
383{
384 char filename[128];
385 string_t filepath;
386 string_t name;
387 string_t subdir;
388 dir_t dir(path);
389
390 while(is(dir) && dir.read(filename, sizeof(filename))) {
391 if(*filename == '.' && (filename[1] == '.' || !filename[1]))
392 continue;
393
394 if(*filename == '.' && !is(hidden))
395 continue;
396
397 filepath = str(path) + str("/") + str(filename);
398 if(prefix[0])
399 name ^= prefix + str("/") + str(filename);
400 else
401 name ^= str(filename);
402
403 if(fsys::is_dir(filepath)) {
404 if(is(recursive) || is(altrecursive))
405 scan(filepath, name);
406 else
407 report(*filepath, EISDIR);
408 }
409 else
410 encodefile(*filepath, *name);
411 }
412}
413
414PROGRAM_MAIN(argc, argv)
415{
416 shell::bind("car");
417 shell args(argc, argv);
418 argv0 = args.argv0();
419 unsigned count = 0;
420 char passphrase[256];
421 char confirm[256];
422 const char *ext;
423
424 argv0 = args.argv0();
425
426 if(is(helpflag) || is(althelp)) {
427 printf("%s\n", _TEXT("Usage: car [options] path..."));
428 printf("%s\n\n", _TEXT("Crytographic archiver"));
429 printf("%s\n", _TEXT("Options:"));
430 shell::help();
431 printf("\n%s\n", _TEXT("Report bugs to dyfet@gnu.org"));
432 PROGRAM_EXIT(0);
433 }
434
435 if(!secure::init())
436 shell::errexit(1, "*** %s: %s\n", argv0, _TEXT("not supported"));
437
438 if(!Digest::has(*hash))
439 shell::errexit(2, "*** %s: %s: %s\n",
440 argv0, *hash, _TEXT("unkown or unsupported digest method"));
441
442 if(!Cipher::has(*algo))
443 shell::errexit(2, "*** %s: %s: %s\n",
444 argv0, *algo, _TEXT("unknown or unsupported cipher method"));
445
446 shell::getpass("passphrase: ", passphrase, sizeof(passphrase));
447 shell::getpass("confirm: ", confirm, sizeof(confirm));
448
449 if(!eq(passphrase, confirm))
450 shell::errexit(3, "*** %s: %s\n",
451 argv0, _TEXT("passphrase does not match confirmation"));
452
453 // set key and cleanup buffers...
454 skey_t key(*algo, *hash, passphrase);
455 memset(passphrase, 0, sizeof(passphrase));
456 memset(confirm, 0, sizeof(confirm));
457 memset(cbuf, 0, sizeof(cbuf));
458
459 if(is(decode))
460 cipher.set(&key, Cipher::DECRYPT, cbuf, sizeof(cbuf));
461 else
462 cipher.set(&key, Cipher::ENCRYPT, cbuf, sizeof(cbuf));
463
464 if(is(decode) && !args()) {
465 streamdecode(stdin, "-");
466 goto end;
467 }
468
469 if(is(decode) && args() > 1)
470 shell::errexit(3, "*** %s: %s\n", argv0, _TEXT("specify only one file for decoder"));
471
472 if(is(decode)) {
473 FILE *fp = fopen(args[0], "r");
474 if(!fp)
475 shell::errexit(7, "*** %s: %s: %s\n",
476 argv0, args[0], _TEXT("cannot open or access"));
477
478 const char *ext = strrchr(args[0], '.');
479 if(eq_case(ext, ".car"))
480 binarydecode(fp, args[0]);
481 else
482 streamdecode(fp, args[0]);
483 goto end;
484 }
485
486 if(!eq(*out, "-")) {
487 output = fopen(*out, "w");
488 if(!output) {
489 shell::errexit(4, "*** %s: %s: %s\n",
490 argv0, *out, _TEXT("cannot create"));
491 }
492 }
493
494 // if we are outputting to a car file, do it in binary
495 ext = strrchr(*out, '.');
496 if(eq_case(ext, ".car"))
497 header();
498
499 if(!binary && !is(noheader)) {
500 fprintf(output, "-----BEGIN CAR STREAM-----\n");
501 if(tag)
502 fprintf(output, "Tag: %s\n", *tag);
503 }
504
505 if(!args()) {
506 encodestream();
507 goto end;
508 }
509
510 while(count < args()) {
511 if(fsys::is_dir(args[count]))
512 scan(str(args[count++]), "");
513 else {
514 const char *cp = args[count++];
515 const char *ep = strrchr(cp, '/');
516 if(!ep)
517 ep = strrchr(cp, '\\');
518 if(ep)
519 ++ep;
520 else
521 ep = cp;
522 encodefile(cp, ep);
523 }
524 }
525
526 if(!binary && !is(noheader))
527 fprintf(output, "-----END CAR STREAM-----\n");
528
529end:
530 PROGRAM_EXIT(exit_code);
531}
532