* Update to version 2.19.1
[alpine.git] / imap / src / dmail / dmail.c
blob25bc547f70e4d374289d0dd73409908d119c9f3e
1 /* ========================================================================
2 * Copyright 2008 Mark Crispin
3 * ========================================================================
4 */
6 /*
7 * Program: Procmail-Callable Mail Delivery Module
9 * Author: Mark Crispin
11 * Date: 5 April 1993
12 * Last Edited: 19 November 2008
14 * Previous versions of this file were
16 * Copyright 1988-2007 University of Washington
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
22 * http://www.apache.org/licenses/LICENSE-2.0
27 #include <stdio.h>
28 #include <pwd.h>
29 #include <errno.h>
30 extern int errno; /* just in case */
31 #include <sysexits.h>
32 #include <sys/file.h>
33 #include <sys/stat.h>
34 #include "c-client.h"
35 #include "dquota.h"
38 /* Globals */
40 char *version = "19"; /* dmail edit version */
41 int debug = NIL; /* debugging (don't fork) */
42 int flagseen = NIL; /* flag message as seen */
43 int trycreate = NIL; /* flag saying gotta create before appending */
44 int critical = NIL; /* flag saying in critical code */
45 char *sender = NIL; /* message origin */
46 char *keywords = NIL; /* keyword list */
47 long precedence = 0; /* delivery precedence - used by quota hook */
50 /* Function prototypes */
52 void file_string_init (STRING *s,void *data,unsigned long size);
53 char file_string_next (STRING *s);
54 void file_string_setpos (STRING *s,unsigned long i);
55 int main (int argc,char *argv[]);
56 int deliver (FILE *f,unsigned long msglen,char *user);
57 long ibxpath (MAILSTREAM *ds,char **mailbox,char *path);
58 int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
59 char *tmp);
60 int delivery_unsafe (char *path,struct stat *sbuf,char *tmp);
61 int fail (char *string,int code);
64 /* File string driver for file stringstructs */
66 STRINGDRIVER file_string = {
67 file_string_init, /* initialize string structure */
68 file_string_next, /* get next byte in string structure */
69 file_string_setpos /* set position in string structure */
73 /* Cache buffer for file stringstructs */
75 #define CHUNKLEN 16384
76 char chunk[CHUNKLEN];
78 /* Initialize file string structure for file stringstruct
79 * Accepts: string structure
80 * pointer to string
81 * size of string
84 void file_string_init (STRING *s,void *data,unsigned long size)
86 s->data = data; /* note fd */
87 s->size = size; /* note size */
88 s->chunk = chunk;
89 s->chunksize = (unsigned long) CHUNKLEN;
90 SETPOS (s,0); /* set initial position */
94 /* Get next character from file stringstruct
95 * Accepts: string structure
96 * Returns: character, string structure chunk refreshed
99 char file_string_next (STRING *s)
101 char c = *s->curpos++; /* get next byte */
102 SETPOS (s,GETPOS (s)); /* move to next chunk */
103 return c; /* return the byte */
107 /* Set string pointer position for file stringstruct
108 * Accepts: string structure
109 * new position
112 void file_string_setpos (STRING *s,unsigned long i)
114 if (i > s->size) i = s->size; /* don't permit setting beyond EOF */
115 s->offset = i; /* set new offset */
116 s->curpos = s->chunk; /* reset position */
117 /* set size of data */
118 if (s->cursize = min (s->chunksize,SIZE (s))) {
119 /* move to that position in the file */
120 fseek ((FILE *) s->data,s->offset,SEEK_SET);
121 fread (s->curpos,sizeof (char),(unsigned int) s->cursize,(FILE *) s->data);
125 /* Main program */
127 int main (int argc,char *argv[])
129 FILE *f = NIL;
130 int c,ret = 0;
131 unsigned long msglen;
132 char *s,tmp[MAILTMPLEN];
133 uid_t ruid = getuid ();
134 struct passwd *pwd = ruid ? getpwnam ("daemon") : NIL;
135 openlog ("dmail",LOG_PID,LOG_MAIL);
136 /* must not be root or daemon! */
137 if (!ruid || (pwd && (pwd->pw_uid == ruid)))
138 _exit (fail ("dmail may not be invoked by root or daemon",EX_USAGE));
139 #include "linkage.c"
140 /* process all flags */
141 for (--argc; argc && (*(s = *++argv)) == '-'; argc--) switch (s[1]) {
142 case 'D': /* debug */
143 debug = T; /* extra debugging */
144 break;
145 case 's': /* deliver as seen */
146 flagseen = T;
147 break;
148 case 'f':
149 case 'r': /* flag giving return path */
150 if (sender) _exit (fail ("duplicate -r",EX_USAGE));
151 if (argc--) sender = cpystr (*++argv);
152 else _exit (fail ("missing argument to -r",EX_USAGE));
153 break;
154 case 'k':
155 if (keywords) _exit (fail ("duplicate -k",EX_USAGE));
156 if (argc--) keywords = cpystr (*++argv);
157 else _exit (fail ("missing argument to -k",EX_USAGE));
158 break;
159 case 'p':
160 if (s[2] && ((s[2] == '-') || isdigit (s[2]))) precedence = atol (s + 2);
161 else if (argc-- && ((*(s = *++argv) == '-') || isdigit (*s)))
162 precedence = atol (s);
163 else _exit (fail ("missing argument to -p",EX_USAGE));
164 break;
165 default: /* anything else */
166 _exit (fail ("unknown switch",EX_USAGE));
169 if (argc > 1) _exit (fail ("too many recipients",EX_USAGE));
170 else if (!(f = tmpfile ())) _exit(fail ("can't make temp file",EX_TEMPFAIL));
171 /* build delivery headers */
172 if (sender) fprintf (f,"Return-Path: <%s>\015\012",sender);
173 /* start Received line: */
174 fprintf (f,"Received: via dmail-%s.%s for %s; ",CCLIENTVERSION,version,
175 (argc == 1) ? *argv : myusername ());
176 rfc822_date (tmp);
177 fputs (tmp,f);
178 fputs ("\015\012",f);
179 /* copy text from standard input */
180 if (!fgets (tmp,MAILTMPLEN-1,stdin) || !(s = strchr (tmp,'\n')) ||
181 (s == tmp) || s[1]) _exit (fail ("bad first message line",EX_USAGE));
182 else if (s[-1] == '\015') { /* nuke leading "From " line */
183 if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
184 (tmp[3] != 'm') || (tmp[4] != ' ')) fputs (tmp,f);
185 while ((c = getchar ()) != EOF) putc (c,f);
187 else {
188 if ((tmp[0] != 'F') || (tmp[1] != 'r') || (tmp[2] != 'o') ||
189 (tmp[3] != 'm') || (tmp[4] != ' ')) {
190 *s++ = '\015'; /* overwrite NL with CRLF */
191 *s++ = '\012';
192 *s = '\0'; /* tie off string */
193 fputs (tmp,f); /* write line */
196 /* copy text from standard input */
197 while ((c = getchar ()) != EOF) {
198 /* add CR if needed */
199 if (c == '\012') putc ('\015',f);
200 putc (c,f);
202 msglen = ftell (f); /* size of message */
203 fflush (f); /* make sure all changes written out */
204 if (ferror (f)) ret = fail ("error writing temp file",EX_TEMPFAIL);
205 else if (!msglen) ret = fail ("empty message",EX_TEMPFAIL);
206 /* single delivery */
207 else ret = deliver (f,msglen,argc ? *argv : myusername ());
208 fclose (f); /* all done with temporary file */
209 _exit (ret); /* normal exit */
210 return 0; /* stupid gcc */
213 /* Deliver message to recipient list
214 * Accepts: file description of message temporary file
215 * size of message temporary file in bytes
216 * recipient name
217 * Returns: NIL if success, else error code
220 int deliver (FILE *f,unsigned long msglen,char *user)
222 MAILSTREAM *ds = NIL;
223 char *s,*mailbox,tmp[MAILTMPLEN],path[MAILTMPLEN];
224 STRING st;
225 struct stat sbuf;
226 /* have a mailbox specifier? */
227 if (mailbox = strchr (user,'+')) {
228 *mailbox++ = '\0'; /* yes, tie off user name */
229 if (!*mailbox || !compare_cstring ((unsigned char *) mailbox,"INBOX"))
230 mailbox = NIL; /* user+ and user+INBOX same as user */
232 if (!*user) user = myusername ();
233 else if (strcmp (user,myusername ()))
234 return fail ("can't deliver to other user",EX_CANTCREAT);
235 sprintf (tmp,"delivering to %.80s+%.80s",user,mailbox ? mailbox : "INBOX");
236 mm_dlog (tmp);
237 /* prepare stringstruct */
238 INIT (&st,file_string,(void *) f,msglen);
239 if (mailbox) { /* non-INBOX name */
240 switch (mailbox[0]) { /* make sure a valid name */
241 default: /* other names, try to deliver if not INBOX */
242 if ((strlen (mailbox) <= NETMAXMBX) &&
243 !strstr (mailbox,"..") && !strstr (mailbox,"//") &&
244 !strstr (mailbox,"/~") && mailboxfile (path,mailbox) && path[0] &&
245 !deliver_safely (NIL,&st,mailbox,path,tmp)) return NIL;
246 case '%': case '*': /* wildcards not valid */
247 case '/': /* absolute path names not valid */
248 case '~': /* user names not valid */
249 sprintf (tmp,"invalid mailbox name %.80s+%.80s",user,mailbox);
250 mm_log (tmp,WARN);
251 break;
253 mm_dlog ("retrying delivery to INBOX");
254 SETPOS (&st,0); /* rewind stringstruct just in case */
257 /* no -I, resolve "INBOX" into path */
258 if (mailboxfile (path,mailbox = "INBOX") && !path[0]) {
259 /* clear box, get generic INBOX prototype */
260 if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
261 fatal ("no INBOX prototype");
262 /* standard system driver? */
263 if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf")) {
264 strcpy (path,sysinbox ());/* use system INBOX */
265 if (!lstat (path,&sbuf)) /* deliver to existing system INBOX */
266 return deliver_safely (ds,&st,mailbox,path,tmp);
268 else { /* other driver, try ~/INBOX */
269 if ((mailboxfile (path,"&&&&&") == path) &&
270 (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX") &&
271 !lstat (path,&sbuf)){ /* deliver to existing ~/INBOX */
272 sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
273 return deliver_safely (ds,&st,cpystr (tmp),path,tmp);
276 /* not dummy, deliver to driver imputed path */
277 if (strcmp (ds->dtb->name,"dummy"))
278 return (ibxpath (ds,&mailbox,path) && !lstat (path,&sbuf)) ?
279 deliver_safely (ds,&st,mailbox,path,tmp) :
280 fail ("unable to resolve INBOX path",EX_CANTCREAT);
281 /* dummy, empty imputed append path exist? */
282 if (ibxpath (ds = default_proto (T),&mailbox,path) &&
283 !lstat (path,&sbuf) && !sbuf.st_size)
284 return deliver_safely (ds,&st,mailbox,path,tmp);
285 /* impute path that we will create */
286 if (!ibxpath (ds = default_proto (NIL),&mailbox,path))
287 return fail ("unable to resolve INBOX",EX_CANTCREAT);
289 /* black box, must create, get create proto */
290 else if (lstat (path,&sbuf)) ds = default_proto (NIL);
291 else { /* black box, existing file */
292 /* empty file, get append prototype */
293 if (!sbuf.st_size) ds = default_proto (T);
294 /* non-empty, get prototype from its data */
295 else if (!(ds = mail_open (NIL,"INBOX",OP_PROTOTYPE)))
296 fatal ("no INBOX prototype");
297 /* error if unknown format */
298 if (!strcmp (ds->dtb->name,"phile"))
299 return fail ("unknown format INBOX",EX_UNAVAILABLE);
300 /* otherwise can deliver to it */
301 return deliver_safely (ds,&st,mailbox,path,tmp);
303 sprintf (tmp,"attempting to create mailbox %.80s path %.80s",mailbox,path);
304 mm_dlog (tmp);
305 /* supplicate to the Evil One */
306 if (!path_create (ds,path)) return fail ("can't create INBOX",EX_CANTCREAT);
307 sprintf (tmp,"created %.80s",path);
308 mm_dlog (tmp);
309 /* deliver the message */
310 return deliver_safely (ds,&st,mailbox,path,tmp);
313 /* Resolve INBOX from driver prototype into mailbox name and filesystem path
314 * Accepts: driver prototype
315 * pointer to mailbox name string pointer
316 * buffer to return mailbox path
317 * Returns: T if success, NIL if error
320 long ibxpath (MAILSTREAM *ds,char **mailbox,char *path)
322 char *s,tmp[MAILTMPLEN];
323 long ret = T;
324 if (!ds) return NIL;
325 else if (!strcmp (ds->dtb->name,"unix") || !strcmp (ds->dtb->name,"mmdf"))
326 strcpy (path,sysinbox ()); /* use system INBOX for unix and MMDF */
327 else if (!strcmp (ds->dtb->name,"tenex"))
328 ret = (mailboxfile (path,"mail.txt") == path) ? T : NIL;
329 else if (!strcmp (ds->dtb->name,"mtx"))
330 ret = (mailboxfile (path,"INBOX.MTX") == path) ? T : NIL;
331 else if (!strcmp (ds->dtb->name,"mbox"))
332 ret = (mailboxfile (path,"mbox") == path) ? T : NIL;
333 /* better not be a namespace driver */
334 else if (ds->dtb->flags & DR_NAMESPACE) return NIL;
335 /* INBOX in home directory */
336 else ret = ((mailboxfile (path,"&&&&&") == path) &&
337 (s = strstr (path,"&&&&&")) && strcpy (s,"INBOX")) ? T : NIL;
338 if (ret) { /* don't bother if lossage */
339 sprintf (tmp,"#driver.%s/INBOX",ds->dtb->name);
340 *mailbox = cpystr (tmp); /* name of INBOX in this namespace */
342 return ret;
345 /* Deliver safely
346 * Accepts: prototype stream to force mailbox format
347 * stringstruct of message temporary file or NIL for check only
348 * mailbox name
349 * filesystem path name
350 * scratch buffer for messages
351 * Returns: NIL if success, else error code
354 int deliver_safely (MAILSTREAM *prt,STRING *st,char *mailbox,char *path,
355 char *tmp)
357 struct stat sbuf;
358 char *flags = NIL;
359 int i = delivery_unsafe (path,&sbuf,tmp);
360 if (i) return i; /* give up now if delivery unsafe */
361 /* directory, not file */
362 if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {
363 if (sbuf.st_mode & 0001) { /* listable directories may be worrisome */
364 sprintf (tmp,"WARNING: directory %.80s is listable",path);
365 mm_log (tmp,WARN);
368 else { /* file, not directory */
369 if (sbuf.st_nlink != 1) { /* multiple links may be worrisome */
370 sprintf (tmp,"WARNING: multiple links to file %.80s",path);
371 mm_log (tmp,WARN);
373 if (sbuf.st_mode & 0111) { /* executable files may be worrisome */
374 sprintf (tmp,"WARNING: file %.80s is executable",path);
375 mm_log (tmp,WARN);
378 if (sbuf.st_mode & 0002) { /* public-write files may be worrisome */
379 sprintf (tmp,"WARNING: file %.80s is publicly-writable",path);
380 mm_log (tmp,WARN);
382 if (sbuf.st_mode & 0004) { /* public-write files may be worrisome */
383 sprintf (tmp,"WARNING: file %.80s is publicly-readable",path);
384 mm_log (tmp,WARN);
386 /* check site-written quota procedure */
387 if (!dmail_quota (st,path,tmp,sender,precedence))
388 return fail (tmp,EX_CANTCREAT);
389 /* so far, so good */
390 sprintf (tmp,"%s appending to %.80s (%s %.80s)",
391 prt ? prt->dtb->name : "default",mailbox,
392 ((sbuf.st_mode & S_IFMT) == S_IFDIR) ? "directory" : "file",path);
393 mm_dlog (tmp);
394 if (keywords) { /* any keywords requested? */
395 if (flagseen) sprintf (flags = tmp,"\\Seen %.1000s",keywords);
396 else flags = keywords;
398 else if (flagseen) flags = "\\Seen";
399 /* do the append now! */
400 if (!mail_append_full (prt,mailbox,flags,NIL,st)) {
401 sprintf (tmp,"message delivery failed to %.80s",path);
402 return fail (tmp,EX_CANTCREAT);
404 /* note success */
405 sprintf (tmp,"delivered to %.80s",path);
406 mm_log (tmp,NIL);
407 /* make sure nothing evil this way comes */
408 return delivery_unsafe (path,&sbuf,tmp);
411 /* Verify that delivery is safe
412 * Accepts: path name
413 * stat buffer
414 * scratch buffer for messages
415 * Returns: NIL if delivery is safe, error code if unsafe
418 int delivery_unsafe (char *path,struct stat *sbuf,char *tmp)
420 u_short type;
421 sprintf (tmp,"Verifying safe delivery to %.80s",path);
422 mm_dlog (tmp);
423 /* prepare message just in case */
424 sprintf (tmp,"delivery to %.80s unsafe: ",path);
425 /* unsafe if can't get its status */
426 if (lstat (path,sbuf)) strcat (tmp,strerror (errno));
427 /* check file type */
428 else switch (sbuf->st_mode & S_IFMT) {
429 case S_IFDIR: /* directory is always OK */
430 return NIL;
431 case S_IFREG: /* file is unsafe if setuid */
432 if (sbuf->st_mode & S_ISUID) strcat (tmp,"setuid file");
433 /* or setgid */
434 else if (sbuf->st_mode & S_ISGID) strcat (tmp,"setgid file");
435 else return NIL; /* otherwise safe */
436 break;
437 case S_IFCHR: strcat (tmp,"character special"); break;
438 case S_IFBLK: strcat (tmp,"block special"); break;
439 case S_IFLNK: strcat (tmp,"symbolic link"); break;
440 case S_IFSOCK: strcat (tmp,"socket"); break;
441 default:
442 sprintf (tmp + strlen (tmp),"file type %07o",(unsigned int) type);
444 return fail (tmp,EX_CANTCREAT);
447 /* Report an error
448 * Accepts: string to output
451 int fail (char *string,int code)
453 mm_log (string,ERROR); /* pass up the string */
454 switch (code) {
455 #if T
456 case EX_USAGE:
457 case EX_OSERR:
458 case EX_SOFTWARE:
459 case EX_NOUSER:
460 case EX_CANTCREAT:
461 code = EX_TEMPFAIL; /* coerce these to TEMPFAIL */
462 break;
463 #endif
464 case -1: /* quota failure... */
465 code = EX_CANTCREAT; /* ...really returns this code */
466 break;
467 default:
468 break;
470 return code; /* error code to return */
473 /* Co-routines from MAIL library */
476 /* Message matches a search
477 * Accepts: MAIL stream
478 * message number
481 void mm_searched (MAILSTREAM *stream,unsigned long msgno)
483 fatal ("mm_searched() call");
487 /* Message exists (i.e. there are that many messages in the mailbox)
488 * Accepts: MAIL stream
489 * message number
492 void mm_exists (MAILSTREAM *stream,unsigned long number)
494 fatal ("mm_exists() call");
498 /* Message expunged
499 * Accepts: MAIL stream
500 * message number
503 void mm_expunged (MAILSTREAM *stream,unsigned long number)
505 fatal ("mm_expunged() call");
509 /* Message flags update seen
510 * Accepts: MAIL stream
511 * message number
514 void mm_flags (MAILSTREAM *stream,unsigned long number)
518 /* Mailbox found
519 * Accepts: MAIL stream
520 * delimiter
521 * mailbox name
522 * mailbox attributes
525 void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
527 fatal ("mm_list() call");
531 /* Subscribed mailbox found
532 * Accepts: MAIL stream
533 * delimiter
534 * mailbox name
535 * mailbox attributes
538 void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
540 fatal ("mm_lsub() call");
544 /* Mailbox status
545 * Accepts: MAIL stream
546 * mailbox name
547 * mailbox status
550 void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
552 fatal ("mm_status() call");
555 /* Notification event
556 * Accepts: MAIL stream
557 * string to log
558 * error flag
561 void mm_notify (MAILSTREAM *stream,char *string,long errflg)
563 char tmp[MAILTMPLEN];
564 tmp[11] = '\0'; /* see if TRYCREATE */
565 if (!strcmp (ucase (strncpy (tmp,string,11)),"[TRYCREATE]")) trycreate = T;
566 mm_log (string,errflg); /* just do mm_log action */
570 /* Log an event for the user to see
571 * Accepts: string to log
572 * error flag
575 void mm_log (char *string,long errflg)
577 if (trycreate)mm_dlog(string);/* debug logging only if trycreate in effect */
578 else { /* ordinary logging */
579 fprintf (stderr,"%s\n",string);
580 switch (errflg) {
581 case NIL: /* no error */
582 syslog (LOG_INFO,"%s",string);
583 break;
584 case PARSE: /* parsing problem */
585 case WARN: /* warning */
586 syslog (LOG_WARNING,"%s",string);
587 break;
588 case ERROR: /* error */
589 default:
590 syslog (LOG_ERR,"%s",string);
591 break;
597 /* Log an event to debugging telemetry
598 * Accepts: string to log
601 void mm_dlog (char *string)
603 if (debug) fprintf (stderr,"%s\n",string);
604 syslog (LOG_DEBUG,"%s",string);
607 /* Get user name and password for this host
608 * Accepts: parse of network mailbox name
609 * where to return user name
610 * where to return password
611 * trial count
614 void mm_login (NETMBX *mb,char *username,char *password,long trial)
616 fatal ("mm_login() call");
620 /* About to enter critical code
621 * Accepts: stream
624 void mm_critical (MAILSTREAM *stream)
626 critical = T; /* note in critical code */
630 /* About to exit critical code
631 * Accepts: stream
634 void mm_nocritical (MAILSTREAM *stream)
636 critical = NIL; /* note not in critical code */
640 /* Disk error found
641 * Accepts: stream
642 * system error code
643 * flag indicating that mailbox may be clobbered
644 * Returns: T if user wants to abort
647 long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
649 return T;
653 /* Log a fatal error event
654 * Accepts: string to log
657 void mm_fatal (char *string)
659 printf ("?%s\n",string); /* shouldn't happen normally */