1 /* ========================================================================
2 * Copyright 2008 Mark Crispin
3 * ========================================================================
7 * Program: Procmail-Callable Mail Delivery Module
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
31 extern int errno
; /* just in case */
41 char *version
= "19"; /* dmail edit version */
42 int debug
= NIL
; /* debugging (don't fork) */
43 int flagseen
= NIL
; /* flag message as seen */
44 int trycreate
= NIL
; /* flag saying gotta create before appending */
45 int critical
= NIL
; /* flag saying in critical code */
46 char *sender
= NIL
; /* message origin */
47 char *keywords
= NIL
; /* keyword list */
48 long precedence
= 0; /* delivery precedence - used by quota hook */
51 /* Function prototypes */
53 void file_string_init (STRING
*s
,void *data
,unsigned long size
);
54 char file_string_next (STRING
*s
);
55 void file_string_setpos (STRING
*s
,unsigned long i
);
56 int main (int argc
,char *argv
[]);
57 int deliver (FILE *f
,unsigned long msglen
,char *user
);
58 long ibxpath (MAILSTREAM
*ds
,char **mailbox
,char *path
);
59 int deliver_safely (MAILSTREAM
*prt
,STRING
*st
,char *mailbox
,char *path
,
61 int delivery_unsafe (char *path
,struct stat
*sbuf
,char *tmp
);
62 int fail (char *string
,int code
);
65 /* File string driver for file stringstructs */
67 STRINGDRIVER file_string
= {
68 file_string_init
, /* initialize string structure */
69 file_string_next
, /* get next byte in string structure */
70 file_string_setpos
/* set position in string structure */
74 /* Cache buffer for file stringstructs */
76 #define CHUNKLEN 16384
79 /* Initialize file string structure for file stringstruct
80 * Accepts: string structure
85 void file_string_init (STRING
*s
,void *data
,unsigned long size
)
87 s
->data
= data
; /* note fd */
88 s
->size
= size
; /* note size */
90 s
->chunksize
= (unsigned long) CHUNKLEN
;
91 SETPOS (s
,0); /* set initial position */
95 /* Get next character from file stringstruct
96 * Accepts: string structure
97 * Returns: character, string structure chunk refreshed
100 char file_string_next (STRING
*s
)
102 char c
= *s
->curpos
++; /* get next byte */
103 SETPOS (s
,GETPOS (s
)); /* move to next chunk */
104 return c
; /* return the byte */
108 /* Set string pointer position for file stringstruct
109 * Accepts: string structure
113 void file_string_setpos (STRING
*s
,unsigned long i
)
115 if (i
> s
->size
) i
= s
->size
; /* don't permit setting beyond EOF */
116 s
->offset
= i
; /* set new offset */
117 s
->curpos
= s
->chunk
; /* reset position */
118 /* set size of data */
119 if ((s
->cursize
= min (s
->chunksize
,SIZE (s
))) != 0L) {
120 /* move to that position in the file */
121 fseek ((FILE *) s
->data
,s
->offset
,SEEK_SET
);
122 fread (s
->curpos
,sizeof (char),(unsigned int) s
->cursize
,(FILE *) s
->data
);
128 int main (int argc
,char *argv
[])
132 unsigned long msglen
;
133 char *s
,tmp
[MAILTMPLEN
];
134 uid_t ruid
= getuid ();
135 struct passwd
*pwd
= ruid
? getpwnam ("daemon") : NIL
;
136 openlog ("dmail",LOG_PID
,LOG_MAIL
);
137 /* must not be root or daemon! */
138 if (!ruid
|| (pwd
&& (pwd
->pw_uid
== ruid
)))
139 _exit (fail ("dmail may not be invoked by root or daemon",EX_USAGE
));
141 /* process all flags */
142 for (--argc
; argc
&& (*(s
= *++argv
)) == '-'; argc
--) switch (s
[1]) {
143 case 'D': /* debug */
144 debug
= T
; /* extra debugging */
146 case 's': /* deliver as seen */
150 case 'r': /* flag giving return path */
151 if (sender
) _exit (fail ("duplicate -r",EX_USAGE
));
152 if (argc
--) sender
= cpystr (*++argv
);
153 else _exit (fail ("missing argument to -r",EX_USAGE
));
156 if (keywords
) _exit (fail ("duplicate -k",EX_USAGE
));
157 if (argc
--) keywords
= cpystr (*++argv
);
158 else _exit (fail ("missing argument to -k",EX_USAGE
));
161 if (s
[2] && ((s
[2] == '-') || isdigit (s
[2]))) precedence
= atol (s
+ 2);
162 else if (argc
-- && ((*(s
= *++argv
) == '-') || isdigit (*s
)))
163 precedence
= atol (s
);
164 else _exit (fail ("missing argument to -p",EX_USAGE
));
166 default: /* anything else */
167 _exit (fail ("unknown switch",EX_USAGE
));
170 if (argc
> 1) _exit (fail ("too many recipients",EX_USAGE
));
171 else if (!(f
= tmpfile ())) _exit(fail ("can't make temp file",EX_TEMPFAIL
));
172 /* build delivery headers */
173 if (sender
) fprintf (f
,"Return-Path: <%s>\015\012",sender
);
174 /* start Received line: */
175 fprintf (f
,"Received: via dmail-%s.%s for %s; ",CCLIENTVERSION
,version
,
176 (argc
== 1) ? *argv
: myusername ());
179 fputs ("\015\012",f
);
180 /* copy text from standard input */
181 if (!fgets (tmp
,MAILTMPLEN
-1,stdin
) || !(s
= strchr (tmp
,'\n')) ||
182 (s
== tmp
) || s
[1]) _exit (fail ("bad first message line",EX_USAGE
));
183 else if (s
[-1] == '\015') { /* nuke leading "From " line */
184 if ((tmp
[0] != 'F') || (tmp
[1] != 'r') || (tmp
[2] != 'o') ||
185 (tmp
[3] != 'm') || (tmp
[4] != ' ')) fputs (tmp
,f
);
186 while ((c
= getchar ()) != EOF
) putc (c
,f
);
189 if ((tmp
[0] != 'F') || (tmp
[1] != 'r') || (tmp
[2] != 'o') ||
190 (tmp
[3] != 'm') || (tmp
[4] != ' ')) {
191 *s
++ = '\015'; /* overwrite NL with CRLF */
193 *s
= '\0'; /* tie off string */
194 fputs (tmp
,f
); /* write line */
197 /* copy text from standard input */
198 while ((c
= getchar ()) != EOF
) {
199 /* add CR if needed */
200 if (c
== '\012') putc ('\015',f
);
203 msglen
= ftell (f
); /* size of message */
204 fflush (f
); /* make sure all changes written out */
205 if (ferror (f
)) ret
= fail ("error writing temp file",EX_TEMPFAIL
);
206 else if (!msglen
) ret
= fail ("empty message",EX_TEMPFAIL
);
207 /* single delivery */
208 else ret
= deliver (f
,msglen
,argc
? *argv
: myusername ());
209 fclose (f
); /* all done with temporary file */
210 _exit (ret
); /* normal exit */
211 return 0; /* stupid gcc */
214 /* Deliver message to recipient list
215 * Accepts: file description of message temporary file
216 * size of message temporary file in bytes
218 * Returns: NIL if success, else error code
221 int deliver (FILE *f
,unsigned long msglen
,char *user
)
223 MAILSTREAM
*ds
= NIL
;
224 char *s
,*mailbox
,tmp
[MAILTMPLEN
],path
[MAILTMPLEN
];
227 /* have a mailbox specifier? */
228 if ((mailbox
= strchr (user
,'+')) != NULL
) {
229 *mailbox
++ = '\0'; /* yes, tie off user name */
230 if (!*mailbox
|| !compare_cstring ((unsigned char *) mailbox
,"INBOX"))
231 mailbox
= NIL
; /* user+ and user+INBOX same as user */
233 if (!*user
) user
= myusername ();
234 else if (strcmp (user
,myusername ()))
235 return fail ("can't deliver to other user",EX_CANTCREAT
);
236 sprintf (tmp
,"delivering to %.80s+%.80s",user
,mailbox
? mailbox
: "INBOX");
238 /* prepare stringstruct */
239 INIT (&st
,file_string
,(void *) f
,msglen
);
240 if (mailbox
) { /* non-INBOX name */
241 switch (mailbox
[0]) { /* make sure a valid name */
242 default: /* other names, try to deliver if not INBOX */
243 if ((strlen (mailbox
) <= NETMAXMBX
) &&
244 !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
);
254 mm_dlog ("retrying delivery to INBOX");
255 SETPOS (&st
,0); /* rewind stringstruct just in case */
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
);
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
);
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
);
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
);
304 sprintf (tmp
,"attempting to create mailbox %.80s path %.80s",mailbox
,path
);
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
);
310 /* deliver the message */
311 return deliver_safely (ds
,&st
,mailbox
,path
,tmp
);
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
321 long ibxpath (MAILSTREAM
*ds
,char **mailbox
,char *path
)
323 char *s
,tmp
[MAILTMPLEN
];
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 */
347 * Accepts: prototype stream to force mailbox format
348 * stringstruct of message temporary file or NIL for check only
350 * filesystem path name
351 * scratch buffer for messages
352 * Returns: NIL if success, else error code
355 int deliver_safely (MAILSTREAM
*prt
,STRING
*st
,char *mailbox
,char *path
,
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
);
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
);
374 if (sbuf
.st_mode
& 0111) { /* executable files may be worrisome */
375 sprintf (tmp
,"WARNING: file %.80s is executable",path
);
379 if (sbuf
.st_mode
& 0002) { /* public-write files may be worrisome */
380 sprintf (tmp
,"WARNING: file %.80s is publicly-writable",path
);
383 if (sbuf
.st_mode
& 0004) { /* public-write files may be worrisome */
384 sprintf (tmp
,"WARNING: file %.80s is publicly-readable",path
);
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
);
395 if (keywords
) { /* any keywords requested? */
396 if (flagseen
) sprintf (flags
= tmp
,"\\Seen %.1000s",keywords
);
397 else flags
= keywords
;
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
);
406 sprintf (tmp
,"delivered to %.80s",path
);
408 /* make sure nothing evil this way comes */
409 return delivery_unsafe (path
,&sbuf
,tmp
);
412 /* Verify that delivery is safe
415 * scratch buffer for messages
416 * Returns: NIL if delivery is safe, error code if unsafe
419 int delivery_unsafe (char *path
,struct stat
*sbuf
,char *tmp
)
422 sprintf (tmp
,"Verifying safe delivery to %.80s",path
);
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 */
432 case S_IFREG
: /* file is unsafe if setuid */
433 if (sbuf
->st_mode
& S_ISUID
) strcat (tmp
,"setuid file");
435 else if (sbuf
->st_mode
& S_ISGID
) strcat (tmp
,"setgid file");
436 else return NIL
; /* otherwise safe */
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;
443 sprintf (tmp
+ strlen (tmp
),"file type %07o",(unsigned int) type
);
445 return fail (tmp
,EX_CANTCREAT
);
449 * Accepts: string to output
452 int fail (char *string
,int code
)
454 mm_log (string
,ERROR
); /* pass up the string */
462 code
= EX_TEMPFAIL
; /* coerce these to TEMPFAIL */
465 case -1: /* quota failure... */
466 code
= EX_CANTCREAT
; /* ...really returns this code */
471 return code
; /* error code to return */
474 /* Co-routines from MAIL library */
477 /* Message matches a search
478 * Accepts: MAIL stream
482 void mm_searched (MAILSTREAM
*stream
,unsigned long msgno
)
484 fatal ("mm_searched() call");
488 /* Message exists (i.e. there are that many messages in the mailbox)
489 * Accepts: MAIL stream
493 void mm_exists (MAILSTREAM
*stream
,unsigned long number
)
495 fatal ("mm_exists() call");
500 * Accepts: MAIL stream
504 void mm_expunged (MAILSTREAM
*stream
,unsigned long number
)
506 fatal ("mm_expunged() call");
510 /* Message flags update seen
511 * Accepts: MAIL stream
515 void mm_flags (MAILSTREAM
*stream
,unsigned long number
)
520 * Accepts: MAIL stream
526 void mm_list (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
528 fatal ("mm_list() call");
532 /* Subscribed mailbox found
533 * Accepts: MAIL stream
539 void mm_lsub (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
541 fatal ("mm_lsub() call");
546 * Accepts: MAIL stream
551 void mm_status (MAILSTREAM
*stream
,char *mailbox
,MAILSTATUS
*status
)
553 fatal ("mm_status() call");
556 /* Notification event
557 * Accepts: MAIL stream
562 void mm_notify (MAILSTREAM
*stream
,char *string
,long errflg
)
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 */
571 /* Log an event for the user to see
572 * Accepts: string to log
576 void mm_log (char *string
,long errflg
)
578 if (trycreate
)mm_dlog(string
);/* debug logging only if trycreate in effect */
579 else { /* ordinary logging */
580 fprintf (stderr
,"%s\n",string
);
582 case NIL
: /* no error */
583 syslog (LOG_INFO
,"%s",string
);
585 case PARSE
: /* parsing problem */
586 case WARN
: /* warning */
587 syslog (LOG_WARNING
,"%s",string
);
589 case ERROR
: /* error */
591 syslog (LOG_ERR
,"%s",string
);
598 /* Log an event to debugging telemetry
599 * Accepts: string to log
602 void mm_dlog (char *string
)
604 if (debug
) fprintf (stderr
,"%s\n",string
);
605 syslog (LOG_DEBUG
,"%s",string
);
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
615 void mm_login (NETMBX
*mb
,char *username
,char *password
,long trial
)
617 fatal ("mm_login() call");
621 /* About to enter critical code
625 void mm_critical (MAILSTREAM
*stream
)
627 critical
= T
; /* note in critical code */
631 /* About to exit critical code
635 void mm_nocritical (MAILSTREAM
*stream
)
637 critical
= NIL
; /* note not in critical code */
644 * flag indicating that mailbox may be clobbered
645 * Returns: T if user wants to abort
648 long mm_diskerror (MAILSTREAM
*stream
,long errcode
,long serious
)
654 /* Log a fatal error event
655 * Accepts: string to log
658 void mm_fatal (char *string
)
660 printf ("?%s\n",string
); /* shouldn't happen normally */