Deleted Added
full compact
ctm_rmail.c (8857) ctm_rmail.c (13015)
1/*
2 * Accept one (or more) ASCII encoded chunks that together make a compressed
3 * CTM delta. Decode them and reconstruct the deltas. Any completed
4 * deltas may be passed to ctm for unpacking.
5 *
6 * Author: Stephen McKay
7 *
8 * NOTICE: This is free software. I hope you get some use from this program.
9 * In return you should think about all the nice people who give away software.
10 * Maybe you should write some free software too.
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
1/*
2 * Accept one (or more) ASCII encoded chunks that together make a compressed
3 * CTM delta. Decode them and reconstruct the deltas. Any completed
4 * deltas may be passed to ctm for unpacking.
5 *
6 * Author: Stephen McKay
7 *
8 * NOTICE: This is free software. I hope you get some use from this program.
9 * In return you should think about all the nice people who give away software.
10 * Maybe you should write some free software too.
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include
15#include <strings.h>
16#include <ctype.h>
16#include <errno.h>
17#include <unistd.h>
18#include <sys/types.h>
19#include <sys/stat.h>
20#include <fcntl.h>
21#include <limits.h>
22#include "error.h"
23#include "options.h"
24
25#define CTM_STATUS ".ctm_status"
26
27char *piece_dir = NULL; /* Where to store pieces of deltas. */
28char *delta_dir = NULL; /* Where to store completed deltas. */
29char *base_dir = NULL; /* The tree to apply deltas to. */
30int delete_after = 0; /* Delete deltas after ctm applies them. */
31
32void apply_complete(void);
33int read_piece(char *input_file);
34int combine_if_complete(char *delta, int pce, int npieces);
35int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
36int decode_line(char *line, char *out_buf);
37int lock_file(char *name);
38
39/*
40 * If given a '-p' flag, read encoded delta pieces from stdin or file
41 * arguments, decode them and assemble any completed deltas. If given
42 * a '-b' flag, pass any completed deltas to 'ctm' for application to
43 * the source tree. The '-d' flag is mandatory, but either of '-p' or
44 * '-b' can be omitted. If given the '-l' flag, notes and errors will
45 * be timestamped and written to the given file.
46 *
47 * Exit status is 0 for success or 1 for indigestible input. That is,
48 * 0 means the encode input pieces were decoded and stored, and 1 means
49 * some input was discarded. If a delta fails to apply, this won't be
50 * reflected in the exit status. In this case, the delta is left in
51 * 'deltadir'.
52 */
53int
54main(int argc, char **argv)
55 {
56 char *log_file = NULL;
57 int status = 0;
58 int fork_ctm = 0;
59
60 err_prog_name(argv[0]);
61
62 OPTIONS("[-Df] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
63 FLAG('D', delete_after)
64 FLAG('f', fork_ctm)
65 STRING('p', piece_dir)
66 STRING('d', delta_dir)
67 STRING('b', base_dir)
68 STRING('l', log_file)
69 ENDOPTS
70
71 if (delta_dir == NULL)
72 usage();
73
74 if (piece_dir == NULL && (base_dir == NULL || argc > 1))
75 usage();
76
77 if (log_file != NULL)
78 err_set_log(log_file);
79
80 /*
81 * Digest each file in turn, or just stdin if no files were given.
82 */
83 if (argc <= 1)
84 {
85 if (piece_dir != NULL)
86 status = read_piece(NULL);
87 }
88 else
89 {
90 while (*++argv != NULL)
91 status |= read_piece(*argv);
92 }
93
94 /*
95 * Maybe it's time to look for and apply completed deltas with ctm.
96 *
97 * Shall we report back to sendmail immediately, and let a child do
98 * the work? Sendmail will be waiting for us to complete, delaying
99 * other mail, and possibly some intermediate process (like MH slocal)
100 * will terminate us if we take too long!
101 *
102 * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
103 * Also, the child exit status is unimportant.
104 */
105 if (base_dir != NULL)
106 if (!fork_ctm || fork() == 0)
107 apply_complete();
108
109 return status;
110 }
111
112
113/*
114 * Construct the file name of a piece of a delta.
115 */
116#define mk_piece_name(fn,d,p,n) \
117 sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
118
119/*
120 * Construct the file name of an assembled delta.
121 */
122#define mk_delta_name(fn,d) \
123 sprintf((fn), "%s/%s", delta_dir, (d))
124
125/*
126 * If the next required delta is now present, let ctm lunch on it and any
127 * contiguous deltas.
128 */
129void
130apply_complete()
131 {
132 int i, dn;
133 int lfd;
134 FILE *fp, *ctm;
135 struct stat sb;
136 char class[20];
137 char delta[30];
138 char junk[2];
139 char fname[PATH_MAX];
140 char here[PATH_MAX];
141 char buf[PATH_MAX*2];
142
143 /*
144 * Grab a lock on the ctm mutex file so that we can be sure we are
145 * working alone, not fighting another ctm_rmail!
146 */
147 strcpy(fname, delta_dir);
148 strcat(fname, "/.mutex_apply");
149 if ((lfd = lock_file(fname)) < 0)
150 return;
151
152 /*
153 * Find out which delta ctm needs next.
154 */
155 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
156 if ((fp = fopen(fname, "r")) == NULL)
157 {
158 close(lfd);
159 return;
160 }
161
162 i = fscanf(fp, "%s %d %c", class, &dn, junk);
163 fclose(fp);
164 if (i != 2)
165 {
166 close(lfd);
167 return;
168 }
169
170 /*
171 * We might need to convert the delta filename to an absolute pathname.
172 */
173 here[0] = '\0';
174 if (delta_dir[0] != '/')
175 {
176 getcwd(here, sizeof(here)-1);
177 i = strlen(here) - 1;
178 if (i >= 0 && here[i] != '/')
179 {
180 here[++i] = '/';
181 here[++i] = '\0';
182 }
183 }
184
185 /*
186 * Keep applying deltas until we run out or something bad happens.
187 */
188 for (;;)
189 {
190 sprintf(delta, "%s.%04d.gz", class, ++dn);
191 mk_delta_name(fname, delta);
192
193 if (stat(fname, &sb) < 0)
194 break;
195
196 sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
197 if ((ctm = popen(buf, "r")) == NULL)
198 {
199 err("ctm failed to apply %s", delta);
200 break;
201 }
202
203 while (fgets(buf, sizeof(buf), ctm) != NULL)
204 {
205 i = strlen(buf) - 1;
206 if (i >= 0 && buf[i] == '\n')
207 buf[i] = '\0';
208 err("ctm: %s", buf);
209 }
210
211 if (pclose(ctm) != 0)
212 {
213 err("ctm failed to apply %s", delta);
214 break;
215 }
216
217 if (delete_after)
218 unlink(fname);
219
220 err("%s applied%s", delta, delete_after ? " and deleted" : "");
221 }
222
223 /*
224 * Closing the lock file clears the lock.
225 */
226 close(lfd);
227 }
228
229
230/*
231 * This cheap plastic checksum effectively rotates our checksum-so-far
232 * left one, then adds the character. We only want 16 bits of it, and
233 * don't care what happens to the rest. It ain't much, but it's small.
234 */
235#define add_ck(sum,x) \
236 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
237
238
239/*
240 * Decode the data between BEGIN and END, and stash it in the staging area.
241 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
242 * If we have all pieces of a delta, combine them. Returns 0 on success,
243 * and 1 for any sort of failure.
244 */
245int
246read_piece(char *input_file)
247 {
248 int status = 0;
249 FILE *ifp, *ofp = 0;
250 int decoding = 0;
251 int got_one = 0;
252 int line_no = 0;
253 int i, n;
254 int pce, npieces;
255 unsigned claimed_cksum;
256 unsigned short cksum = 0;
257 char out_buf[200];
258 char line[200];
259 char delta[30];
260 char pname[PATH_MAX];
261 char tname[PATH_MAX];
262 char junk[2];
263
264 ifp = stdin;
265 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
266 {
267 err("cannot open '%s' for reading", input_file);
268 return 1;
269 }
270
271 while (fgets(line, sizeof(line), ifp) != NULL)
272 {
273 line_no++;
274
275 /*
17#include <errno.h>
18#include <unistd.h>
19#include <sys/types.h>
20#include <sys/stat.h>
21#include <fcntl.h>
22#include <limits.h>
23#include "error.h"
24#include "options.h"
25
26#define CTM_STATUS ".ctm_status"
27
28char *piece_dir = NULL; /* Where to store pieces of deltas. */
29char *delta_dir = NULL; /* Where to store completed deltas. */
30char *base_dir = NULL; /* The tree to apply deltas to. */
31int delete_after = 0; /* Delete deltas after ctm applies them. */
32
33void apply_complete(void);
34int read_piece(char *input_file);
35int combine_if_complete(char *delta, int pce, int npieces);
36int combine(char *delta, int npieces, char *dname, char *pname, char *tname);
37int decode_line(char *line, char *out_buf);
38int lock_file(char *name);
39
40/*
41 * If given a '-p' flag, read encoded delta pieces from stdin or file
42 * arguments, decode them and assemble any completed deltas. If given
43 * a '-b' flag, pass any completed deltas to 'ctm' for application to
44 * the source tree. The '-d' flag is mandatory, but either of '-p' or
45 * '-b' can be omitted. If given the '-l' flag, notes and errors will
46 * be timestamped and written to the given file.
47 *
48 * Exit status is 0 for success or 1 for indigestible input. That is,
49 * 0 means the encode input pieces were decoded and stored, and 1 means
50 * some input was discarded. If a delta fails to apply, this won't be
51 * reflected in the exit status. In this case, the delta is left in
52 * 'deltadir'.
53 */
54int
55main(int argc, char **argv)
56 {
57 char *log_file = NULL;
58 int status = 0;
59 int fork_ctm = 0;
60
61 err_prog_name(argv[0]);
62
63 OPTIONS("[-Df] [-p piecedir] [-d deltadir] [-b basedir] [-l log] [file ...]")
64 FLAG('D', delete_after)
65 FLAG('f', fork_ctm)
66 STRING('p', piece_dir)
67 STRING('d', delta_dir)
68 STRING('b', base_dir)
69 STRING('l', log_file)
70 ENDOPTS
71
72 if (delta_dir == NULL)
73 usage();
74
75 if (piece_dir == NULL && (base_dir == NULL || argc > 1))
76 usage();
77
78 if (log_file != NULL)
79 err_set_log(log_file);
80
81 /*
82 * Digest each file in turn, or just stdin if no files were given.
83 */
84 if (argc <= 1)
85 {
86 if (piece_dir != NULL)
87 status = read_piece(NULL);
88 }
89 else
90 {
91 while (*++argv != NULL)
92 status |= read_piece(*argv);
93 }
94
95 /*
96 * Maybe it's time to look for and apply completed deltas with ctm.
97 *
98 * Shall we report back to sendmail immediately, and let a child do
99 * the work? Sendmail will be waiting for us to complete, delaying
100 * other mail, and possibly some intermediate process (like MH slocal)
101 * will terminate us if we take too long!
102 *
103 * If fork() fails, it's unlikely we'll be able to run ctm, so give up.
104 * Also, the child exit status is unimportant.
105 */
106 if (base_dir != NULL)
107 if (!fork_ctm || fork() == 0)
108 apply_complete();
109
110 return status;
111 }
112
113
114/*
115 * Construct the file name of a piece of a delta.
116 */
117#define mk_piece_name(fn,d,p,n) \
118 sprintf((fn), "%s/%s+%03d-%03d", piece_dir, (d), (p), (n))
119
120/*
121 * Construct the file name of an assembled delta.
122 */
123#define mk_delta_name(fn,d) \
124 sprintf((fn), "%s/%s", delta_dir, (d))
125
126/*
127 * If the next required delta is now present, let ctm lunch on it and any
128 * contiguous deltas.
129 */
130void
131apply_complete()
132 {
133 int i, dn;
134 int lfd;
135 FILE *fp, *ctm;
136 struct stat sb;
137 char class[20];
138 char delta[30];
139 char junk[2];
140 char fname[PATH_MAX];
141 char here[PATH_MAX];
142 char buf[PATH_MAX*2];
143
144 /*
145 * Grab a lock on the ctm mutex file so that we can be sure we are
146 * working alone, not fighting another ctm_rmail!
147 */
148 strcpy(fname, delta_dir);
149 strcat(fname, "/.mutex_apply");
150 if ((lfd = lock_file(fname)) < 0)
151 return;
152
153 /*
154 * Find out which delta ctm needs next.
155 */
156 sprintf(fname, "%s/%s", base_dir, CTM_STATUS);
157 if ((fp = fopen(fname, "r")) == NULL)
158 {
159 close(lfd);
160 return;
161 }
162
163 i = fscanf(fp, "%s %d %c", class, &dn, junk);
164 fclose(fp);
165 if (i != 2)
166 {
167 close(lfd);
168 return;
169 }
170
171 /*
172 * We might need to convert the delta filename to an absolute pathname.
173 */
174 here[0] = '\0';
175 if (delta_dir[0] != '/')
176 {
177 getcwd(here, sizeof(here)-1);
178 i = strlen(here) - 1;
179 if (i >= 0 && here[i] != '/')
180 {
181 here[++i] = '/';
182 here[++i] = '\0';
183 }
184 }
185
186 /*
187 * Keep applying deltas until we run out or something bad happens.
188 */
189 for (;;)
190 {
191 sprintf(delta, "%s.%04d.gz", class, ++dn);
192 mk_delta_name(fname, delta);
193
194 if (stat(fname, &sb) < 0)
195 break;
196
197 sprintf(buf, "(cd %s && ctm %s%s) 2>&1", base_dir, here, fname);
198 if ((ctm = popen(buf, "r")) == NULL)
199 {
200 err("ctm failed to apply %s", delta);
201 break;
202 }
203
204 while (fgets(buf, sizeof(buf), ctm) != NULL)
205 {
206 i = strlen(buf) - 1;
207 if (i >= 0 && buf[i] == '\n')
208 buf[i] = '\0';
209 err("ctm: %s", buf);
210 }
211
212 if (pclose(ctm) != 0)
213 {
214 err("ctm failed to apply %s", delta);
215 break;
216 }
217
218 if (delete_after)
219 unlink(fname);
220
221 err("%s applied%s", delta, delete_after ? " and deleted" : "");
222 }
223
224 /*
225 * Closing the lock file clears the lock.
226 */
227 close(lfd);
228 }
229
230
231/*
232 * This cheap plastic checksum effectively rotates our checksum-so-far
233 * left one, then adds the character. We only want 16 bits of it, and
234 * don't care what happens to the rest. It ain't much, but it's small.
235 */
236#define add_ck(sum,x) \
237 ((sum) += ((x)&0xff) + (sum) + (((sum)&0x8000) ? 1 : 0))
238
239
240/*
241 * Decode the data between BEGIN and END, and stash it in the staging area.
242 * Multiple pieces can be present in a single file, bracketed by BEGIN/END.
243 * If we have all pieces of a delta, combine them. Returns 0 on success,
244 * and 1 for any sort of failure.
245 */
246int
247read_piece(char *input_file)
248 {
249 int status = 0;
250 FILE *ifp, *ofp = 0;
251 int decoding = 0;
252 int got_one = 0;
253 int line_no = 0;
254 int i, n;
255 int pce, npieces;
256 unsigned claimed_cksum;
257 unsigned short cksum = 0;
258 char out_buf[200];
259 char line[200];
260 char delta[30];
261 char pname[PATH_MAX];
262 char tname[PATH_MAX];
263 char junk[2];
264
265 ifp = stdin;
266 if (input_file != NULL && (ifp = fopen(input_file, "r")) == NULL)
267 {
268 err("cannot open '%s' for reading", input_file);
269 return 1;
270 }
271
272 while (fgets(line, sizeof(line), ifp) != NULL)
273 {
274 line_no++;
275
276 /*
277 * Remove all trailing white space.
278 */
279 i = strlen(line) - 1;
280 while (i > 0 && isspace(line[i]))
281 line[--i] = '\0';
282
283 /*
276 * Look for the beginning of an encoded piece.
277 */
278 if (!decoding)
279 {
280 char *s;
281
282 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c",
283 delta, &pce, &npieces, junk) != 3)
284 continue;
285
286 while ((s = strchr(delta, '/')) != NULL)
287 *s = '_';
288
289 got_one++;
290 strcpy(tname, piece_dir);
291 strcat(tname, "/p.XXXXXX");
292 if (mktemp(tname) == NULL)
293 {
294 err("*mktemp: '%s'", tname);
295 status++;
296 continue;
297 }
298 if ((ofp = fopen(tname, "w")) == NULL)
299 {
300 err("cannot open '%s' for writing", tname);
301 status++;
302 continue;
303 }
304
305 cksum = 0xffff;
306 decoding++;
307 continue;
308 }
309
310 /*
311 * We are decoding. Stop if we see the end flag.
312 */
313 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
314 {
315 int e;
316
317 decoding = 0;
318
319 fflush(ofp);
320 e = ferror(ofp);
321 fclose(ofp);
322
323 if (e)
324 err("error writing %s", tname);
325
326 if (cksum != claimed_cksum)
327 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
328
329 if (e || cksum != claimed_cksum)
330 {
331 err("%s %d/%d discarded", delta, pce, npieces);
332 unlink(tname);
333 status++;
334 continue;
335 }
336
337 mk_piece_name(pname, delta, pce, npieces);
338 if (rename(tname, pname) < 0)
339 {
340 err("*rename: '%s' to '%s'", tname, pname);
341 err("%s %d/%d lost!", delta, pce, npieces);
342 unlink(tname);
343 status++;
344 continue;
345 }
346
347 err("%s %d/%d stored", delta, pce, npieces);
348
349 if (!combine_if_complete(delta, pce, npieces))
350 status++;
351 continue;
352 }
353
354 /*
355 * Must be a line of encoded data. Decode it, sum it, and save it.
356 */
357 n = decode_line(line, out_buf);
358 if (n <= 0)
359 {
360 err("line %d: illegal character: '%c'", line_no, line[-n]);
361 err("%s %d/%d discarded", delta, pce, npieces);
362
363 fclose(ofp);
364 unlink(tname);
365
366 status++;
367 decoding = 0;
368 continue;
369 }
370
371 for (i = 0; i < n; i++)
372 add_ck(cksum, out_buf[i]);
373
374 fwrite(out_buf, sizeof(char), n, ofp);
375 }
376
377 if (decoding)
378 {
379 err("truncated file");
380 err("%s %d/%d discarded", delta, pce, npieces);
381
382 fclose(ofp);
383 unlink(tname);
384
385 status++;
386 }
387
388 if (ferror(ifp))
389 {
390 err("error reading %s", input_file == NULL ? "stdin" : input_file);
391 status++;
392 }
393
394 if (input_file != NULL)
395 fclose(ifp);
396
397 if (!got_one)
398 {
399 err("message contains no delta");
400 status++;
401 }
402
403 return (status != 0);
404 }
405
406
407/*
408 * Put the pieces together to form a delta, if they are all present.
409 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
410 */
411int
412combine_if_complete(char *delta, int pce, int npieces)
413 {
414 int i, e;
415 int lfd;
416 struct stat sb;
417 char pname[PATH_MAX];
418 char dname[PATH_MAX];
419 char tname[PATH_MAX];
420
421 /*
422 * We can probably just rename() it into place if it is a small delta.
423 */
424 if (npieces == 1)
425 {
426 mk_delta_name(dname, delta);
427 mk_piece_name(pname, delta, 1, 1);
428 if (rename(pname, dname) == 0)
429 {
430 err("%s complete", delta);
431 return 1;
432 }
433 }
434
435 /*
436 * Grab a lock on the reassembly mutex file so that we can be sure we are
437 * working alone, not fighting another ctm_rmail!
438 */
439 strcpy(tname, delta_dir);
440 strcat(tname, "/.mutex_build");
441 if ((lfd = lock_file(tname)) < 0)
442 return 0;
443
444 /*
445 * Are all of the pieces present? Of course the current one is,
446 * unless all pieces are missing because another ctm_rmail has
447 * processed them already.
448 */
449 for (i = 1; i <= npieces; i++)
450 {
451 if (i == pce)
452 continue;
453 mk_piece_name(pname, delta, i, npieces);
454 if (stat(pname, &sb) < 0)
455 {
456 close(lfd);
457 return 1;
458 }
459 }
460
461 /*
462 * Stick them together. Let combine() use our file name buffers, since
463 * we're such good buddies. :-)
464 */
465 e = combine(delta, npieces, dname, pname, tname);
466 close(lfd);
467 return e;
468 }
469
470
471/*
472 * Put the pieces together to form a delta.
473 * Returns 1 on success, and 0 on failure.
474 * Note: dname, pname, and tname are room for some file names that just
475 * happened to by lying around in the calling routine. Waste not, want not!
476 */
477int
478combine(char *delta, int npieces, char *dname, char *pname, char *tname)
479 {
480 FILE *dfp, *pfp;
481 int i, n, e;
482 char buf[BUFSIZ];
483
484 strcpy(tname, delta_dir);
485 strcat(tname, "/d.XXXXXX");
486 if (mktemp(tname) == NULL)
487 {
488 err("*mktemp: '%s'", tname);
489 return 0;
490 }
491 if ((dfp = fopen(tname, "w")) == NULL)
492 {
493 err("cannot open '%s' for writing", tname);
494 return 0;
495 }
496
497 /*
498 * Reconstruct the delta by reading each piece in order.
499 */
500 for (i = 1; i <= npieces; i++)
501 {
502 mk_piece_name(pname, delta, i, npieces);
503 if ((pfp = fopen(pname, "r")) == NULL)
504 {
505 err("cannot open '%s' for reading", pname);
506 fclose(dfp);
507 unlink(tname);
508 return 0;
509 }
510 while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
511 fwrite(buf, sizeof(char), n, dfp);
512 e = ferror(pfp);
513 fclose(pfp);
514 if (e)
515 {
516 err("error reading '%s'", pname);
517 fclose(dfp);
518 unlink(tname);
519 return 0;
520 }
521 }
522 fflush(dfp);
523 e = ferror(dfp);
524 fclose(dfp);
525 if (e)
526 {
527 err("error writing '%s'", tname);
528 unlink(tname);
529 return 0;
530 }
531
532 mk_delta_name(dname, delta);
533 if (rename(tname, dname) < 0)
534 {
535 err("*rename: '%s' to '%s'", tname, dname);
536 unlink(tname);
537 return 0;
538 }
539
540 /*
541 * Throw the pieces away.
542 */
543 for (i = 1; i <= npieces; i++)
544 {
545 mk_piece_name(pname, delta, i, npieces);
546 if (unlink(pname) < 0)
547 err("*unlink: '%s'", pname);
548 }
549
550 err("%s complete", delta);
551 return 1;
552 }
553
554
555/*
556 * MIME BASE64 decode table.
557 */
558static unsigned char from_b64[0x80] =
559 {
560 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
561 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
562 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
563 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
564 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
565 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
566 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
567 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
568 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
569 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
570 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
571 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
572 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
573 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
574 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
575 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
576 };
577
578
579/*
580 * Decode a line of ASCII into binary. Returns the number of bytes in
581 * the output buffer, or < 0 on indigestable input. Error output is
582 * the negative of the index of the inedible character.
583 */
584int
585decode_line(char *line, char *out_buf)
586 {
587 unsigned char *ip = (unsigned char *)line;
588 unsigned char *op = (unsigned char *)out_buf;
589 unsigned long bits;
590 unsigned x;
591
592 for (;;)
593 {
594 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
595 break;
596 bits = x << 18;
597 ip++;
598 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
599 {
600 bits |= x << 12;
601 *op++ = bits >> 16;
602 ip++;
603 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
604 {
605 bits |= x << 6;
606 *op++ = bits >> 8;
607 ip++;
608 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
609 {
610 bits |= x;
611 *op++ = bits;
612 ip++;
613 }
614 }
615 }
616 }
617
618 if (*ip == '\0' || *ip == '\n')
619 return op - (unsigned char *)out_buf;
620 else
621 return -(ip - (unsigned char *)line);
622 }
623
624
625/*
626 * Create and lock the given file.
627 *
628 * Clearing the lock is as simple as closing the file descriptor we return.
629 */
630int
631lock_file(char *name)
632 {
633 int lfd;
634
635 if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
636 {
637 err("*open: '%s'", name);
638 return -1;
639 }
640 if (flock(lfd, LOCK_EX) < 0)
641 {
642 close(lfd);
643 err("*flock: '%s'", name);
644 return -1;
645 }
646 return lfd;
647 }
284 * Look for the beginning of an encoded piece.
285 */
286 if (!decoding)
287 {
288 char *s;
289
290 if (sscanf(line, "CTM_MAIL BEGIN %s %d %d %c",
291 delta, &pce, &npieces, junk) != 3)
292 continue;
293
294 while ((s = strchr(delta, '/')) != NULL)
295 *s = '_';
296
297 got_one++;
298 strcpy(tname, piece_dir);
299 strcat(tname, "/p.XXXXXX");
300 if (mktemp(tname) == NULL)
301 {
302 err("*mktemp: '%s'", tname);
303 status++;
304 continue;
305 }
306 if ((ofp = fopen(tname, "w")) == NULL)
307 {
308 err("cannot open '%s' for writing", tname);
309 status++;
310 continue;
311 }
312
313 cksum = 0xffff;
314 decoding++;
315 continue;
316 }
317
318 /*
319 * We are decoding. Stop if we see the end flag.
320 */
321 if (sscanf(line, "CTM_MAIL END %d %c", &claimed_cksum, junk) == 1)
322 {
323 int e;
324
325 decoding = 0;
326
327 fflush(ofp);
328 e = ferror(ofp);
329 fclose(ofp);
330
331 if (e)
332 err("error writing %s", tname);
333
334 if (cksum != claimed_cksum)
335 err("checksum: read %d, calculated %d", claimed_cksum, cksum);
336
337 if (e || cksum != claimed_cksum)
338 {
339 err("%s %d/%d discarded", delta, pce, npieces);
340 unlink(tname);
341 status++;
342 continue;
343 }
344
345 mk_piece_name(pname, delta, pce, npieces);
346 if (rename(tname, pname) < 0)
347 {
348 err("*rename: '%s' to '%s'", tname, pname);
349 err("%s %d/%d lost!", delta, pce, npieces);
350 unlink(tname);
351 status++;
352 continue;
353 }
354
355 err("%s %d/%d stored", delta, pce, npieces);
356
357 if (!combine_if_complete(delta, pce, npieces))
358 status++;
359 continue;
360 }
361
362 /*
363 * Must be a line of encoded data. Decode it, sum it, and save it.
364 */
365 n = decode_line(line, out_buf);
366 if (n <= 0)
367 {
368 err("line %d: illegal character: '%c'", line_no, line[-n]);
369 err("%s %d/%d discarded", delta, pce, npieces);
370
371 fclose(ofp);
372 unlink(tname);
373
374 status++;
375 decoding = 0;
376 continue;
377 }
378
379 for (i = 0; i < n; i++)
380 add_ck(cksum, out_buf[i]);
381
382 fwrite(out_buf, sizeof(char), n, ofp);
383 }
384
385 if (decoding)
386 {
387 err("truncated file");
388 err("%s %d/%d discarded", delta, pce, npieces);
389
390 fclose(ofp);
391 unlink(tname);
392
393 status++;
394 }
395
396 if (ferror(ifp))
397 {
398 err("error reading %s", input_file == NULL ? "stdin" : input_file);
399 status++;
400 }
401
402 if (input_file != NULL)
403 fclose(ifp);
404
405 if (!got_one)
406 {
407 err("message contains no delta");
408 status++;
409 }
410
411 return (status != 0);
412 }
413
414
415/*
416 * Put the pieces together to form a delta, if they are all present.
417 * Returns 1 on success (even if we didn't do anything), and 0 on failure.
418 */
419int
420combine_if_complete(char *delta, int pce, int npieces)
421 {
422 int i, e;
423 int lfd;
424 struct stat sb;
425 char pname[PATH_MAX];
426 char dname[PATH_MAX];
427 char tname[PATH_MAX];
428
429 /*
430 * We can probably just rename() it into place if it is a small delta.
431 */
432 if (npieces == 1)
433 {
434 mk_delta_name(dname, delta);
435 mk_piece_name(pname, delta, 1, 1);
436 if (rename(pname, dname) == 0)
437 {
438 err("%s complete", delta);
439 return 1;
440 }
441 }
442
443 /*
444 * Grab a lock on the reassembly mutex file so that we can be sure we are
445 * working alone, not fighting another ctm_rmail!
446 */
447 strcpy(tname, delta_dir);
448 strcat(tname, "/.mutex_build");
449 if ((lfd = lock_file(tname)) < 0)
450 return 0;
451
452 /*
453 * Are all of the pieces present? Of course the current one is,
454 * unless all pieces are missing because another ctm_rmail has
455 * processed them already.
456 */
457 for (i = 1; i <= npieces; i++)
458 {
459 if (i == pce)
460 continue;
461 mk_piece_name(pname, delta, i, npieces);
462 if (stat(pname, &sb) < 0)
463 {
464 close(lfd);
465 return 1;
466 }
467 }
468
469 /*
470 * Stick them together. Let combine() use our file name buffers, since
471 * we're such good buddies. :-)
472 */
473 e = combine(delta, npieces, dname, pname, tname);
474 close(lfd);
475 return e;
476 }
477
478
479/*
480 * Put the pieces together to form a delta.
481 * Returns 1 on success, and 0 on failure.
482 * Note: dname, pname, and tname are room for some file names that just
483 * happened to by lying around in the calling routine. Waste not, want not!
484 */
485int
486combine(char *delta, int npieces, char *dname, char *pname, char *tname)
487 {
488 FILE *dfp, *pfp;
489 int i, n, e;
490 char buf[BUFSIZ];
491
492 strcpy(tname, delta_dir);
493 strcat(tname, "/d.XXXXXX");
494 if (mktemp(tname) == NULL)
495 {
496 err("*mktemp: '%s'", tname);
497 return 0;
498 }
499 if ((dfp = fopen(tname, "w")) == NULL)
500 {
501 err("cannot open '%s' for writing", tname);
502 return 0;
503 }
504
505 /*
506 * Reconstruct the delta by reading each piece in order.
507 */
508 for (i = 1; i <= npieces; i++)
509 {
510 mk_piece_name(pname, delta, i, npieces);
511 if ((pfp = fopen(pname, "r")) == NULL)
512 {
513 err("cannot open '%s' for reading", pname);
514 fclose(dfp);
515 unlink(tname);
516 return 0;
517 }
518 while ((n = fread(buf, sizeof(char), sizeof(buf), pfp)) != 0)
519 fwrite(buf, sizeof(char), n, dfp);
520 e = ferror(pfp);
521 fclose(pfp);
522 if (e)
523 {
524 err("error reading '%s'", pname);
525 fclose(dfp);
526 unlink(tname);
527 return 0;
528 }
529 }
530 fflush(dfp);
531 e = ferror(dfp);
532 fclose(dfp);
533 if (e)
534 {
535 err("error writing '%s'", tname);
536 unlink(tname);
537 return 0;
538 }
539
540 mk_delta_name(dname, delta);
541 if (rename(tname, dname) < 0)
542 {
543 err("*rename: '%s' to '%s'", tname, dname);
544 unlink(tname);
545 return 0;
546 }
547
548 /*
549 * Throw the pieces away.
550 */
551 for (i = 1; i <= npieces; i++)
552 {
553 mk_piece_name(pname, delta, i, npieces);
554 if (unlink(pname) < 0)
555 err("*unlink: '%s'", pname);
556 }
557
558 err("%s complete", delta);
559 return 1;
560 }
561
562
563/*
564 * MIME BASE64 decode table.
565 */
566static unsigned char from_b64[0x80] =
567 {
568 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
569 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
570 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
571 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
572 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
573 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
574 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
575 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
576 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
577 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
578 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
579 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
580 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
581 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
582 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
583 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
584 };
585
586
587/*
588 * Decode a line of ASCII into binary. Returns the number of bytes in
589 * the output buffer, or < 0 on indigestable input. Error output is
590 * the negative of the index of the inedible character.
591 */
592int
593decode_line(char *line, char *out_buf)
594 {
595 unsigned char *ip = (unsigned char *)line;
596 unsigned char *op = (unsigned char *)out_buf;
597 unsigned long bits;
598 unsigned x;
599
600 for (;;)
601 {
602 if (*ip >= 0x80 || (x = from_b64[*ip]) >= 0x40)
603 break;
604 bits = x << 18;
605 ip++;
606 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
607 {
608 bits |= x << 12;
609 *op++ = bits >> 16;
610 ip++;
611 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
612 {
613 bits |= x << 6;
614 *op++ = bits >> 8;
615 ip++;
616 if (*ip < 0x80 && (x = from_b64[*ip]) < 0x40)
617 {
618 bits |= x;
619 *op++ = bits;
620 ip++;
621 }
622 }
623 }
624 }
625
626 if (*ip == '\0' || *ip == '\n')
627 return op - (unsigned char *)out_buf;
628 else
629 return -(ip - (unsigned char *)line);
630 }
631
632
633/*
634 * Create and lock the given file.
635 *
636 * Clearing the lock is as simple as closing the file descriptor we return.
637 */
638int
639lock_file(char *name)
640 {
641 int lfd;
642
643 if ((lfd = open(name, O_WRONLY|O_CREAT, 0600)) < 0)
644 {
645 err("*open: '%s'", name);
646 return -1;
647 }
648 if (flock(lfd, LOCK_EX) < 0)
649 {
650 close(lfd);
651 err("*flock: '%s'", name);
652 return -1;
653 }
654 return lfd;
655 }