imapext-2007

view src/dmail/dmail.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children
line source
1 /* ========================================================================
2 * Copyright 1988-2007 University of Washington
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *
11 * ========================================================================
12 */
14 /*
15 * Program: Procmail-Callable Mail Delivery Module
16 *
17 * Author: Mark Crispin
18 * Networks and Distributed Computing
19 * Computing & Communications
20 * University of Washington
21 * Administration Building, AG-44
22 * Seattle, WA 98195
23 * Internet: MRC@CAC.Washington.EDU
24 *
25 * Date: 5 April 1993
26 * Last Edited: 30 October 2008
27 */
29 #include <stdio.h>
30 #include <pwd.h>
31 #include <errno.h>
32 extern int errno; /* just in case */
33 #include <sysexits.h>
34 #include <sys/file.h>
35 #include <sys/stat.h>
36 #include "c-client.h"
37 #include "dquota.h"
40 /* Globals */
42 char *version = "18"; /* dmail edit version */
43 int debug = NIL; /* debugging (don't fork) */
44 int flagseen = NIL; /* flag message as seen */
45 int trycreate = NIL; /* flag saying gotta create before appending */
46 int critical = NIL; /* flag saying in critical code */
47 char *sender = NIL; /* message origin */
48 char *keywords = NIL; /* keyword list */
49 long precedence = 0; /* delivery precedence - used by quota hook */
52 /* Function prototypes */
54 void file_string_init (STRING *s,void *data,unsigned long size);
55 char file_string_next (STRING *s);
56 void file_string_setpos (STRING *s,unsigned long i);
57 int main (int argc,char *argv[]);
58 int deliver (FILE *f,unsigned long msglen,char *user);
59 long ibxpath (MAILSTREAM *ds,char **mailbox,char *path);
60 int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
61 char *tmp);
62 int delivery_unsafe (char *path,struct stat *sbuf,char *tmp);
63 int fail (char *string,int code);
66 /* File string driver for file stringstructs */
68 STRINGDRIVER file_string = {
69 file_string_init, /* initialize string structure */
70 file_string_next, /* get next byte in string structure */
71 file_string_setpos /* set position in string structure */
72 };
75 /* Cache buffer for file stringstructs */
77 #define CHUNKLEN 16384
78 char chunk[CHUNKLEN];
80 /* Initialize file string structure for file stringstruct
81 * Accepts: string structure
82 * pointer to string
83 * size of string
84 */
86 void file_string_init (STRING *s,void *data,unsigned long size)
87 {
88 s->data = data; /* note fd */
89 s->size = size; /* note size */
90 s->chunk = chunk;
91 s->chunksize = (unsigned long) CHUNKLEN;
92 SETPOS (s,0); /* set initial position */
93 }
96 /* Get next character from file stringstruct
97 * Accepts: string structure
98 * Returns: character, string structure chunk refreshed
99 */
101 char file_string_next (STRING *s)
102 {
103 char c = *s->curpos++; /* get next byte */
104 SETPOS (s,GETPOS (s)); /* move to next chunk */
105 return c; /* return the byte */
106 }
109 /* Set string pointer position for file stringstruct
110 * Accepts: string structure
111 * new position
112 */
114 void file_string_setpos (STRING *s,unsigned long i)
115 {
116 if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
117 s->offset = i; /* set new offset */
118 s->curpos = s->chunk; /* reset position */
119 /* set size of data */
120 if (s->cursize = min (s->chunksize,SIZE (s))) {
121 /* move to that position in the file */
122 fseek ((FILE *) s->data,s->offset,SEEK_SET);
123 fread (s->curpos,sizeof (char),(unsigned int) s->cursize,(FILE *) s->data);
124 }
125 }
127 /* Main program */
129 int main (int argc,char *argv[])
130 {
131 FILE *f = NIL;
132 int c,ret = 0;
133 unsigned long msglen;
134 char *s,tmp[MAILTMPLEN];
135 uid_t ruid = getuid ();
136 struct passwd *pwd = ruid ? getpwnam ("daemon") : NIL;
137 openlog ("dmail",LOG_PID,LOG_MAIL);
138 /* must not be root or daemon! */
139 if (!ruid || (pwd && (pwd->pw_uid == ruid)))
140 _exit (fail ("dmail may not be invoked by root or daemon",EX_USAGE));
141 #include "linkage.c"
142 /* process all flags */
143 for (--argc; argc && (*(s = *++argv)) == '-'; argc--) switch (s[1]) {
144 case 'D': /* debug */
145 debug = T; /* extra debugging */
146 break;
147 case 's': /* deliver as seen */
148 flagseen = T;
149 break;
150 case 'f':
151 case 'r': /* flag giving return path */
152 if (sender) _exit (fail ("duplicate -r",EX_USAGE));
153 if (argc--) sender = cpystr (*++argv);
154 else _exit (fail ("missing argument to -r",EX_USAGE));
155 break;
156 case 'k':
157 if (keywords) _exit (fail ("duplicate -k",EX_USAGE));
158 if (argc--) keywords = cpystr (*++argv);
159 else _exit (fail ("missing argument to -k",EX_USAGE));
160 break;
161 case 'p':
162 if (s[2] && ((s[2] == '-') || isdigit (s[2]))) precedence = atol (s + 2);
163 else if (argc-- && ((*(s = *++argv) == '-') || isdigit (*s)))
164 precedence = atol (s);
165 else _exit (fail ("missing argument to -p",EX_USAGE));
166 break;
167 default: /* anything else */
168 _exit (fail ("unknown switch",EX_USAGE));
169 }
171 if (argc > 1) _exit (fail ("too many recipients",EX_USAGE));
172 else if (!(f = tmpfile ())) _exit(fail ("can't make temp file",EX_TEMPFAIL));
173 /* build delivery headers */
174 if (sender) fprintf (f,"Return-Path: <%s>\015\012",sender);
175 /* start Received line: */
176 fprintf (f,"Received: via dmail-%s.%s for %s; ",CCLIENTVERSION,version,
177 (argc == 1) ? *argv : myusername ());
178 rfc822_date (tmp);
179 fputs (tmp,f);
180 fputs ("\015\012",f);
181 /* copy text from standard input */
182 if (!fgets (tmp,MAILTMPLEN-1,stdin) || !(s = strchr (tmp,'\n')) ||
183 (s == tmp) || s[1]) _exit (fail ("bad first message line",EX_USAGE));
184 else if (s[-1] == '\015') { /* nuke leading "From " line */
185 if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
186 (tmp[3] != 'm') || (tmp[4] != ' ')) fputs (tmp,f);
187 while ((c = getchar ()) != EOF) putc (c,f);
188 }
189 else {
190 if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
191 (tmp[3] != 'm') || (tmp[4] != ' ')) {
192 *s++ = '\015'; /* overwrite NL with CRLF */
193 *s++ = '\012';
194 *s = '\0'; /* tie off string */
195 fputs (tmp,f); /* write line */
196 }
197 }
198 /* copy text from standard input */
199 while ((c = getchar ()) != EOF) {
200 /* add CR if needed */
201 if (c == '\012') putc ('\015',f);
202 putc (c,f);
203 }
204 msglen = ftell (f); /* size of message */
205 fflush (f); /* make sure all changes written out */
206 if (ferror (f)) ret = fail ("error writing temp file",EX_TEMPFAIL);
207 else if (!msglen) ret = fail ("empty message",EX_TEMPFAIL);
208 /* single delivery */
209 else ret = deliver (f,msglen,argc ? *argv : myusername ());
210 fclose (f); /* all done with temporary file */
211 _exit (ret); /* normal exit */
212 return 0; /* stupid gcc */
213 }
215 /* Deliver message to recipient list
216 * Accepts: file description of message temporary file
217 * size of message temporary file in bytes
218 * recipient name
219 * Returns: NIL if success, else error code
220 */
222 int deliver (FILE *f,unsigned long msglen,char *user)
223 {
224 MAILSTREAM *ds = NIL;
225 char *s,*mailbox,tmp[MAILTMPLEN],path[MAILTMPLEN];
226 STRING st;
227 struct stat sbuf;
228 /* have a mailbox specifier? */
229 if (mailbox = strchr (user,'+')) {
230 *mailbox++ = '\0'; /* yes, tie off user name */
231 if (!*mailbox || !compare_cstring ((unsigned char *) mailbox,"INBOX"))
232 mailbox = NIL; /* user+ and user+INBOX same as user */
233 }
234 if (!*user) user = myusername ();
235 else if (strcmp (user,myusername ()))
236 return fail ("can't deliver to other user",EX_CANTCREAT);
237 sprintf (tmp,"delivering to %.80s+%.80s",user,mailbox ? mailbox : "INBOX");
238 mm_dlog (tmp);
239 /* prepare stringstruct */
240 INIT (&st,file_string,(void *) f,msglen);
241 if (mailbox) { /* non-INBOX name */
242 switch (mailbox[0]) { /* make sure a valid name */
243 default: /* other names, try to deliver if not INBOX */
244 if (!strstr (mailbox,"..") && !strstr (mailbox,"//") &&
245 !strstr (mailbox,"/~") && mailboxfile (path,mailbox) && path[0] &&
246 !deliver_safely (NIL,&st,mailbox,path,tmp)) return NIL;
247 case '%': case '*': /* wildcards not valid */
248 case '/': /* absolute path names not valid */
249 case '~': /* user names not valid */
250 sprintf (tmp,"invalid mailbox name %.80s+%.80s",user,mailbox);
251 mm_log (tmp,WARN);
252 break;
253 }
254 mm_dlog ("retrying delivery to INBOX");
255 SETPOS (&st,0); /* rewind stringstruct just in case */
256 }
258 /* no -I, resolve "INBOX" into path */
259 if (mailboxfile (path,mailbox = "INBOX") && !path[0]) {
260 /* clear box, get generic INBOX prototype */
261 if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
262 fatal ("no INBOX prototype");
263 /* standard system driver? */
264 if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf")) {
265 strcpy (path,sysinbox ());/* use system INBOX */
266 if (!lstat (path,&sbuf)) /* deliver to existing system INBOX */
267 return deliver_safely (ds,&st,mailbox,path,tmp);
268 }
269 else { /* other driver, try ~/INBOX */
270 if ((mailboxfile (path,"&&&&&") == path) &&
271 (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX") &&
272 !lstat (path,&sbuf)){ /* deliver to existing ~/INBOX */
273 sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
274 return deliver_safely (ds,&st,cpystr (tmp),path,tmp);
275 }
276 }
277 /* not dummy, deliver to driver imputed path */
278 if (strcmp (ds->dtb->name,"dummy"))
279 return (ibxpath (ds,&mailbox,path) && !lstat (path,&sbuf)) ?
280 deliver_safely (ds,&st,mailbox,path,tmp) :
281 fail ("unable to resolve INBOX path",EX_CANTCREAT);
282 /* dummy, empty imputed append path exist? */
283 if (ibxpath (ds = default_proto (T),&mailbox,path) &&
284 !lstat (path,&sbuf) && !sbuf.st_size)
285 return deliver_safely (ds,&st,mailbox,path,tmp);
286 /* impute path that we will create */
287 if (!ibxpath (ds = default_proto (NIL),&mailbox,path))
288 return fail ("unable to resolve INBOX",EX_CANTCREAT);
289 }
290 /* black box, must create, get create proto */
291 else if (lstat (path,&sbuf)) ds = default_proto (NIL);
292 else { /* black box, existing file */
293 /* empty file, get append prototype */
294 if (!sbuf.st_size) ds = default_proto (T);
295 /* non-empty, get prototype from its data */
296 else if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
297 fatal ("no INBOX prototype");
298 /* error if unknown format */
299 if (!strcmp (ds->dtb->name,"phile"))
300 return fail ("unknown format INBOX",EX_UNAVAILABLE);
301 /* otherwise can deliver to it */
302 return deliver_safely (ds,&st,mailbox,path,tmp);
303 }
304 sprintf (tmp,"attempting to create mailbox %.80s path %.80s",mailbox,path);
305 mm_dlog (tmp);
306 /* supplicate to the Evil One */
307 if (!path_create (ds,path)) return fail ("can't create INBOX",EX_CANTCREAT);
308 sprintf (tmp,"created %.80s",path);
309 mm_dlog (tmp);
310 /* deliver the message */
311 return deliver_safely (ds,&st,mailbox,path,tmp);
312 }
314 /* Resolve INBOX from driver prototype into mailbox name and filesystem path
315 * Accepts: driver prototype
316 * pointer to mailbox name string pointer
317 * buffer to return mailbox path
318 * Returns: T if success, NIL if error
319 */
321 long ibxpath (MAILSTREAM *ds,char **mailbox,char *path)
322 {
323 char *s,tmp[MAILTMPLEN];
324 long ret = T;
325 if (!ds) return NIL;
326 else if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf"))
327 strcpy (path,sysinbox ()); /* use system INBOX for unix and MMDF */
328 else if (!strcmp (ds->dtb->name,"tenex"))
329 ret = (mailboxfile (path,"mail.txt") == path) ? T : NIL;
330 else if (!strcmp (ds->dtb->name,"mtx"))
331 ret = (mailboxfile (path,"INBOX.MTX") == path) ? T : NIL;
332 else if (!strcmp (ds->dtb->name,"mbox"))
333 ret = (mailboxfile (path,"mbox") == path) ? T : NIL;
334 /* better not be a namespace driver */
335 else if (ds->dtb->flags & DR_NAMESPACE) return NIL;
336 /* INBOX in home directory */
337 else ret = ((mailboxfile (path,"&&&&&") == path) &&
338 (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX")) ? T : NIL;
339 if (ret) { /* don't bother if lossage */
340 sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
341 *mailbox = cpystr (tmp); /* name of INBOX in this namespace */
342 }
343 return ret;
344 }
346 /* Deliver safely
347 * Accepts: prototype stream to force mailbox format
348 * stringstruct of message temporary file or NIL for check only
349 * mailbox name
350 * filesystem path name
351 * scratch buffer for messages
352 * Returns: NIL if success, else error code
353 */
355 int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
356 char *tmp)
357 {
358 struct stat sbuf;
359 char *flags = NIL;
360 int i = delivery_unsafe (path,&sbuf,tmp);
361 if (i) return i; /* give up now if delivery unsafe */
362 /* directory, not file */
363 if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
364 if (sbuf.st_mode & 0001) { /* listable directories may be worrisome */
365 sprintf (tmp,"WARNING: directory %.80s is listable",path);
366 mm_log (tmp,WARN);
367 }
368 }
369 else { /* file, not directory */
370 if (sbuf.st_nlink != 1) { /* multiple links may be worrisome */
371 sprintf (tmp,"WARNING: multiple links to file %.80s",path);
372 mm_log (tmp,WARN);
373 }
374 if (sbuf.st_mode & 0111) { /* executable files may be worrisome */
375 sprintf (tmp,"WARNING: file %.80s is executable",path);
376 mm_log (tmp,WARN);
377 }
378 }
379 if (sbuf.st_mode & 0002) { /* public-write files may be worrisome */
380 sprintf (tmp,"WARNING: file %.80s is publicly-writable",path);
381 mm_log (tmp,WARN);
382 }
383 if (sbuf.st_mode & 0004) { /* public-write files may be worrisome */
384 sprintf (tmp,"WARNING: file %.80s is publicly-readable",path);
385 mm_log (tmp,WARN);
386 }
387 /* check site-written quota procedure */
388 if (!dmail_quota (st,path,tmp,sender,precedence))
389 return fail (tmp,EX_CANTCREAT);
390 /* so far, so good */
391 sprintf (tmp,"%s appending to %.80s (%s %.80s)",
392 prt ? prt->dtb->name : "default",mailbox,
393 ((sbuf.st_mode & S_IFMT) == S_IFDIR) ? "directory" : "file",path);
394 mm_dlog (tmp);
395 if (keywords) { /* any keywords requested? */
396 if (flagseen) sprintf (flags = tmp,"\\Seen %.1000s",keywords);
397 else flags = keywords;
398 }
399 else if (flagseen) flags = "\\Seen";
400 /* do the append now! */
401 if (!mail_append_full (prt,mailbox,flags,NIL,st)) {
402 sprintf (tmp,"message delivery failed to %.80s",path);
403 return fail (tmp,EX_CANTCREAT);
404 }
405 /* note success */
406 sprintf (tmp,"delivered to %.80s",path);
407 mm_log (tmp,NIL);
408 /* make sure nothing evil this way comes */
409 return delivery_unsafe (path,&sbuf,tmp);
410 }
412 /* Verify that delivery is safe
413 * Accepts: path name
414 * stat buffer
415 * scratch buffer for messages
416 * Returns: NIL if delivery is safe, error code if unsafe
417 */
419 int delivery_unsafe (char *path,struct stat *sbuf,char *tmp)
420 {
421 u_short type;
422 sprintf (tmp,"Verifying safe delivery to %.80s",path);
423 mm_dlog (tmp);
424 /* prepare message just in case */
425 sprintf (tmp,"delivery to %.80s unsafe: ",path);
426 /* unsafe if can't get its status */
427 if (lstat (path,sbuf)) strcat (tmp,strerror (errno));
428 /* check file type */
429 else switch (sbuf->st_mode & S_IFMT) {
430 case S_IFDIR: /* directory is always OK */
431 return NIL;
432 case S_IFREG: /* file is unsafe if setuid */
433 if (sbuf->st_mode & S_ISUID) strcat (tmp,"setuid file");
434 /* or setgid */
435 else if (sbuf->st_mode & S_ISGID) strcat (tmp,"setgid file");
436 else return NIL; /* otherwise safe */
437 break;
438 case S_IFCHR: strcat (tmp,"character special"); break;
439 case S_IFBLK: strcat (tmp,"block special"); break;
440 case S_IFLNK: strcat (tmp,"symbolic link"); break;
441 case S_IFSOCK: strcat (tmp,"socket"); break;
442 default:
443 sprintf (tmp + strlen (tmp),"file type %07o",(unsigned int) type);
444 }
445 return fail (tmp,EX_CANTCREAT);
446 }
448 /* Report an error
449 * Accepts: string to output
450 */
452 int fail (char *string,int code)
453 {
454 mm_log (string,ERROR); /* pass up the string */
455 switch (code) {
456 #if T
457 case EX_USAGE:
458 case EX_OSERR:
459 case EX_SOFTWARE:
460 case EX_NOUSER:
461 case EX_CANTCREAT:
462 code = EX_TEMPFAIL; /* coerce these to TEMPFAIL */
463 break;
464 #endif
465 case -1: /* quota failure... */
466 code = EX_CANTCREAT; /* ...really returns this code */
467 break;
468 default:
469 break;
470 }
471 return code; /* error code to return */
472 }
474 /* Co-routines from MAIL library */
477 /* Message matches a search
478 * Accepts: MAIL stream
479 * message number
480 */
482 void mm_searched (MAILSTREAM *stream,unsigned long msgno)
483 {
484 fatal ("mm_searched() call");
485 }
488 /* Message exists (i.e. there are that many messages in the mailbox)
489 * Accepts: MAIL stream
490 * message number
491 */
493 void mm_exists (MAILSTREAM *stream,unsigned long number)
494 {
495 fatal ("mm_exists() call");
496 }
499 /* Message expunged
500 * Accepts: MAIL stream
501 * message number
502 */
504 void mm_expunged (MAILSTREAM *stream,unsigned long number)
505 {
506 fatal ("mm_expunged() call");
507 }
510 /* Message flags update seen
511 * Accepts: MAIL stream
512 * message number
513 */
515 void mm_flags (MAILSTREAM *stream,unsigned long number)
516 {
517 }
519 /* Mailbox found
520 * Accepts: MAIL stream
521 * delimiter
522 * mailbox name
523 * mailbox attributes
524 */
526 void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
527 {
528 fatal ("mm_list() call");
529 }
532 /* Subscribed mailbox found
533 * Accepts: MAIL stream
534 * delimiter
535 * mailbox name
536 * mailbox attributes
537 */
539 void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
540 {
541 fatal ("mm_lsub() call");
542 }
545 /* Mailbox status
546 * Accepts: MAIL stream
547 * mailbox name
548 * mailbox status
549 */
551 void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
552 {
553 fatal ("mm_status() call");
554 }
556 /* Notification event
557 * Accepts: MAIL stream
558 * string to log
559 * error flag
560 */
562 void mm_notify (MAILSTREAM *stream,char *string,long errflg)
563 {
564 char tmp[MAILTMPLEN];
565 tmp[11] = '\0'; /* see if TRYCREATE */
566 if (!strcmp (ucase (strncpy (tmp,string,11)),"[TRYCREATE]")) trycreate = T;
567 mm_log (string,errflg); /* just do mm_log action */
568 }
571 /* Log an event for the user to see
572 * Accepts: string to log
573 * error flag
574 */
576 void mm_log (char *string,long errflg)
577 {
578 if (trycreate)mm_dlog(string);/* debug logging only if trycreate in effect */
579 else { /* ordinary logging */
580 fprintf (stderr,"%s\n",string);
581 switch (errflg) {
582 case NIL: /* no error */
583 syslog (LOG_INFO,"%s",string);
584 break;
585 case PARSE: /* parsing problem */
586 case WARN: /* warning */
587 syslog (LOG_WARNING,"%s",string);
588 break;
589 case ERROR: /* error */
590 default:
591 syslog (LOG_ERR,"%s",string);
592 break;
593 }
594 }
595 }
598 /* Log an event to debugging telemetry
599 * Accepts: string to log
600 */
602 void mm_dlog (char *string)
603 {
604 if (debug) fprintf (stderr,"%s\n",string);
605 syslog (LOG_DEBUG,"%s",string);
606 }
608 /* Get user name and password for this host
609 * Accepts: parse of network mailbox name
610 * where to return user name
611 * where to return password
612 * trial count
613 */
615 void mm_login (NETMBX *mb,char *username,char *password,long trial)
616 {
617 fatal ("mm_login() call");
618 }
621 /* About to enter critical code
622 * Accepts: stream
623 */
625 void mm_critical (MAILSTREAM *stream)
626 {
627 critical = T; /* note in critical code */
628 }
631 /* About to exit critical code
632 * Accepts: stream
633 */
635 void mm_nocritical (MAILSTREAM *stream)
636 {
637 critical = NIL; /* note not in critical code */
638 }
641 /* Disk error found
642 * Accepts: stream
643 * system error code
644 * flag indicating that mailbox may be clobbered
645 * Returns: T if user wants to abort
646 */
648 long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
649 {
650 return T;
651 }
654 /* Log a fatal error event
655 * Accepts: string to log
656 */
658 void mm_fatal (char *string)
659 {
660 printf ("?%s\n",string); /* shouldn't happen normally */
661 }

UW-IMAP'd extensions by yuuji