chiark / gitweb /
Catcrypt tools: Roll out progress indicator stuff from hashsum.
[catacomb] / cc-enc.c
1 /* -*-c-*-
2  *
3  * $Id$
4  *
5  * Catcrypt data encoding
6  *
7  * (c) 2004 Straylight/Edgeware
8  */
9
10 /*----- Licensing notice --------------------------------------------------*
11  *
12  * This file is part of Catacomb.
13  *
14  * Catacomb is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU Library General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * Catacomb is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU Library General Public License for more details.
23  *
24  * You should have received a copy of the GNU Library General Public
25  * License along with Catacomb; if not, write to the Free
26  * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27  * MA 02111-1307, USA.
28  */
29
30 /*----- Header files ------------------------------------------------------*/
31
32 #define _FILE_OFFSET_BITS 64
33
34 #include <errno.h>
35 #include <stdio.h>
36
37 #include <mLib/alloc.h>
38 #include <mLib/base64.h>
39 #include <mLib/dstr.h>
40 #include <mLib/mdwopt.h>
41 #include <mLib/report.h>
42 #include <mLib/sub.h>
43
44 #include "cc.h"
45
46 typedef int encbdryp(const char *, void *);
47
48 /*----- Main code ---------------------------------------------------------*/
49
50 /* --- Binary --- */
51
52 static enc *bin_encinit(FILE *fp, const char *msg)
53   { enc *e = CREATE(enc); return (e); }
54 static enc *bin_decinit(FILE *fp, encbdryp *func, void *p)
55   { enc *e = CREATE(enc); return (e); }
56
57 static int bin_read(enc *e, void *p, size_t sz)
58 {
59   size_t n;
60
61   if (!sz) return (0);
62   n = fread(p, 1, sz, e->fp);
63   if (!n || ferror(e->fp)) return (-1);
64   return (n);
65 }
66
67 static int bin_write(enc *e, const void *p, size_t sz)
68   { if (sz && fwrite(p, 1, sz, e->fp) < sz) return (-1); return (0); }
69
70 static int bin_done(enc *e) { return (0); }
71
72 static void bin_destroy(enc *e) { DESTROY(e); }
73
74 /* --- PEM --- */
75
76 typedef struct pem_encctx {
77   enc e;
78   char *msg;
79   unsigned f;
80   base64_ctx b;
81   dstr d;
82   size_t n;
83 #define PEMF_NL 1u
84 #define PEMF_EOF 2u
85 } pem_encctx;
86
87 static enc *pem_encinit(FILE *fp, const char *msg)
88 {
89   pem_encctx *pe = CREATE(pem_encctx);
90   base64_init(&pe->b);
91   fprintf(fp, "-----BEGIN %s-----\n", msg);
92   pe->msg = xstrdup(msg);
93   dstr_create(&pe->d);
94   pe->n = 0;
95   pe->f = 0;
96   return (&pe->e);
97 }
98
99 int checkbdry(const char *b, void *p) { return (!p || strcmp(b, p) == 0); }
100
101 static enc *pem_decinit(FILE *fp, encbdryp *func, void *p)
102 {
103   char buf[128];
104   int i, d;
105   pem_encctx *pe;
106   int ch;
107
108   /* --- Go until I find a newline and `-----' --- */
109
110 top:
111   d = 0;
112   for (;;) {
113     if ((ch = getc(fp)) == EOF) goto fail;
114     switch (ch) {
115       case '\n': d = 0; break;
116       case '-': if (d >= 0) { d++; if (d == 5) goto banner; }; break;
117       default: d = -1; break;
118     }
119   }
120
121   /* --- See what the banner looks like --- */
122
123 banner:
124   i = d = 0;
125   for (;;) {
126     if ((ch = getc(fp)) == EOF) goto fail;
127     if (ch == '-') { d++; continue; }
128     if (ch == '\n') break;
129     if (i + d + 1 >= sizeof(buf)) goto top;
130     while (d) { buf[i++] = '-'; d--; }
131     buf[i++] = ch;
132   }
133   buf[i] = 0;
134
135   /* --- Check we have the right framing --- */
136
137   if (d != 5) goto top;
138   if (strncmp(buf, "BEGIN ", 6) != 0 || (func && !func(buf + 6, p)))
139     goto top;
140
141   /* --- Ready --- */
142
143   pe = CREATE(pem_encctx);
144   base64_init(&pe->b);
145   pe->msg = xstrdup(buf + 6);
146   dstr_create(&pe->d);
147   pe->n = 0;
148   pe->f = PEMF_NL;
149   return (&pe->e);
150
151   /* --- Failed --- */
152
153 fail:
154   die(EXIT_FAILURE, "initial encapsulation boundary not found");
155   return (0);
156 }
157
158 #define PEM_CHUNKSZ 4096
159
160 static int pem_read(enc *e, void *p, size_t sz)
161 {
162   pem_encctx *pe = (pem_encctx *)e;
163   char buf[PEM_CHUNKSZ];
164   char *pp = p;
165   int ch;
166   size_t n;
167   int rc = 0;
168
169   for (;;) {
170     n = pe->d.len - pe->n;
171     if (n > sz) n = sz;
172     memcpy(pp, pe->d.buf + pe->n, n);
173     pe->n += n;
174     pp += n;
175     rc += n;
176     sz -= n;
177     if (!sz) break;
178     if (pe->f & PEMF_EOF) return (rc ? rc : -1);
179     dstr_reset(&pe->d);
180     n = 0;
181     for (;;) {
182       if ((ch = getc(pe->e.fp)) == EOF) return (-1);
183       if ((pe->f & PEMF_NL) && ch == '-') {
184         ungetc(ch, pe->e.fp);
185         pe->f |= PEMF_EOF;
186         break;
187       }
188       if (ch == '\n') { pe->f |= PEMF_NL; continue; }
189       pe->f &= ~PEMF_NL;
190       buf[n++] = ch;
191       if (n >= PEM_CHUNKSZ) break;
192     }
193     if (n)
194       base64_decode(&pe->b, buf, n, &pe->d);
195     if (pe->f & PEMF_EOF)
196       base64_decode(&pe->b, 0, 0, &pe->d);
197     pe->n = 0;
198   }
199   return (rc);
200 }
201
202 static int pem_write(enc *e, const void *p, size_t sz)
203 {
204   pem_encctx *pe = (pem_encctx *)e;
205   const char *pp = p;
206   size_t n;
207
208   while (sz) {
209     n = PEM_CHUNKSZ;
210     if (n > sz) n = sz;
211     dstr_reset(&pe->d);
212     base64_encode(&pe->b, pp, n, &pe->d);
213     if (fwrite(pe->d.buf, 1, pe->d.len, pe->e.fp) < pe->d.len)
214       return (-1);
215     pp += n;
216     sz -= n;
217   }
218   return (0);
219 }
220
221 static int pem_encdone(enc *e)
222 {
223   pem_encctx *pe = (pem_encctx *)e;
224   dstr_reset(&pe->d);
225   base64_encode(&pe->b, 0, 0, &pe->d);
226   if (fwrite(pe->d.buf, 1, pe->d.len, pe->e.fp) < pe->d.len)
227     return (-1);
228   if (pe->b.lnlen) fputc('\n', pe->e.fp);
229   fprintf(pe->e.fp, "-----END %s-----\n", pe->msg);
230   return (0);
231 }
232
233 static int pem_decdone(enc *e)
234 {
235   pem_encctx *pe = (pem_encctx *)e;
236   char buf[128];
237   int i, d;
238   int ch;
239
240   for (d = 0; d < 5; d++)
241     if ((ch = getc(pe->e.fp)) != '-') goto fail;
242   i = d = 0;
243   for (;;) {
244     if ((ch = getc(pe->e.fp)) == EOF) goto fail;
245     if (ch == '-') { d++; continue; }
246     if (ch == '\n') break;
247     if (i + d + 1 >= sizeof(buf)) goto fail;
248     while (d) { buf[i++] = '-'; d--; }
249     buf[i++] = ch;
250   }
251   if (d != 5) goto fail;
252   buf[i] = 0;
253   if (strncmp(buf, "END ", 4) != 0 || strcmp(buf + 4, pe->msg) != 0)
254     goto fail;
255   return (0);
256
257 fail:
258   die(EXIT_FAILURE, "final encapsulation boundary not found");
259   return (-1);
260 }
261
262 static void pem_destroy(enc *e)
263 {
264   pem_encctx *pe = (pem_encctx *)e;
265   dstr_destroy(&pe->d);
266   xfree(pe->msg);
267   DESTROY(pe);
268 }
269
270 /* --- Encoder table --- */
271
272 const encops enctab[] = {
273   { "binary", "rb", "wb", 1, 1,
274     bin_encinit, bin_decinit,
275     bin_read, bin_write,
276     bin_done, bin_done,
277     bin_destroy },
278   { "pem", "r", "w", 3, 4,
279     pem_encinit, pem_decinit,
280     pem_read, pem_write,
281     pem_encdone, pem_decdone,
282     pem_destroy },
283   { 0 }
284 };
285
286 /* --- @getenc@ --- *
287  *
288  * Arguments:   @const char *enc@ = name of wanted encoding
289  *
290  * Returns:     Pointer to encoder operations.
291  *
292  * Use:         Finds a named encoder or decoder.
293  */
294
295 const encops *getenc(const char *enc)
296 {
297   const encops *eo;
298
299   for (eo = enctab; eo->name; eo++) {
300     if (strcmp(eo->name, enc) == 0)
301       goto e_found;
302   }
303   die(EXIT_FAILURE, "couldn't find encoding `%s'", enc);
304 e_found:
305   return (eo);
306 }
307
308 /* --- @initenc@ --- *
309  *
310  * Arguments:   @const encops *eo@ = operations (from @getenc@)
311  *              @FILE *fp@ = file handle to attach
312  *              @const char *msg@ = banner message
313  *
314  * Returns:     The encoder object.
315  *
316  * Use:         Initializes an encoder.
317  */
318
319 enc *initenc(const encops *eo, FILE *fp, const char *msg)
320 {
321   enc *e = eo->initenc(fp, msg);
322   e->ops = eo;
323   e->fp = fp;
324   return (e);
325 }
326
327 /* --- @initdec@ --- *
328  *
329  * Arguments:   @const encops *eo@ = operations (from @getenc@)
330  *              @FILE *fp@ = file handle to attach
331  *              @int (*func)(const char *, void *)@ = banner check function
332  *              @void *p@ = argument for @func@
333  *
334  * Returns:     The encoder object.
335  *
336  * Use:         Initializes an encoder.
337  */
338
339 enc *initdec(const encops *eo, FILE *fp,
340              int (*func)(const char *, void *), void *p)
341 {
342   enc *e = eo->initdec(fp, func, p);
343   e->ops = eo;
344   e->fp = fp;
345   return (e);
346 }
347
348 /* --- @freeenc@ --- *
349  *
350  * Arguments:   @enc *e@ = encoder object
351  *
352  * Returns:     ---
353  *
354  * Use:         Frees an encoder object.
355  */
356
357 void freeenc(enc *e) { e->ops->destroy(e); }
358
359 /*----- Encoding and decoding commands ------------------------------------*/
360
361 int cmd_encode(int argc, char *argv[])
362 {
363   const char *fn, *of = 0;
364   FILE *ofp = 0;
365   FILE *fp = 0;
366   const char *ef = "binary";
367   const char *bd = "MESSAGE";
368   fprogress ff;
369   int i;
370   size_t n;
371   char buf[4096];
372   unsigned f = 0;
373   const encops *eo;
374   enc *e;
375
376 #define f_bogus 1u
377 #define f_progress 2u
378
379   for (;;) {
380     static const struct option opt[] = {
381       { "format",       OPTF_ARGREQ,    0,      'f' },
382       { "boundary",     OPTF_ARGREQ,    0,      'b' },
383       { "output",       OPTF_ARGREQ,    0,      'o' },
384       { "progress",     0,              0,      'p' },
385       { 0,              0,              0,      0 }
386     };
387     i = mdwopt(argc, argv, "f:b:o:p", opt, 0, 0, 0);
388     if (i < 0) break;
389     switch (i) {
390       case 'f': ef = optarg; break;
391       case 'b': bd = optarg; break;
392       case 'o': of = optarg; break;
393       case 'p': f |= f_progress; break;
394       default: f |= f_bogus; break;
395     }
396   }
397   if (argc - optind > 1 || (f & f_bogus))
398     die(EXIT_FAILURE, "Usage: encode [-OPTIONS] [FILE]");
399
400   if ((eo = getenc(ef)) == 0)
401     die(EXIT_FAILURE, "encoding `%s' not found", ef);
402
403   fn = optind < argc ? argv[optind++] : "-";
404   if (strcmp(fn, "-") == 0)
405     fp = stdin;
406   else if ((fp = fopen(fn, "rb")) == 0) {
407     die(EXIT_FAILURE, "couldn't open file `%s': %s",
408         fn, strerror(errno));
409   }
410
411   if (!of || strcmp(of, "-") == 0)
412     ofp = stdout;
413   else if ((ofp = fopen(of, eo->wmode)) == 0) {
414     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
415         ofp, strerror(errno));
416   }
417
418   e = initenc(eo, ofp, bd);
419
420   if (f & f_progress) {
421     if (fprogress_init(&ff, fn, fp)) {
422       die(EXIT_FAILURE, "failed to initialize progress display: %s",
423           strerror(errno));
424     }
425   }
426
427   do {
428     n = fread(buf, 1, sizeof(buf), fp);
429     if (f & f_progress) fprogress_update(&ff, n);
430     if (e->ops->write(e, buf, n)) {
431       if (f & f_progress) fprogress_done(&ff);
432       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
433     }
434   } while (n == sizeof(buf));
435   if (f & f_progress) fprogress_done(&ff);
436   e->ops->encdone(e);
437   freeenc(e);
438   return (0);
439
440 #undef f_bogus
441 #undef f_progress
442 }
443
444 int cmd_decode(int argc, char *argv[])
445 {
446   const char *fn, *of = 0;
447   FILE *ofp = 0;
448   FILE *fp = 0;
449   const char *ef = "binary";
450   const char *bd = 0;
451   fprogress ff;
452   int i;
453   char buf[4096];
454   unsigned f = 0;
455   const encops *eo;
456   enc *e;
457
458 #define f_bogus 1u
459 #define f_progress 2u
460
461   for (;;) {
462     static const struct option opt[] = {
463       { "format",       OPTF_ARGREQ,    0,      'f' },
464       { "boundary",     OPTF_ARGREQ,    0,      'b' },
465       { "output",       OPTF_ARGREQ,    0,      'o' },
466       { "progress",     0,              0,      'p' },
467       { 0,              0,              0,      0 }
468     };
469     i = mdwopt(argc, argv, "f:b:o:p", opt, 0, 0, 0);
470     if (i < 0) break;
471     switch (i) {
472       case 'f': ef = optarg; break;
473       case 'b': bd = optarg; break;
474       case 'o': of = optarg; break;
475       case 'p': f |= f_progress; break;
476       default: f |= f_bogus; break;
477     }
478   }
479   if (argc - optind > 1 || (f & f_bogus))
480     die(EXIT_FAILURE, "Usage: decode [-OPTIONS] [FILE]");
481
482   if ((eo = getenc(ef)) == 0)
483     die(EXIT_FAILURE, "encoding `%s' not found", ef);
484
485   fn = optind < argc ? argv[optind++] : "-";
486   if (strcmp(fn, "-") == 0)
487     fp = stdin;
488   else if ((fp = fopen(fn, eo->rmode)) == 0) {
489     die(EXIT_FAILURE, "couldn't open file `%s': %s",
490         fn, strerror(errno));
491   }
492
493   if (!of || strcmp(of, "-") == 0)
494     ofp = stdout;
495   else if ((ofp = fopen(of, "wb")) == 0) {
496     die(EXIT_FAILURE, "couldn't open file `%s' for output: %s",
497         ofp, strerror(errno));
498   }
499
500   e = initdec(eo, fp, checkbdry, (/*unconst*/ void *)bd);
501
502   if (f & f_progress) {
503     if (fprogress_init(&ff, fn, fp)) {
504       die(EXIT_FAILURE, "failed to initialize progress display: %s",
505           strerror(errno));
506     }
507   }
508
509   do {
510     if ((i = e->ops->read(e, buf, sizeof(buf))) < 0) {
511       if (f & f_progress) fprogress_done(&ff);
512       die(EXIT_FAILURE, "error reading input: %s", strerror(errno));
513     }
514     if (f & f_progress)
515       fprogress_update(&ff, i*e->ops->ncook/e->ops->nraw);
516     if (fwrite(buf, 1, i, ofp) < i) {
517       if (f & f_progress) fprogress_done(&ff);
518       die(EXIT_FAILURE, "error writing output: %s", strerror(errno));
519     }
520   } while (i == sizeof(buf));
521   e->ops->decdone(e);
522   if (f & f_progress) fprogress_done(&ff);
523   freeenc(e);
524   return (0);
525
526 #undef f_bogus
527 #undef f_progress
528 }
529
530 /*----- That's all, folks -------------------------------------------------*/