mess822x 1.23
mess822x
Loading...
Searching...
No Matches
822mimeparts.c
Go to the documentation of this file.
1#include <unistd.h>
2#include "buffer.h"
3#include "logmsg.h"
4#include "getln.h"
5#include "mess822.h"
6#include "exit.h"
7#include "case.h"
8#include "str.h"
9#include "open.h"
10#include "fmt.h"
11#include "getoptb.h"
12#include "byte.h"
13
14#define WHO "822mimeparts"
15
16#define LINELEN 1000
17
19int flag;
20int slink = 0;
21int verbose = 0;
22stralloc line = {0};
23stralloc valuem = {0};
24stralloc valuet = {0};
25stralloc valuec = {0};
26stralloc boundary = {0};
27stralloc mname = {0};
28stralloc bodybuf = {0};
29stralloc partbuf = {0};
30stralloc partname = {0};
31stralloc alias = {0};
32
33static void nomem()
34{
35 logmsg(WHO,111,FATAL,"out of memory");
36}
37
38static int issafe(char ch)
39{
40 if ((ch >= 'a') && (ch <= 'z')) return 1;
41 if ((ch >= 'A') && (ch <= 'Z')) return 1;
42 if ((ch >= '0') && (ch <= '9')) return 1;
43 return 0;
44}
45
46static void die_write(char *fn) { unlink(fn); _exit(1); };
47
48/* We use the "Message-ID" line and take
49 ten valid and significant characters
50 out of it to be used as file name part.
51*/
52char name[11] = { '0', '0', '0', 'u', 'n', 'k', 'n', 'o', 'w', 'n', '\0' };
53char pname[7] = { '0', '0', '0', '0', '0', '0', '\0' };
54
55static int msgname(char *out,const char *in)
56{
57 int c;
58 int j = 9;
59
60 int at = str_chr(in,'@');
61 if (in[at] == '@' && at > 9) {
62 int i = 1;
63 do {
64 c = *(in + at - i);
65 if (issafe(c)) out[j--] = c;
66 i++;
67 } while (j >= 0);
68 }
69
70 return 0;
71}
72
73static int mime_partname(char *out,char *in,int len)
74{
75 int c;
76 int j = 6, i = 1;
77
78 do {
79 c = *(in + len - i);
80 if (issafe(c)) out[j--] = c;
81 i++;
82 } while (j >= 0);
83
84 return 0;
85}
86
88buffer bo;
89
90char *mime_type(int type)
91{
92 switch(type) {
93 case 1: case -1: return "7bit";
94 case 2: case -2: return "8bit";
95 case 3: case -3: return "base64";
96 case 4: case -4: return "binary";
97 case 5: case -5: return "quoted-printable";
98 default: return "unkown";
99 }
100 return 0;
101}
102
103static int printpart(char *name,stralloc *in,int mimetype,stralloc *alias)
104{
105 int fd;
106 int out = 0;
107 char *type;
108 char size[FMT_ULONG];
109 int len;
110
111 case_lowers(name);
112 fd = open_trunc(name);
113 if (fd == -1) die_write(name);
114
115 type = mime_type(mimetype);
116 len = in->len;
117 size[fmt_ulong(size,(unsigned long) len)] = 0;
118
119 buffer_init(&bo,buffer_unixwrite,fd,outbuf,sizeof(outbuf));
120
121 do {
122 out = (in->len < LINELEN) ? in->len : LINELEN;
123 buffer_put(&bo,in->s,out);
124 in->len -= out;
125 in->s += out;
126 } while (in->len > 0);
127
128 if (buffer_flush(&bo)) die_write(name);
129 if (close(fd) == -1) die_write(name);
130
131 if (alias->len) {
132 logmsg(WHO,0,INFO,B("Created: ",name," (",type,", ",size," byte) [",alias->s,"]."));
133 if (slink) {
134 if (symlink(name,alias->s) == -1) logmsg(WHO,111,ERROR,B("Can't symlink: ",name," to: ",alias->s));
135 }
136 } else
137 logmsg(WHO,0,INFO,B("Created: ",name," (",type,", ",size," byte)."));
138
139 return 0;
140}
141
144 { "Message-ID", &flag, 0, &valuem, 0, 0, 0 } , { 0, 0, 0, 0, 0, 0, 0 }
145} ;
148 { "Content-Type", &flag, 0, &valuet, 0, 0, 0 } , { 0, 0, 0, 0, 0, 0, 0 }
149} ;
152 { "Content-Transfer-Encoding", &flag, 0, &valuec, 0, 0, 0 } , { 0, 0, 0, 0, 0, 0, 0 }
153} ;
154
155void msg_bodyproc(int mimetype,int start,int end)
156{
157 int r = 0;
158 char sizes[FMT_ULONG];
159 char sizee[FMT_ULONG];
160
161 if (!stralloc_copys(&partname,name)) nomem();
162 if (!stralloc_cats(&partname,".body,0")) nomem();
163 if (!stralloc_0(&partname)) nomem();
164
165 stralloc decoded = {0};
166 if (!stralloc_ready(&decoded,bodybuf.len)) nomem();
167
168 if (mimetype == -1) {
169 r = mess822_b64decode(&decoded,bodybuf.s,bodybuf.len,1);
170 if (r < 0) logmsg(WHO,111,WARN,"malformed base64 mime part");
171 } else if (mimetype == -2) {
172 r = mess822_qpdecode(&decoded,bodybuf.s,bodybuf.len,0);
173 if (r < 0) logmsg(WHO,111,WARN,"malformed quoted-printable part");
174 }
175
176 if (r > 0) {
177 if (!printpart(partname.s,&decoded,mimetype,&alias)) {
178 sizes[fmt_ulong(sizes,(unsigned long) start + 3)] = 0;
179 sizee[fmt_ulong(sizee,(unsigned long) end + 2)] = 0;
180 if (verbose) logmsg(WHO,0,INFO,B("Start of part: @line=",sizes," End of part: @line=",sizee));
181 }
182 }
183}
184
185void mime_partproc(stralloc *bound,int mimetype,int start,int end)
186{
187 int r = 0;
188 char sizes[FMT_ULONG];
189 char sizee[FMT_ULONG];
190 static int num = 0; // count of MIME parts
191
192 if (partbuf.len < 3) return;
193
194 num++;
195 sizes[fmt_ulong(sizes,(unsigned long) num)] = 0;
196 if (!stralloc_0(&partbuf)) nomem();
197
198 mime_partname(pname,bound->s,bound->len);
199 if (!stralloc_copys(&partname,name)) nomem();
200 if (!stralloc_cats(&partname,".")) nomem();
201 if (!stralloc_cats(&partname,pname)) nomem();
202 if (!stralloc_cats(&partname,",")) nomem();
203 if (!stralloc_cats(&partname,sizes)) nomem();
204 if (!stralloc_0(&partname)) nomem();
205
206 stralloc decoded = {0};
207 if (!stralloc_ready(&decoded,partbuf.len)) nomem();
208
209 if (mimetype == 3) {
210 r = mess822_b64decode(&decoded,partbuf.s,partbuf.len,0);
211 if (r < 0) logmsg(WHO,111,INFO,"malformed base64 mime part");
212 } else if (mimetype == 5) {
213 r = mess822_qpdecode(&decoded,partbuf.s,partbuf.len,0);
214 if (r < 0) logmsg(WHO,111,INFO,"malformed quoted-printable part");
215 }
216
217 if (r > 0) {
218 if (!printpart(partname.s,&decoded,mimetype,&alias)) {
219 sizes[fmt_ulong(sizes,(unsigned long) start + 3)] = 0;
220 sizee[fmt_ulong(sizee,(unsigned long) end + 2)] = 0;
221 if (verbose) logmsg(WHO,0,INFO,B("Start of part: @line=",sizes," End of part: @line=",sizee));
222 }
223 }
224
225 // Done; reset counters
226
227 if (!stralloc_copys(&partbuf,"")) nomem();
228 partbuf.len = 0;
229
230 return;
231}
232
233// =?iso-8859-1?B?TGVkIEZvbGllX01lZXNlbmJ1cmcgNWVyTEVEX0F132VuYXVmbmFo?=
234
235int decode_name(stralloc *name)
236{
237 unsigned char ch;
238 int seenq = 0;
239 int mimetype = 0;
240 int done = 0;
241
242 if (*(name->s) != '=' && *(name->s + 1) != '?') return 0;
243
244 stralloc in = {0}; // token for decoding
245 stralloc out = {0}; // output of entire name
246 stralloc word = {0}; // result of decoding token
247 if (!stralloc_copys(&in,"")) nomem();
248 if (!stralloc_copys(&out,"")) nomem();
249
250 for (int i = 0; i < name->len; i++) {
251 ch = name->s[i];
252
253 if (ch == '\0') break;
254 if (ch == '?') seenq++;
255 if (seenq == 2) {
256 if (!stralloc_copys(&in,"")) nomem();
257 mimetype = 0;
258 if (ch == 'q' || ch == 'Q') { // new QP word
259 mimetype = 5;
260 } else if (ch == 'b' || ch == 'B') { // new BASE64 word
261 mimetype = 3;
262 }
263 continue;
264 }
265
266 // Get token
267
268 if (mimetype) {
269 if (seenq == 3 && ch != '?') if (!stralloc_append(&in,&ch)) nomem();
270 if (seenq == 4) { done = 1; if (!stralloc_0(&in)) nomem(); }
271 } else
272 if (!stralloc_append(&in,&ch)) nomem();
273
274 if (i == name->len - 1) done = 1; // nothing to follow
275 if (!done) continue;
276
277 // For each token -> decode
278
279 switch (mimetype) {
280 case 3: if (!stralloc_copys(&word,"")) nomem();
281 if ((done = mess822_b64decode(&word,in.s,in.len,4648)) > 0) {
282 if (!stralloc_catb(&out,word.s,done)) nomem();
283 } else {
284 logmsg(WHO,0,WARN,B("error in decoding 'Name:' ",word.s));
285 if (!stralloc_catb(&out,in.s,in.len)) nomem();
286 } done = 0; seenq = 0; break;
287 case 5: if (!stralloc_copys(&word,"")) nomem();
288 if ((done = mess822_qpdecode(&word,in.s,in.len,2047)) > 0) {
289 if (!stralloc_catb(&out,word.s,done)) nomem();
290 } else {
291 if (!stralloc_catb(&out,in.s,in.len)) nomem();
292 logmsg(WHO,0,WARN,B("error in decoding 'Name:' ",word.s));
293 if (!stralloc_catb(&out,in.s,in.len)) nomem();
294 } done = 0; seenq = 0; break;
295 }
296 }
297
298 if (!stralloc_copy(name,&out)) nomem();
299 if (!stralloc_0(name)) nomem();
300
301 return out.len - 1;
302}
303
304int b64name(stralloc *name)
305{
306 int r;
307 unsigned char ch;
308 int seen = 0;
309
310 if (*(name->s) != '=' && *(name->s + 1) != '?') return 0;
311
312 stralloc in = {0};
313 stralloc out = {0};
314
315 if (!stralloc_copys(&in,"")) nomem();
316
317 for (int i = 2; i < name->len; i++) {
318 ch = name->s[i];
319 if (ch == '?') { seen++; continue; }
320 if (seen == 2) if (!stralloc_append(&in,&ch)) nomem();
321 }
322 if (!stralloc_0(&in)) nomem();
323 r = mess822_b64decode(&out,in.s,in.len,4648);
324
325 if (r <= 0) {
326 logmsg(WHO,0,WARN,B("error in BASE64 decoding: ",in.s));
327 if (!stralloc_copy(name,&in)) nomem();
328 } else
329 if (!stralloc_copy(name,&out)) nomem();
330
331 return out.len - 1;
332}
333
334int attachment_name(stralloc *name,stralloc *in)
335{
336 static int seen = 0; // seen = 1: name; seen = 2: catching; seen = 3: caught all;
337 unsigned char ch;
338 int seena = 0;
339
340 ch = *in->s;
341 if (seen == 2)
342 if (ch == 'C' || ch == '\n') seen = 3; // next Content- or empty line
343
344 if (seen == 0 && in->len > 10) { // 'Content' and 'name' required
345 for (int i = 0; i < in->len - 5; i++) {
346 if (!case_diffb(in->s + i,5,"name=")) { // initial line
347 if (!stralloc_copys(name,"")) nomem();
348 seen = 1;
349 i += 5;
350
351 for (int j = i; j < in->len - 2; j++) {
352 ch = in->s[j];
353 if (ch == '"') { seena++; continue; } // " => sometimes enclosed
354 if (ch == ';') { seen = 3; goto DONE; } // ; => end-of-token; done
355 if (!stralloc_append(name,&ch)) nomem();
356 }
357 seen = 2;
358 if (seena == 2) { seen = 3; goto DONE; }
359 return 1;
360 }
361 }
362 }
363
364 if (seen && seen < 3) {
365 if (ch == ' ' || ch == '\t') {
366 for (int j = 1 ; j < in->len - 2; j++) {
367 ch = in->s[j];
368 if (ch == '"') { seena++; continue; } // maybe
369 if (ch == ';') { seen = 3; goto DONE; } // done
370 if (!stralloc_append(name,&ch)) nomem();
371 seen = 3;
372 }
373 if (seen == 3 || seena == 2) goto DONE;
374 return 1;
375 }
376 if (ch == '\n') { seen == 3; goto DONE; } // empty line follows
377 return 1;
378 }
379
380 DONE:
381
382 if (seen == 3) { // Got name
383 if (!stralloc_0(name)) nomem();
384 seen = 0;
385 seena = 0;
386 return 2;
387 }
388 seen = 0;
389
390 return 0;
391}
392
393int main(int argc,char **argv)
394{
395 int mimetype = 0; // +/-3: base64, +/-5: quoted-printable + others; 10: PGP pub key
396 int part[4] = {0, 0, 0, 0}; // part[0]=part#, part[1]=linestart# in body, part[2]=lineend# in body, part[3]=state
397 unsigned char ch;
398 int len = 0;
399 int done = 0;
400 int seen = 0;
401 int skip = 0;
402 int opt;
403
404 while ((opt = getoptb(argc,argv,"hlv")) != opteof)
405 switch (opt) {
406 case 'h': logmsg(WHO,99,USAGE,"822mimeparts [-v|-l] < message."); break;
407 case 'l': slink = 1; break;
408 case 'v': verbose = 1; break;
409 default : logmsg(WHO,99,USAGE,"822mimeparts [-v|-l] < message."); break;
410 }
411
412 // The headers we are looking for:
413
414 if (!mess822_begin(&msid,hm)) nomem();
415 if (!mess822_begin(&type,ht)) nomem();
416 if (!mess822_begin(&enco,hc)) nomem();
417
418 for (len = 0;;len++) {
419 if (getln(buffer_0,&line,&match,'\n') == -1)
420 logmsg(WHO,111,FATAL,"unable to read input: ");
421 if (!mess822_ok(&line)) break;
422 if (!mess822_line(&msid,&line)) nomem();
423 if (!mess822_line(&type,&line)) nomem();
424 if (!mess822_line(&enco,&line)) nomem();
425 }
426
427 if (match && line.len > 1)
428 logmsg(WHO,111,ERROR,"malformed message (first non-header line not blank)");
429
430 // Message Name
431
432 if (!mess822_end(&msid)) nomem();
433 if (!stralloc_copyb(&mname,valuem.s,valuem.len)) nomem();
434 if (!stralloc_0(&mname)) nomem();
435 msgname(name,mname.s);
436
437 // Encoding entire message
438
439 if (!mess822_end(&enco)) nomem();
440 if (!case_diffb(valuec.s,16,"quoted-printable")) { mimetype = -2; part[3] = -1; }
441 else if (!case_diffb(valuec.s,6,"base64")) { mimetype = -1; part[3] = -1; }
442 if (mimetype < 0) { if (!stralloc_cats(&bodybuf,"")) return -1; }
443
444 // MIME part boundary from header
445
446 if (!mess822_end(&type)) nomem();
447
448 for (int i = 0; i < valuet.len - 12; i++) {
449 if (*(valuet.s + i) == '\0') break;
450 if (!case_diffb(valuet.s + i,9,"boundary=")) {
451 if (!stralloc_copys(&boundary,"--")) nomem();
452 i += 9;
453
454 for (int j = i; j < valuet.len - 2; j++) {
455 ch = valuet.s[j];
456 if (ch == ';' || ch == '"') continue; // ; => next tokenr, " => sometimes enclosed
457 if (!stralloc_append(&boundary,&ch)) nomem();
458 }
459
460 if (boundary.len > 3) if (!stralloc_0(&boundary)) nomem();
461 }
462 }
463
464 // Message body
465
466 for (int l = len;;l++) {
467 if (getln(buffer_0,&line,&match,'\n') == -1)
468 logmsg(WHO,111,FATAL,"unable to read input: ");
469
470 if (!match) break;
471 if (!stralloc_0(&line)) nomem(); // => line end: \n\0
472
473 // bodybuf is used to capture the entire body; partbuf for a MIME part
474 // Unlike partbuf, bodybuf needs to be unaltered for processing
475
476 if (mimetype < 0) {
477 if (!stralloc_catb(&bodybuf,line.s,line.len - 1)) nomem(); // \n\0
478 if (!part[1]) part[1] = l; part[2] = l;
479 }
480
481 // Get Encoding
482
483 if (case_startb(line.s,26,"Content-Transfer-Encoding:")) {
484 part[0] += 1; part[1] = l; part[3] = 1;
485 mimetype = 0;
486 if (case_startb(line.s + 27,4,"7bit")) mimetype = 1;
487 else if (case_startb(line.s + 27,4,"8bit")) mimetype = 2;
488 else if (case_startb(line.s + 27,6,"base64")) mimetype = 3;
489 else if (case_startb(line.s + 27,6,"binary")) mimetype = 4;
490 else if (case_startb(line.s + 27,16,"quoted-printable")) mimetype = 5;
491 if (!stralloc_copys(&partbuf,"")) nomem(); // mew MIME part
492 continue;
493 }
494
495 // Get Content-Type - check for exceptions
496
497 if (case_startb(line.s,13,"Content-Type:")) {
498 part[3] = 2;
499 if (*(line.s + line.len - 3) == ';') skip = 1; // Apple-Mail
500 if (case_startb(line.s + 14,27,"application/pgp-signature")) mimetype = 10;
501 }
502 if (case_startb(line.s,2,"X-")) continue; // 'Exchange': X-Attachment-ID
503
504 // Get current MIME boundary
505
506 for (int i = 0; i < line.len - 12; i++) {
507 done = 0;
508 if (*(line.s + i) == '\n') break;
509 if (!case_diffb(line.s + i,9,"boundary=")) {
510 if (!stralloc_copys(&boundary,"--")) nomem();
511 i += 9;
512
513 for (int j = i; j < line.len - 2; j++) {
514 ch = line.s[j];
515 if (ch == ';') break; // ; => next token,
516 if (ch == '"') continue; // " => sometimes enclosed
517 if (!stralloc_append(&boundary,&ch)) nomem();
518 }
519
520 if (boundary.len > 3) {
521 if (!stralloc_0(&boundary)) nomem();
522 part[3] = 2;
523 done = 1;
524 break;
525 }
526 }
527 }
528 if (done) continue;
529
530 // Get type of attachment
531
532 if (part[3]) {
533 if (case_startb(line.s,12,"MIME-Version")) { part[3] = 3; continue; }
534 if (case_startb(line.s,25,"Content-Type: application") ||
535 case_startb(line.s,23,"Content-Type: text/html") ||
536 case_startb(line.s,19,"Content-Type: image") ||
537 case_startb(line.s,19,"Content-Type: audio") ||
538 case_startb(line.s,19,"Content-Type: video") ||
539 case_startb(line.s,19,"Content-Type: octet") ||
540 case_startb(line.s,19,"Content-Disposition")) { part[3] = 4; }
541 }
542
543 // Look for attachment name
544
545 if (part[3] == 4) {
546 seen = attachment_name(&alias,&line);
547 if (seen == 1) continue;
548 if (seen == 2) decode_name(&alias);
549 }
550
551 // Got MIME part - discard all none content lines and save rest to 'partbuf'
552
553 if (mimetype > 0) {
554 if (*line.s == '\n') continue;
555 if (case_startb(line.s,10,"Content-")) continue;
556 if (skip == 1) { skip = 0; continue; } // FIXME: Dirty hack
557
558 if (case_startb(line.s,boundary.len - 1,boundary.s)) {
559 mime_partproc(&boundary,mimetype,part[1],part[2]);
560 part[3] = 2; // needs to be pushed back
561 continue;
562 } else {
563 switch (mimetype) {
564 case 3: if (*line.s == ' ' || *line.s == '-' || *line.s == '\t') break;
565 if (!stralloc_catb(&partbuf,line.s,line.len - 2)) nomem(); // \n\0 BASE64
566 part[2] = l; part[3] = 5;
567 break;
568 case 5: if (!stralloc_catb(&partbuf,line.s,line.len - 1)) nomem(); // \n\0
569 part[2] = l; part[3] = 5;
570 break;
571 default: continue;
572 }
573
574 // Reading + safing MIME part finished - process it
575
576 if (mimetype != 5 && !str_diffn(line.s,"--",2)) {
577 mime_partproc(&boundary,mimetype,part[1],part[2]);
578 if (!stralloc_copys(&partbuf,"")) nomem();
579 partbuf.len = 0;
580 part[3] = 2; // needs to be pushed back
581 }
582 }
583 }
584 } // end for loop
585
586 if (mimetype < 0) {
587 if (!stralloc_0(&bodybuf)) nomem();
588 msg_bodyproc(mimetype,part[1],part[2]);
589 }
590
591 _exit(0);
592}
int main()
Definition: 822print.c:351
stralloc out
Definition: b64decode.c:12
int verbose
Definition: 822mimeparts.c:21
stralloc bodybuf
Definition: 822mimeparts.c:28
stralloc mname
Definition: 822mimeparts.c:27
void mime_partproc(stralloc *bound, int mimetype, int start, int end)
Definition: 822mimeparts.c:185
mess822_action hm[]
Definition: 822mimeparts.c:143
char * mime_type(int type)
Definition: 822mimeparts.c:90
char outbuf[LINELEN]
Definition: 822mimeparts.c:87
stralloc valuet
Definition: 822mimeparts.c:24
int attachment_name(stralloc *name, stralloc *in)
Definition: 822mimeparts.c:334
stralloc alias
Definition: 822mimeparts.c:31
int b64name(stralloc *name)
Definition: 822mimeparts.c:304
int slink
Definition: 822mimeparts.c:20
#define LINELEN
Definition: 822mimeparts.c:16
char pname[7]
Definition: 822mimeparts.c:53
mess822_action ht[]
Definition: 822mimeparts.c:147
int decode_name(stralloc *name)
Definition: 822mimeparts.c:235
stralloc valuec
Definition: 822mimeparts.c:25
stralloc line
Definition: 822mimeparts.c:22
void msg_bodyproc(int mimetype, int start, int end)
Definition: 822mimeparts.c:155
int match
Definition: 822mimeparts.c:18
mess822_action hc[]
Definition: 822mimeparts.c:151
buffer bo
Definition: 822mimeparts.c:88
stralloc valuem
Definition: 822mimeparts.c:23
int flag
Definition: 822mimeparts.c:19
#define WHO
Definition: 822mimeparts.c:14
stralloc boundary
Definition: 822mimeparts.c:26
stralloc partname
Definition: 822mimeparts.c:30
mess822_header enco
Definition: 822mimeparts.c:150
mess822_header msid
Definition: 822mimeparts.c:142
stralloc partbuf
Definition: 822mimeparts.c:29
char name[11]
Definition: 822mimeparts.c:52
mess822_header type
Definition: 822mimeparts.c:146
int mess822_qpdecode(stralloc *, const char *, int, int)
Definition: mess822_qp.c:103
int mess822_line(mess822_header *, stralloc *)
Definition: mess822_line.c:107
int mess822_end(mess822_header *)
Definition: mess822_line.c:26
int mess822_ok(stralloc *)
Definition: mess822_ok.c:4
#define MESS822_HEADER
Definition: mess822.h:48
int mess822_begin(mess822_header *, mess822_action *)
Definition: mess822_line.c:5
int mess822_b64decode(stralloc *, const char *, int, int)
void c(char *, char *, char *, int, int, int)
Definition: install.c:46
struct tai start
Definition: new-inject.c:46