comparison contrib/receivestrip/mlmmj-receive-strip.c @ 748:dfc9ab125fd4

Fix spelling of 'receive' and 'voodoo'; make mlmmj-recieve a symlink
author Chris Webb
date Sun, 03 Oct 2010 21:40:42 +1100
parents contrib/recievestrip/mlmmj-recieve-strip.c@c334ffdf8807
children
comparison
equal deleted inserted replaced
747:fd77dd58bde1 748:dfc9ab125fd4
1 /* Copyright (C) 2007 Sascha Sommer <ssommer at suse.de>
2 *
3 * Permission is hereby granted, free of charge, to any person obtaining a copy
4 * of this software and associated documentation files (the "Software"), to
5 * deal in the Software without restriction, including without limitation the
6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 * sell copies of the Software, and to permit persons to whom the Software is
8 * furnished to do so, subject to the following conditions:
9 *
10 * The above copyright notice and this permission notice shall be included in
11 * all copies or substantial portions of the Software.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
19 * IN THE SOFTWARE.
20 */
21
22 /* a version of mlmmj-receive that parses the mail on the fly and strips unwanted
23 mime parts
24 opens the files control/mimedeny and control/mimestrip for a list of mime
25 types for body parts that should be denied or stripped.
26 It adds an extra header X-ThisMailContainsUnwantedMimeParts: Y for mails that
27 contain disallowed mimeparts and X-ThisMailContainsUnwantedMimeParts: N otherwise
28 */
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <string.h>
38 #include <sys/types.h>
39
40
41
42 #include "mlmmj.h"
43 #include "mygetline.h"
44 #include "gethdrline.h"
45 #include "strgen.h"
46 #include "chomp.h"
47 #include "ctrlvalue.h"
48 #include "ctrlvalues.h"
49
50 #include "log_error.h"
51 #include "wrappers.h"
52 #include "memory.h"
53
54 #define UNWANTED_MIME_HDR "X-ThisMailContainsUnwantedMimeParts: N\n"
55
56 /* append a copy of a string to a string list */
57 static void append_strtolist(struct strlist *list, char* str) {
58 list->count++;
59 list->strs = myrealloc(list->strs,
60 sizeof(char *) * (list->count + 1));
61 list->strs[list->count-1] = mystrdup(str);
62 list->strs[list->count] = NULL;
63 }
64
65 /* free all strings in a strlist */
66 static void free_strlist(struct strlist *list) {
67 if(!list)
68 return;
69 if(list->strs) {
70 int i;
71 for(i=0;i < list->count;i++)
72 myfree(list->strs[i]);
73 myfree(list->strs);
74 }
75 list->strs = NULL;
76 list->count = 0;
77 }
78
79 static int findit(char *line, char **headers)
80 {
81 int i = 0;
82 size_t len;
83
84 if (!headers)
85 return 0;
86
87 while(headers[i]) {
88 len = strlen(headers[i]);
89 if(strncasecmp(line, headers[i], len) == 0)
90 return 1;
91 i++;
92 }
93
94 return 0;
95 }
96
97 /* extract mime_type and boundary from the Content-Type header
98 * allocates a string for the mime_type if one is found
99 * always allocates a boundarie (using "--" when none is found)
100 * the caller needs to free the allocated strings
101 */
102 static void extract_boundary(struct strlist *allhdrs, char** mime_type, char** boundary)
103 {
104 int x;
105 *boundary = NULL;
106 *mime_type = NULL;
107 for( x = 0 ; x < allhdrs->count ; x++ ){
108 char* hdr = allhdrs->strs[x];
109 if(hdr && !strncasecmp(hdr,"Content-Type:",13)){
110 char* pos = hdr + 13;
111 size_t len = 0;
112
113 /* find the start of the mimetype */
114 while(*pos && (*pos == ' ' || *pos == '\t'))
115 ++pos;
116
117 if(*pos == '"'){ /* handle quoted mime types */
118 ++pos;
119 while(pos[len] && pos[len] != '"')
120 ++len;
121 }else{
122 while(pos[len] && pos[len] != ' ' && pos[len] != '\t' && pos[len] != ';')
123 ++len;
124 }
125
126 /* extract mime type if any */
127 if(len){
128 *mime_type = mymalloc(len+1);
129 strncpy(*mime_type,pos,len);
130 (*mime_type)[len] = '\0';
131 }
132
133 pos += len;
134 len = 0;
135 /* find start of the boundary info */
136 while(*pos && strncasecmp(pos,"boundary=",9))
137 ++pos;
138 if(*pos == '\0') /* no boundary */
139 break;
140
141 pos += 9;
142 if(*pos == '"'){ /* quoted boundary */
143 ++pos;
144 while(pos[len] && pos[len] != '"')
145 ++len;
146 }else{ /* unquoted boundary */
147 while(pos[len] && pos[len] != ' ' && pos[len] != '\t' && pos[len] != ';')
148 ++len;
149 }
150
151 /* extract boundary */
152 *boundary = mymalloc(len + 3);
153 strcpy(*boundary,"--");
154 strncat(*boundary,pos,len);
155 break;
156 }
157 }
158 }
159
160 /* read all mail headers and save them in a strlist
161 * check what to do with parts that contain the given mime_type
162 *return values
163 * 0: ok
164 * 1: strip
165 * sets deny to 1 if the entire mail should be denied
166 */
167 #define MIME_OK 0
168 #define MIME_STRIP 1
169 static int read_hdrs(int fd, struct strlist *allhdrs,struct strlist* delmime,struct strlist* denymime,int* deny,char** boundary) {
170 int result = MIME_OK;
171 char* mime_type = NULL;
172 allhdrs->strs = NULL;
173 allhdrs->count = 0;
174 /* read headers */
175 while(1) {
176 char* line = mygetline(fd);
177 if(!line) /* end of file and also end of headers */
178 break;
179
180 /* end of headers */
181 if(line[0] == '\n'){
182 myfree(line);
183 break;
184 }
185 if(!allhdrs->count || ((line[0] != '\t') && (line[0] != ' '))) /* first header line or no more unfolding */
186 append_strtolist(allhdrs,line);
187 else{
188 char* tmp = concatstr(2, allhdrs->strs[allhdrs->count-1], line);
189 myfree(allhdrs->strs[allhdrs->count-1]);
190 allhdrs->strs[allhdrs->count-1] = tmp;
191 }
192 myfree(line);
193 }
194 extract_boundary(allhdrs,&mime_type,boundary);
195 if(mime_type) {
196 /* check if this part should be stripped */
197 if(delmime && findit(mime_type, delmime->strs))
198 result = MIME_STRIP;
199 /* check if the mail should be denied */
200 if(denymime && findit(mime_type, denymime->strs))
201 *deny = 1;
202 myfree(mime_type);
203 }
204 return result;
205 }
206
207 /* writes the mail headers if unwantedmime_hdrpos is not NULL an UNWANTED_MIME_HDR
208 * is inserted and its position saved in unwantedmime_hdrpos
209 * returns 0 on success
210 */
211 static int write_hdrs(int outfd,struct strlist* hdrs,off_t* unwantedmime_hdrpos) {
212 int i;
213 for(i = 0; i < hdrs->count ; i++) {
214 if(writen(outfd, hdrs->strs[i], strlen(hdrs->strs[i])) < 0){
215 log_error(LOG_ARGS, "Error when dumping headers");
216 return -1;
217 }
218 }
219
220 /* if this is not the header of an embedded part add the header that will
221 indicate if the mail contains unwanted mime parts */
222 if(unwantedmime_hdrpos) {
223 if(writen(outfd, UNWANTED_MIME_HDR,strlen(UNWANTED_MIME_HDR)) < 0){
224 log_error(LOG_ARGS, "Error writting unwanted mime header");
225 return -1;
226 }
227 /* get the current position so that we can update the header later */
228 *unwantedmime_hdrpos = lseek(outfd,0,SEEK_CUR);
229 if(*unwantedmime_hdrpos < 2){
230 log_error(LOG_ARGS, "Error getting file position");
231 return -1;
232 }
233 *unwantedmime_hdrpos -= 2;
234 }
235
236 /* write a single line feed to terminate the header part */
237 if(writen(outfd, "\n", 1) < 0) {
238 log_error(LOG_ARGS,"Error writting end of hdrs.");
239 return -1;
240 }
241 return 0;
242 }
243
244 /* set the unwanted mime_hdr to Y */
245 static int update_unwantedmime_hdr(int outfd,off_t unwantedmime_hdrpos) {
246 /* seek to the header position */
247 if(lseek(outfd,unwantedmime_hdrpos,SEEK_SET) < 0) {
248 log_error(LOG_ARGS,"Error seeking to the unwantedmime_hdr");
249 return -1;
250 }
251
252 /* update the header */
253 if(writen(outfd, "Y\n",2) < 0){
254 log_error(LOG_ARGS, "Error writting extra header");
255 return -1;
256 }
257
258 /* seek back to the end of the mail */
259 if(lseek(outfd,0,SEEK_END) < 0) {
260 log_error(LOG_ARGS,"Error seeking to the mail end");
261 return -1;
262 }
263 return 0;
264 }
265
266 static int parse_body(int infd,int outfd, struct strlist* delmime, struct strlist* denymime,
267 int* deny,char* boundary){
268 int strip = 0;
269 char* line;
270 while((line = mygetline(infd))) {
271 if(boundary && !strncmp(line,boundary,strlen(boundary))){
272 strip = 0;
273 /* check if the boundary is the beginning of a new part */
274 if(strncmp(line + strlen(boundary),"--",2)){
275 struct strlist hdrs;
276 char* new_boundary = NULL;
277 /* check if this part should be stripped */
278 if(read_hdrs(infd, &hdrs,delmime,denymime,deny,&new_boundary) == MIME_STRIP)
279 strip = 1;
280 else {
281 /* write boundary */
282 if(writen(outfd, line, strlen(line)) < 0){
283 log_error(LOG_ARGS, "Error writting boundary");
284 return -1;
285 }
286 /* write hdr */
287 if(write_hdrs(outfd, &hdrs, NULL) < 0){
288 log_error(LOG_ARGS, "Error writting hdrs");
289 return -1;
290 }
291 /* parse embedded part if a new boundary was found */
292 if(new_boundary && parse_body(infd,outfd,delmime,denymime,deny,new_boundary) != 0) {
293 log_error(LOG_ARGS, "Could not parse embedded part");
294 return -1;
295 }
296 }
297 free_strlist(&hdrs);
298 if(new_boundary)
299 myfree(new_boundary);
300 }else{
301 /* write end of part */
302 if(writen(outfd, line, strlen(line)) < 0){
303 log_error(LOG_ARGS, "Error writting hdrs");
304 return -1;
305 }
306 /* and leave */
307 myfree(line);
308 break;
309 }
310 }else {
311 if(!strip) { /* write the current line */
312 if(writen(outfd, line, strlen(line)) < 0){
313 log_error(LOG_ARGS, "Error when dumping line");
314 return -1;
315 }
316 }
317 }
318 myfree(line);
319 }
320 return 0;
321 }
322
323
324
325
326
327 /* read a mail stripping unwanted parts */
328 static int dump_mail(int infd, int outfd,char* listdir) {
329 struct strlist hdrs;
330 struct strlist* delmime;
331 struct strlist* denymime;
332 char* boundary=NULL;
333 int deny = 0;
334 int result;
335 off_t unwantedmime_hdr_pos = 0;
336
337 /* get list control values */
338 delmime = ctrlvalues(listdir, "mimestrip");
339 denymime = ctrlvalues(listdir, "mimedeny");
340
341 /* read mail header */
342 result = read_hdrs(infd, &hdrs,delmime,denymime,&deny,&boundary);
343 /* write mail header */
344 if(write_hdrs(outfd,&hdrs,&unwantedmime_hdr_pos) < 0) {
345 log_error(LOG_ARGS, "Could not write mail headers");
346 return -1;
347 }
348
349 /* free mail header */
350 free_strlist(&hdrs);
351
352 if(result == MIME_OK && !deny) {
353 /* try to parse the mail */
354 if(parse_body(infd,outfd,delmime,denymime,&deny,boundary) != 0) {
355 log_error(LOG_ARGS, "Could not parse mail");
356 return -1;
357 }
358 myfree(boundary);
359 }else
360 deny = 1;
361
362 /* dump rest of mail */
363 if(dumpfd2fd(infd, outfd) != 0) {
364 log_error(LOG_ARGS, "Could not receive mail");
365 return -1;
366 }
367
368 /* update header */
369 if(deny) {
370 if(update_unwantedmime_hdr(outfd,unwantedmime_hdr_pos) != 0) {
371 log_error(LOG_ARGS, "Could not update header");
372 return -1;
373 }
374 }
375
376 /* free mime types */
377 if(delmime) {
378 free_strlist(delmime);
379 myfree(delmime);
380 }
381 if(denymime) {
382 free_strlist(denymime);
383 myfree(denymime);
384 }
385 return 0;
386 }
387
388 static void print_help(const char *prg)
389 {
390 printf("Usage: %s -L /path/to/listdir [-h] [-V] [-P] [-F]\n"
391 " -h: This help\n"
392 " -F: Don't fork in the background\n"
393 " -L: Full path to list directory\n"
394 " -P: Don't execute mlmmj-process\n"
395 " -V: Print version\n", prg);
396 exit(EXIT_SUCCESS);
397 }
398
399 int main(int argc, char **argv)
400 {
401 char *infilename = NULL, *listdir = NULL;
402 char *randomstr = random_str();
403 char *mlmmjprocess, *bindir;
404 int fd, opt, noprocess = 0, nofork = 0;
405 struct stat st;
406 uid_t uid;
407 pid_t childpid;
408
409 CHECKFULLPATH(argv[0]);
410
411 log_set_name(argv[0]);
412
413 bindir = mydirname(argv[0]);
414 mlmmjprocess = concatstr(2, bindir, "/mlmmj-process");
415 myfree(bindir);
416
417 while ((opt = getopt(argc, argv, "hPVL:F")) != -1) {
418 switch(opt) {
419 case 'h':
420 print_help(argv[0]);
421 break;
422 case 'L':
423 listdir = optarg;
424 break;
425 case 'P':
426 noprocess = 1;
427 break;
428 case 'F':
429 nofork = 1;
430 break;
431 case 'V':
432 print_version(argv[0]);
433 exit(0);
434 }
435 }
436
437 if(listdir == NULL) {
438 fprintf(stderr, "You have to specify -L\n");
439 fprintf(stderr, "%s -h for help\n", argv[0]);
440 exit(EXIT_FAILURE);
441 }
442
443 /* Lets make sure no random user tries to send mail to the list */
444 if(listdir) {
445 if(stat(listdir, &st) == 0) {
446 uid = getuid();
447 if(uid && uid != st.st_uid) {
448 log_error(LOG_ARGS,
449 "Have to invoke either as root "
450 "or as the user owning listdir "
451 "Invoked with uid = [%d]", (int)uid);
452 writen(STDERR_FILENO,
453 "Have to invoke either as root "
454 "or as the user owning listdir\n", 60);
455 exit(EXIT_FAILURE);
456 }
457 } else {
458 log_error(LOG_ARGS, "Could not stat %s", listdir);
459 exit(EXIT_FAILURE);
460 }
461 }
462
463 infilename = concatstr(3, listdir, "/incoming/", randomstr);
464 myfree(randomstr);
465 fd = open(infilename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
466 while(fd < 0 && errno == EEXIST) {
467 myfree(infilename);
468 randomstr = random_str();
469 infilename = concatstr(3, listdir, "/incoming/", randomstr);
470 myfree(randomstr);
471 fd = open(infilename, O_RDWR|O_CREAT|O_EXCL, S_IRUSR|S_IWUSR);
472 }
473
474 if(fd < 0) {
475 log_error(LOG_ARGS, "could not create mail file in "
476 "%s/incoming directory", listdir);
477 myfree(infilename);
478 exit(EXIT_FAILURE);
479 }
480
481 if(dump_mail(fileno(stdin), fd, listdir) != 0) {
482 log_error(LOG_ARGS, "Could not receive mail");
483 exit(EXIT_FAILURE);
484 }
485
486 #if 0
487 log_oper(listdir, OPLOGFNAME, "mlmmj-receive got %s", infilename);
488 #endif
489 fsync(fd);
490 close(fd);
491
492 if(noprocess) {
493 myfree(infilename);
494 exit(EXIT_SUCCESS);
495 }
496
497 /*
498 * Now we fork so we can exit with success since it could potentially
499 * take a long time for mlmmj-send to finish delivering the mails and
500 * returning, making it susceptible to getting a SIGKILL from the
501 * mailserver invoking mlmmj-receive.
502 */
503 if (!nofork) {
504 childpid = fork();
505 if(childpid < 0)
506 log_error(LOG_ARGS, "fork() failed! Proceeding anyway");
507
508 if(childpid)
509 exit(EXIT_SUCCESS); /* Parent says: "bye bye kids!"*/
510
511 close(0);
512 close(1);
513 close(2);
514 }
515
516 execlp(mlmmjprocess, mlmmjprocess,
517 "-L", listdir,
518 "-m", infilename, NULL);
519 log_error(LOG_ARGS, "execlp() of '%s' failed", mlmmjprocess);
520
521 exit(EXIT_FAILURE);
522 }
523