1/************************************************************************ 2 * procmail - The autonomous mail processor * 3 * * 4 * It has been designed to be able to be run suid root and (in * 5 * case your mail spool area is *not* world writable) sgid * 6 * mail (or daemon), without creating security holes. * 7 * * 8 * Seems to be perfect. * 9 * * 10 * Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands * 11 * Copyright (c) 1999-2001, Philip Guenther, The United States * 12 * of America * 13 * #include "../README" * 14 ************************************************************************/ 15#ifdef RCS 16static /*const*/char rcsid[]= 17 "$Id: procmail.c,v 1.183 2001/08/31 04:57:36 guenther Exp $"; 18#endif 19#include "../patchlevel.h" 20#include "procmail.h" 21#include "acommon.h" 22#include "sublib.h" 23#include "robust.h" 24#include "shell.h" 25#include "misc.h" 26#include "memblk.h" 27#include "pipes.h" 28#include "common.h" 29#include "cstdio.h" 30#include "exopen.h" 31#include "mcommon.h" 32#include "goodies.h" 33#include "locking.h" 34#include "mailfold.h" 35#include "lastdirsep.h" 36#include "authenticate.h" 37#include "lmtp.h" 38#include "foldinfo.h" 39#include "variables.h" 40#include "comsat.h" 41#include "from.h" 42 43static const char*const nullp,exflags[]=RECFLAGS,drcfile[]="Rcfile:", 44 pmusage[]=PM_USAGE,*etcrc=ETCRC,misrecpt[]="Missing recipient\n", 45 extrns[]="Extraneous ",ignrd[]=" ignored\n",pardir[]=chPARDIR, 46 defspath[]=DEFSPATH,defmaildir[]=DEFmaildir; 47char*buf,*buf2,*loclock; 48const char shell[]="SHELL",lockfile[]="LOCKFILE",newline[]="\n",binsh[]=BinSh, 49 unexpeof[]="Unexpected EOL\n",*const*gargv,*const*restargv= &nullp,*sgetcp, 50 pmrc[]=PROCMAILRC,*rcfile,dirsep[]=DIRSEP,devnull[]=DevNull,empty[]="", 51 lgname[]="LOGNAME",executing[]="Executing",oquote[]=" \"",cquote[]="\"\n", 52 procmailn[]="procmail",whilstwfor[]=" whilst waiting for ",home[]="HOME", 53 host[]="HOST",*defdeflock=empty,*argv0=empty,curdir[]={chCURDIR,'\0'}, 54 slogstr[]="%s \"%s\"",conflicting[]="Conflicting ",orgmail[]="ORGMAIL", 55 insufprivs[]="Insufficient privileges\n",defpath[]=DEFPATH, 56 exceededlb[]="Exceeded LINEBUF\n",errwwriting[]="Error while writing to", 57 Version[]=VERSION; 58int retval=EX_CANTCREAT,retvl2=EXIT_SUCCESS,sh,pwait,rc= -1, 59 privileged=priv_START,lexitcode=EXIT_SUCCESS,ignwerr,crestarg,savstdout, 60 berkeley,mailfilter,erestrict,Deliverymode,ifdepth; /* depth of outermost */ 61struct dyna_array ifstack; 62size_t linebuf=mx(DEFlinebuf,1024/*STRLEN(systm_mbox)<<1*/); 63volatile int nextexit,lcking; /* if termination is imminent */ 64pid_t thepid; 65long filled,lastscore; /* the length of the mail, and the last score */ 66memblk themail; /* the mail */ 67char*thebody; /* the body of the message */ 68uid_t uid; 69gid_t gid,sgid; 70 71static auth_identity*savepass(spass,uid)auth_identity*const spass; 72 const uid_t uid; 73{ const auth_identity*tpass; 74 if(auth_filledid(spass)&&auth_whatuid(spass)==uid) 75 goto ret; 76 if(tpass=auth_finduid(uid,0)) /* save by copying */ 77 { auth_copyid(spass,tpass); 78ret: return spass; 79 } 80 return (auth_identity*)0; 81} 82 83#define rct_ABSOLUTE 0 /* rctypes for tryopen() */ 84#define rct_CURRENT 1 85#define rct_DEFAULT 2 86 87#define rcs_DELIVERED 1 /* rc exit codes for mainloop() */ 88#define rcs_EOF 2 89#define rcs_HOST 3 90 91static void 92 usage P((void)); 93static int 94 tryopen P((const int delay_setid,const int rctype,const int dowarning)), 95 mainloop P((void)); 96 97int main(argc,argv)int argc;const char*const argv[]; 98{ register char*chp,*chp2; 99#if 0 /* enable this if you want to trace procmail */ 100 kill(getpid(),SIGSTOP);/*raise(SIGSTOP);*/ 101#endif 102 newid(); 103 ;{ int presenviron,override;char*fromwhom=0; 104 const char*idhint=0;gid_t egid=getegid(); 105 presenviron=Deliverymode=mailfilter=override=0; 106 Openlog(procmailn,LOG_PID,LOG_MAIL); /* for the syslogd */ 107 if(argc) /* sanity check, any argument at all? */ 108 { Deliverymode=!!strncmp(lastdirsep(argv0=argv[0]),procmailn, 109 STRLEN(procmailn)); 110 for(argc=0;(chp2=(char*)argv[++argc])&&*chp2=='-';) 111 for(;;) /* processing options */ 112 { switch(*++chp2) 113 { case VERSIONOPT: 114 usage(); 115 return EXIT_SUCCESS; 116 case HELPOPT1:case HELPOPT2:elog(pmusage);elog(PM_HELP); 117 elog(PM_QREFERENCE); 118 return EX_USAGE; 119 case PRESERVOPT:presenviron=1; 120 continue; 121 case MAILFILTOPT:mailfilter=1; 122 continue; 123 case OVERRIDEOPT:override=1; 124 continue; 125 case BERKELEYOPT:case ALTBERKELEYOPT:berkeley=1; 126 continue; 127 case TEMPFAILOPT:retval=EX_TEMPFAIL; 128 continue; 129 case FROMWHOPT:case ALTFROMWHOPT: 130 if(*++chp2) 131 fromwhom=chp2; 132 else if(chp2=(char*)argv[argc+1]) 133 argc++,fromwhom=chp2; 134 else 135 nlog("Missing name\n"); 136 break; 137 case ARGUMENTOPT: 138 { static struct dyna_array newargv; 139 if(*++chp2) 140 goto setarg; 141 else if(chp2=(char*)argv[argc+1]) 142 { argc++; 143setarg: app_valp(newargv,(const char*)chp2); 144 restargv=&(acc_valp(newargv,0)); 145 crestarg++; 146 } 147 else 148 nlog("Missing argument\n"); 149 break; 150 } 151 case DELIVEROPT: 152 if(!*(chp= ++chp2)&&!(chp=(char*)argv[++argc])) 153 { nlog(misrecpt); 154 break; 155 } 156 else 157 { Deliverymode=1; 158 goto last_option; 159 } 160 case LMTPOPT: 161#ifdef LMTP 162 Deliverymode=2; 163 goto last_option; 164#else 165 nlog("LMTP support not enabled in this binary\n"); 166 return EX_USAGE; 167#endif 168 case '-': 169 if(!*++chp2) 170 { argc++; 171 goto last_option; 172 } 173 default:nlog("Unrecognised options:");logqnl(chp2); 174 elog(pmusage);elog("Processing continued\n"); 175 case '\0':; 176 } 177 break; 178 } 179 } 180 if(Deliverymode==1&&!(chp=chp2)) 181 nlog(misrecpt),Deliverymode=0; 182last_option: 183 switch(Deliverymode) 184 { case 0: 185 idhint=getenv(lgname); 186 if(mailfilter&&crestarg) 187 { crestarg=0; /* -m will supersede -a */ 188conflopt: nlog(conflicting);elog("options\n");elog(pmusage); 189 } 190 break; 191#ifdef LMTP 192 case 2: 193 if(fromwhom) 194 { fromwhom=0; /* -z disables -f, */ 195 goto confldopt; /* -p and -m */ 196 } 197#endif 198 case 1: 199 if(presenviron||mailfilter) 200confldopt: { presenviron=mailfilter=0; /* -d disables -p and -m */ 201 goto conflopt; 202 } 203 break; 204 default: /* this cannot happen */ 205 abort(); 206 } 207 cleanupenv(presenviron); 208 ;{ auth_identity*pass,*passinvk;auth_identity*spassinvk; 209 uid_t euid=geteuid(); 210 uid=getuid();gid=getgid(); 211 spassinvk=auth_newid();passinvk=savepass(spassinvk,uid); /* are we */ 212 checkprivFrom_(euid,passinvk?auth_username(passinvk):0,override); 213 doumask(INIT_UMASK); /* allowed to set the From_ line? */ 214 while((savstdout=rdup(STDOUT))<=STDERR) 215 { rclose(savstdout); /* move stdout out of the way */ 216 if(0>(savstdout=opena(devnull))) 217 goto nodevnull; 218 syslog(LOG_ALERT,"Descriptor %d was not open\n",savstdout); 219 } 220 fclose(stdout);rclose(STDOUT); /* just to make sure */ 221 if(0>opena(devnull)) 222nodevnull: 223 { writeerr(devnull);syslog(LOG_EMERG,slogstr,errwwriting,devnull); 224 return EX_OSFILE; /* couldn't open /dev/null */ 225 } 226#ifdef console 227 opnlog(console); 228#endif 229 setbuf(stdin,(char*)0);allocbuffers(linebuf,1); 230#ifdef SIGXCPU 231 signal(SIGXCPU,SIG_IGN);signal(SIGXFSZ,SIG_IGN); 232#endif 233#ifdef SIGLOST 234 signal(SIGLOST,SIG_IGN); 235#endif 236#if DEFverbose 237 verboff();verbon(); 238#else 239 verbon();verboff(); 240#endif 241#ifdef SIGCHLD 242 signal(SIGCHLD,SIG_DFL); 243#endif 244 signal(SIGPIPE,SIG_IGN); 245 setcomsat(empty); /* turn on biff by default */ 246 ultstr(0,(unsigned long)uid,buf);filled=0; 247 if(!passinvk||!(chp2=(char*)auth_username(passinvk))) 248 chp2=buf; 249#ifdef LMTP 250 if(Deliverymode==2) 251 { auth_identity**rcpts,**lastrcpt,**currcpt; 252 currcpt=rcpts=lmtp(&lastrcpt,chp2); 253 if(currcpt+1!=lastrcpt) /* if there's more than one recipient */ 254 lockblock(&themail); /* then no one can write to the block */ 255 else /* otherwise the only recipient */ 256 private(1); /* gets the original */ 257 while(currcpt<lastrcpt) 258 { if(!(pidchild=sfork())) 259 { setupsigs(); 260 pass= *currcpt++; 261 while(currcpt<lastrcpt) 262 auth_freeid(*currcpt++); 263 freeoverread(); 264 free(rcpts);newid();gargv=&nullp; 265 goto dorcpt; 266 } 267 if(forkerr(pidchild,procmailn)) 268 lexitcode=EX_OSERR; 269 else 270 waitfor(pidchild); 271 lmtpresponse(lexitcode); 272 pidchild=0; 273 auth_freeid(*currcpt++); 274 } 275 free(rcpts); 276 flushoverread(); /* pass upwards the extra LMTP data */ 277 exit(EXIT_SUCCESS); 278 } 279#endif 280 setupsigs(); 281 makeFrom(fromwhom,chp2); 282 readmail(0,0L); /* read in the mail completely */ 283 if(Deliverymode) 284 { if(argv[argc+1]) /* more than one recipient? */ 285 { private(0); /* time to share */ 286 lockblock(&themail); 287 } 288 else 289 private(1); 290 do /* chp should point to the first recipient */ 291 { chp2=chp; 292 if(argv[++argc]) /* more than one recipient */ 293 if(pidchild=sfork()) 294 { if(forkerr(pidchild,procmailn)|| 295 waitfor(pidchild)!=EXIT_SUCCESS) 296 retvl2=retval; 297 pidchild=0; /* loop for the next recipient */ 298 } 299 else 300 { newid(); 301 while(argv[++argc]); /* skip till end of command line */ 302 break; 303 } 304 } 305 while(chp=(char*)argv[argc]); 306 } 307 gargv=argv+argc; /* save it for nextrcfile() */ 308 if(Deliverymode) /* try recipient without changing case first */ 309 { if(!(pass=auth_finduser(chp2,-1))) /* chp2 is the recipient */ 310 { static const char unkuser[]="Unknown user"; 311 nlog(unkuser);logqnl(chp2);syslog(LOG_ERR,slogstr,unkuser,chp2); 312 return EX_NOUSER; /* we don't handle strangers */ 313 } 314dorcpt: if(enoughprivs(passinvk,euid,egid,auth_whatuid(pass), 315 auth_whatgid(pass))) 316 goto Setuser; 317 nlog(insufprivs); 318 syslog(LOG_CRIT,"Insufficient privileges to deliver to \"%s\"\n", 319 chp2); 320 return EX_NOPERM; /* need more mana, decline the request */ 321 } 322 else 323 { int commandlinerc=nextrcfile(); 324 if(presenviron) /* preserving the environment? */ 325 etcrc=0; /* don't read etcrc then */ 326 if(commandlinerc) /* command-line rcfile? */ 327 { etcrc=0; /* forget etcrc and comsat: */ 328 setcomsat(DEFcomsat); /* the internal flag */ 329 if(!presenviron) /* and usually the variable */ 330 setdef(scomsat,DEFcomsat); 331 } 332 if(mailfilter) 333 { if(!commandlinerc) 334 { nlog("Missing rcfile\n"); 335 return EX_NOINPUT; 336 } 337#ifdef ETCRCS 338 ;{ static const char etcrcs[]=ETCRCS; 339 if(!strncmp(etcrcs,rcfile,STRLEN(etcrcs))) 340 { struct stat stbuf; /* path starts with /etc/procmailrcs/ */ 341 /* 342 * although the filename starts with /etc/procmailrcs/ 343 * we will now check if it does not contain any backward 344 * references which would allow it to escape the secure 345 * tree; look for /../ sequences 346 */ 347 for(chp=(char*)rcfile+STRLEN(etcrcs)-1; 348 chp; /* any devious embedded /../? */ 349 chp=strpbrk(chp,dirsep)) 350 if(!strncmp(pardir,++chp,STRLEN(pardir))&& 351 (chp+=STRLEN(pardir),strchr(dirsep,*chp))) 352 goto nospecial; /* yes, security violation */ 353#ifdef CAN_chown 354 *(chp=strcpy(buf2,etcrcs)+STRLEN(etcrcs))=chCURDIR; 355 *++chp='\0'; 356#endif 357 /* 358 * so far, so good, now verify the credentials down to the 359 * last bit 360 */ 361 if(presenviron|| /* -p is dangerous */ 362 commandlinerc!=2|| /* so are variable assignments */ 363#ifdef CAN_chown /* anyone can chown in this filesystem so: */ 364 stat(buf2,&stbuf)|| /* the /etc/procmailrcs */ 365 !S_ISDIR(stbuf.st_mode)|| /* directory must be */ 366 stbuf.st_uid!=ROOT_uid&& /* owned by root */ 367 chown(buf2,ROOT_uid,stbuf.st_gid)|| 368 stbuf.st_mode&(S_IXGRP|S_IXOTH)&& /* and accessible */ 369 chmod(buf2,S_IRWXU)|| /* to no one else */ 370#endif 371 lstat(rcfile,&stbuf)|| /* it seems to exist */ 372 !enoughprivs(passinvk,euid,egid,stbuf.st_uid, 373 stbuf.st_gid)|| /* can we do this at all? */ 374 S_ISDIR(stbuf.st_mode)|| /* no directories! */ 375 !savepass(spassinvk,stbuf.st_uid) /* user exists? */ 376 ) 377nospecial: { static const char densppr[]= 378 "Denying special privileges for"; 379 nlog(densppr);logqnl(rcfile); 380 syslog(LOG_ALERT,slogstr,densppr,rcfile); 381 } 382 else /* no security violation */ 383 mailfilter=2,passinvk=spassinvk; 384 } /* accept new identity */ 385 } 386#endif 387 } 388 } 389 pass=passinvk; 390 if(passinvk&&idhint) /* if same uid as $LOGNAME, use latter */ 391 { auth_identity*idpass=auth_finduser((char*)idhint,0); 392 if(idpass) 393 { if(auth_whatuid(passinvk)==auth_whatuid(idpass)) 394 pass=idpass; 395 } 396 } 397 if(pass) /* set preferred uid to the intended recipient */ 398Setuser: { gid=auth_whatgid(pass);uid=auth_whatuid(pass); 399 if(euid==ROOT_uid&&(chp=(char*)auth_username(pass))&&*chp) 400 initgroups(chp,gid); 401 endgrent(); 402 } 403 else /* user could not be found */ 404 setids(); /* to prevent security holes, drop any privileges now */ 405 initdefenv(pass,buf,!presenviron||!mailfilter); /* override */ 406 endpwent();auth_freeid(spassinvk); /* environment by default */ 407 } 408 if(buildpath(orgmail,fdefault,(char*)0)) /* setup DEFAULT and ORGMAIL */ 409 { fdefault=empty; /* uh, Houston, we have a problem */ 410 goto nix_sysmbox; 411 } 412 /* 413 * Processing point of proposed /etc/procmail.conf file 414 */ 415 fdefault=tstrdup(buf);sgid=egid; 416 chp=(char*)tgetenv(orgmail); 417 if(mailfilter||!screenmailbox(chp,egid,Deliverymode)) 418nix_sysmbox: 419 { sputenv(orgmail); /* nix delivering to system mailbox */ 420 if(privileged) /* don't need this any longer */ 421 privileged=priv_DONTNEED; 422 if(!strcmp(chp,fdefault)) /* DEFAULT the same? */ 423 free((char*)fdefault),fdefault=empty; /* so panic */ 424 } /* bad news, be conservative */ 425 } 426 doumask(INIT_UMASK); 427 while(chp=(char*)argv[argc]) /* interpret command line specs first */ 428 { argc++; 429 if(!asenvcpy(chp)&&mailfilter) 430 { gargv= &nullp; /* stop at the first rcfile */ 431 for(restargv=argv+argc;restargv[crestarg];crestarg++); 432 break; 433 } 434 } 435 if(etcrc) /* do we start with an /etc/procmailrc file first? */ 436 { if(0<=bopen(etcrc)) 437 { yell(drcfile,etcrc); 438#if !DEFverbose 439 if(privileged) 440 verbose=0; /* no peeking in /etc/procmailrc */ 441#endif 442 eputenv(defspath,buf); /* use the secure PATH */ 443 if(mainloop()==rcs_DELIVERED) /* run the rcfile */ 444 goto mailed; 445 eputenv(defpath,buf); /* switch back to the insecure PATH */ 446 } 447 } 448 erestrict=mailfilter!=2; /* possibly restrict execs now */ 449 if(rcfile&&!mailfilter) /* "procmail rcfile..." */ 450 { int rctype,dowarning; /* only warn about the first missing rcfile */ 451 for(dowarning=1;;) 452 { rctype=rct_ABSOLUTE; 453 if(strchr(dirsep,*rcfile)) /* absolute? */ 454 strcpy(buf,rcfile); 455 else if(*rcfile==chCURDIR&&strchr(dirsep,rcfile[1])) /* ./ prefix? */ 456 strcpy(buf,rcfile),rctype=rct_CURRENT; 457 else /* prepend default procmailrc directory */ 458 if(buildpath(maildir,defmaildir,rcfile)) 459 break; 460 if(tryopen(0,rctype,dowarning)) 461 { register int rcs=mainloop(); /* run it */ 462 if(rcs==rcs_DELIVERED) 463 goto mailed; /* success! */ 464 if(rcs==rcs_EOF) 465 break; /* normal end of rcfile */ 466 if(!nextrcfile()) /* none left? */ 467 goto mailed; /* then out */ 468 } 469 else /* not available? try the next */ 470 { dowarning=0; /* suppress further messages */ 471 if(!nextrcfile()) /* none left? */ 472 break; /* then out */ 473 } 474 } 475 } 476 else 477 { int rctype; 478 if(!rcfile) /* no rcfile on the command line */ 479 { rctype=rct_DEFAULT; 480 if(buildpath("default rcfile",pmrc,(char*)0)) 481 goto nomore_rc; 482 } 483 else /* mailfilter mode */ 484 { rctype=strchr(dirsep,*rcfile)?rct_ABSOLUTE:rct_CURRENT; 485 strcpy(buf,rcfile); 486 } 487 if(tryopen(mailfilter==2,rctype,DEFverbose||mailfilter)) 488 if(mainloop()!=rcs_EOF) 489 goto mailed; 490 } 491nomore_rc: 492 if(ifstack.vals) 493 free(ifstack.vals); 494 ;{ int succeed; 495 concon('\n');succeed=0; 496 if(*(chp=(char*)fdefault)) /* DEFAULT set? */ 497 { int len; 498 setuid(uid); /* make sure we have enough space */ 499 if(linebuf<(len=strlen(chp)+strlen(lockext)+UNIQnamelen)) 500 allocbuffers(linebuf=len,1); /* to perform the lock & delivery */ 501 if(writefolder(chp,(char*)0,themail.p,filled,0,1)) /* default */ 502 succeed=1; 503 } /* if all else failed */ 504 if(!succeed&&*(chp2=(char*)tgetenv(orgmail))&&strcmp(chp2,chp)) 505 { rawnonl=0; 506 if(writefolder(chp2,(char*)0,themail.p,filled,0,0)) /* don't panic */ 507 succeed=1; /* try the last resort */ 508 } 509 if(succeed) /* should we panic now? */ 510mailed: retval=EXIT_SUCCESS; /* we're home free, mail delivered */ 511 } 512 unlock(&loclock);Terminate(); 513} 514 515static void usage P((void)) 516{ elog(procmailn);elog(Version); 517 elog("\nLocking strategies:\tdotlocking"); 518#ifndef NOfcntl_lock 519 elog(", fcntl()"); /* a peek under the hood */ 520#endif 521#ifdef USElockf 522 elog(", lockf()"); 523#endif 524#ifdef USEflock 525 elog(", flock()"); 526#endif 527 elog("\nDefault rcfile:\t\t");elog(pmrc); 528#ifdef GROUP_PER_USER 529 elog("\n\tIt may be writable by your primary group"); 530#endif 531 elog("\nYour system mailbox:\t"); 532 elog(auth_mailboxname(auth_finduid(getuid(),0))); 533 elog(newline); 534#ifdef USE_MMAP 535 elog("\nLarge messages will be memory mapped during processing.\n"); 536#endif 537} 538 539/* 540 * if we happen to be still running as root, and the rcfile 541 * is mounted on a secure NFS-partition, we might not be able 542 * to access it, so check if we can stat it or don't need any 543 * sgid privileges, if yes, drop all privs and set uid to 544 * the recipient beforehand 545 */ 546static int tryopen(delay_setid,rctype,dowarning) 547 const int delay_setid,rctype,dowarning; 548{ struct stat stbuf; 549 if(!delay_setid&&privileged&& /* if we can setid now and we haven't yet, */ 550 (privileged==priv_DONTNEED||!stat(buf,&stbuf))) /* and we either don't */ 551 setids(); /* need the privileges or it's accessible, then setid now */ 552 if(0>bopen(buf)) /* try opening the rcfile */ 553 { if(dowarning) 554rerr: readerr(buf); 555 return 0; 556 } 557 if(!delay_setid&&privileged) /* if we're not supposed to delay */ 558 { closerc(); /* and we haven't changed yet, then close it, */ 559 setids(); /* transmogrify to prevent peeking, */ 560 if(0>bopen(buf)) /* and try again */ 561 goto rerr; /* they couldn't read it, so it was bogus */ 562 } 563#ifndef NOfstat 564 if(fstat(rc,&stbuf)) /* the right way */ 565#else 566 if(stat(buf,&stbuf)) /* the best we can */ 567#endif 568 { static const char susprcf[]="Suspicious rcfile"; 569suspicious_rc: 570 closerc();nlog(susprcf);logqnl(buf); 571 syslog(LOG_ALERT,slogstr,susprcf,buf); 572 goto rerr; 573 } 574 if(delay_setid) /* change now if we haven't already */ 575 setids(); 576 if(rctype==rct_CURRENT) /* opened rcfile in the current directory? */ 577 { if(!didchd) 578 setmaildir(curdir); 579 } 580 else 581 /* 582 * OK, so now we have opened an absolute rcfile, but for security 583 * reasons we only accept it if it is owned by the recipient or 584 * root and is not world writable, and the directory it is in is 585 * not world writable or has the sticky bit set. If this is the 586 * default rcfile then we also outlaw group writability. 587 */ 588 { register char*chp=lastdirsep(buf),c; 589 c= *chp; 590 if(((stbuf.st_uid!=uid&&stbuf.st_uid!=ROOT_uid|| /* check uid, */ 591 (stbuf.st_mode&S_IWOTH)|| /* writable by others, */ 592 rctype==rct_DEFAULT&& /* if the default then also check */ 593 (stbuf.st_mode&S_IWGRP)&& /* for writable by group, */ 594 (NO_CHECK_stgid||stbuf.st_gid!=gid) 595 )&&strcmp(devnull,buf)|| /* /dev/null is a special case, */ 596 (*chp='\0',stat(buf,&stbuf))|| /* check the directory, */ 597#ifndef CAN_chown /* sticky and can't chown */ 598 !(stbuf.st_mode&S_ISVTX)&& /* means we don't care if */ 599#endif /* it's group or world writable */ 600 ((stbuf.st_mode&(S_IWOTH|S_IXOTH))==(S_IWOTH|S_IXOTH)|| 601 rctype==rct_DEFAULT&& 602 (stbuf.st_mode&(S_IWGRP|S_IXGRP))==(S_IWGRP|S_IXGRP)&& 603 (NO_CHECK_stgid||stbuf.st_gid!=gid)))) 604 { *chp=c; 605 goto suspicious_rc; 606 } 607 *chp=c; 608 } 609 yell(drcfile,buf); 610 /* 611 * Chdir now if we haven't already 612 */ 613 if(!didchd) /* have we done this already? */ 614 { const char*chp; 615 if(buildpath(maildir,defmaildir,(char*)0)) 616 exit(EX_OSERR); /* something was wrong: give up the ghost */ 617 if(chdir(chp=buf)) 618 { chderr(buf); /* no, well, then try an initial chdir */ 619 chp=tgetenv(home); 620 if(!strcmp(chp,buf)||chdir(chp)) 621 chderr(chp),chp=curdir; /* that didn't work, use "." */ 622 } 623 setmaildir(chp); 624 } 625 return 1; /* we're good to go */ 626} 627 628static int mainloop P((void)) 629{ int lastsucc,lastcond,prevcond,i,skiprc;register char*chp,*tolock; 630 lastsucc=lastcond=prevcond=skiprc=0; 631 tolock=0; 632 do 633 { unlock(&loclock); /* unlock any local lockfile */ 634 goto commint; 635 do 636 { skipline(); 637commint:do skipspace(); /* skip whitespace */ 638 while(testB('\n')); 639 } 640 while(testB('#')); /* no comment :-) */ 641 if(testB(':')) /* check for a recipe */ 642 { int locknext,succeed;char*startchar;long tobesent; 643 static char flags[maxindex(exflags)]; 644 do 645 { int nrcond; 646 if(readparse(buf,getb,0,skiprc)) 647 return rcs_EOF; /* give up on this one */ 648 ;{ char*temp; /* so that chp isn't forced */ 649 nrcond=strtol(buf,&temp,10);chp=temp; /* into memory */ 650 } 651 if(chp==buf) /* no number parsed */ 652 nrcond= -1; 653 if(tolock) /* clear temporary buffer for lockfile name */ 654 free(tolock); 655 for(i=maxindex(flags);i;i--) /* clear the flags */ 656 flags[i]=0; 657 for(tolock=0,locknext=0;;) 658 { chp=skpspace(chp); 659 switch(i= *chp++) 660 { default: 661 ;{ char*flg; 662 if(!(flg=strchr(exflags,i))) /* a valid flag? */ 663 { chp--; 664 break; 665 } 666 flags[flg-exflags]=1; /* set the flag */ 667 } 668 case '\0': 669 if(chp!=Tmnate) /* if not the real end, skip */ 670 continue; 671 break; 672 case ':':locknext=1; /* yep, local lockfile specified */ 673 if(*chp||++chp!=Tmnate) 674 tolock=tstrdup(chp),chp=strchr(chp,'\0')+1; 675 } 676 concatenate(chp);skipped(chp); /* display leftovers */ 677 break; 678 } /* parse & test the conditions */ 679 i=conditions(flags,prevcond,lastsucc,lastcond,skiprc!=0,nrcond); 680 if(!skiprc) 681 { if(!flags[ALSO_NEXT_RECIPE]&&!flags[ALSO_N_IF_SUCC]) 682 lastcond=i==1; /* save the outcome for posterity */ 683 if(!prevcond||!flags[ELSE_DO]) 684 prevcond=i==1; /* ditto for `else if' like constructs */ 685 } 686 } 687 while(i==2); /* missing in action, reiterate */ 688 startchar=themail.p;tobesent=filled; 689 if(flags[PASS_HEAD]) /* body, header or both? */ 690 { if(!flags[PASS_BODY]) 691 tobesent=thebody-themail.p; 692 } 693 else if(flags[PASS_BODY]) 694 tobesent-=(startchar=thebody)-themail.p; 695 Stdout=0;succeed=sh=0; 696 pwait=flags[WAIT_EXIT]|flags[WAIT_EXIT_QUIET]<<1; 697 ignwerr=flags[IGNORE_WRITERR];skipspace(); 698 if(i) 699 zombiecollect(),concon('\n'); 700progrm: if(testB('!')) /* forward the mail */ 701 { if(!i) 702 skiprc|=1; 703 if(strlcpy(buf,sendmail,linebuf)>=linebuf) 704 goto fail; 705 chp=strchr(buf,'\0'); 706 if(*flagsendmail) 707 { char*q;int got=0; 708 if(!(q=simplesplit(chp+1,flagsendmail,buf+linebuf-1,&got))) 709 goto fail; 710 *(chp=q)='\0'; 711 } 712 if(readparse(chp+1,getb,0,skiprc)) 713 goto fail; 714 if(i) 715 { if(startchar==themail.p) 716 { startchar[filled]='\0'; /* just in case */ 717 startchar=(char*)skipFrom_(startchar,&tobesent); 718 } /* leave off leading From_ -- it confuses some mailers */ 719 goto forward; 720 } 721 skiprc&=~1; 722 } 723 else if(testB('|')) /* pipe the mail */ 724 { chp=buf2; 725 if(getlline(chp,buf2+linebuf)) /* get the command to start */ 726 goto commint; 727 if(i) 728 { metaparse(buf2); 729 if(!sh&&buf+1==Tmnate) /* just a pipe symbol? */ 730 { *buf='|';*(char*)(Tmnate++)='\0'; /* fake it */ 731 goto tostdout; 732 } 733forward: if(locknext) 734 { if(!tolock) /* an explicit lockfile specified already */ 735 { *buf2='\0'; /* find the implicit lockfile ('>>name') */ 736 for(chp=buf;i= *chp++;) 737 if(i=='>'&&*chp=='>') 738 { chp=skpspace(chp+1); 739 tmemmove(buf2,chp,i=strcspn(chp,EOFName)); 740 buf2[i]='\0'; 741 if(sh) /* expand any environment variables */ 742 { chp=tstrdup(buf);sgetcp=buf2; 743 if(readparse(buf,sgetc,0,0)) 744 { *buf2='\0'; 745 goto nolock; 746 } 747 strcpy(buf2,buf);strcpy(buf,chp);free(chp); 748 } 749 break; 750 } 751 if(!*buf2) 752nolock: { nlog("Couldn't determine implicit lockfile from"); 753 logqnl(buf); 754 } 755 } 756 lcllock(tolock,buf2); 757 if(!pwait) /* try and protect the user from his */ 758 pwait=2; /* blissful ignorance :-) */ 759 } 760 rawnonl=flags[RAW_NONL]; 761 if(flags[CONTINUE]&&(flags[FILTER]||Stdout)) 762 nlog(extrns),elog("copy-flag"),elog(ignrd); 763 inittmout(buf); 764 if(flags[FILTER]) 765 { if(startchar==themail.p&&tobesent!=filled) /* if only 'h' */ 766 { if(!pipthrough(buf,startchar,tobesent)) 767 readmail(1,tobesent),succeed=!pipw; 768 } 769 else if(!pipthrough(buf,startchar,tobesent)) 770 { filled=startchar-themail.p; 771 readmail(0,tobesent); 772 succeed=!pipw; 773 } 774 } 775 else if(Stdout) /* capturing stdout again? */ 776 succeed=!pipthrough(buf,startchar,tobesent); 777 else if(!pipin(buf,startchar,tobesent,1)) /* regular program */ 778 { succeed=1; 779 if(flags[CONTINUE]) 780 goto logsetlsucc; 781 else 782 goto frmailed; 783 } 784 goto setlsucc; 785 } 786 } 787 else if(testB(EOF)) 788 nlog("Incomplete recipe\n"); 789 else /* dump the mail into a mailbox file or directory */ 790 { int ofiltflag;char*end=buf+linebuf-4; /* reserve some room */ 791 if(ofiltflag=flags[FILTER]) 792 flags[FILTER]=0,nlog(extrns),elog("filter-flag"),elog(ignrd); 793 if(chp=gobenv(buf,end)) /* can it be an environment name? */ 794 { if(chp==end) 795 { getlline(buf,buf+linebuf); 796 goto fail; 797 } 798 if(skipspace()) 799 chp++; /* keep pace with argument breaks */ 800 if(testB('=')) /* is it really an assignment? */ 801 { int c; 802 *chp++='=';*chp='\0'; 803 if(skipspace()) 804 chp++; 805 ungetb(c=getb()); 806 switch(c) 807 { case '!':case '|': /* ok, it's a pipe */ 808 if(i) 809 Stdout = tstrdup(buf); 810 goto progrm; 811 } 812 } 813 } /* find the end, start of a nesting recipe? */ 814 else if((chp=strchr(buf,'\0'))==buf&& 815 testB('{')&& 816 (*chp++='{',*chp='\0',testB(' ')|| /* } } */ 817 testB('\t')|| 818 testB('\n'))) 819 { if(locknext&&!flags[CONTINUE]) 820 nlog(extrns),elog("locallockfile"),elog(ignrd); 821 if(flags[PASS_BODY]) 822 nlog(extrns),elog("deliver-body flag"),elog(ignrd); 823 if(flags[PASS_HEAD]) 824 nlog(extrns),elog("deliver-head flag"),elog(ignrd); 825 if(flags[IGNORE_WRITERR]) 826 nlog(extrns),elog("ignore-write-error flag"),elog(ignrd); 827 if(flags[RAW_NONL]) 828 nlog(extrns),elog("raw-mode flag"),elog(ignrd); 829 if(!i) /* no match? */ 830 skiprc+=2; /* increase the skipping level */ 831 else 832 { app_vali(ifstack,prevcond); /* push prevcond */ 833 app_vali(ifstack,lastcond); /* push lastcond */ 834 if(locknext) 835 { lcllock(tolock,""); 836 if(!pwait) /* try and protect the user from his */ 837 pwait=2; /* blissful ignorance :-) */ 838 } 839 succeed=1; 840 if(flags[CONTINUE]) 841 { yell("Forking",procmailn); 842 private(0); /* can't share anymore */ 843 inittmout(procmailn);onguard(); 844 if(!(pidchild=sfork())) /* clone yourself */ 845 { if(loclock) /* lockfiles are not inherited */ 846 free(loclock),loclock=0; 847 if(globlock) 848 free(globlock),globlock=0; /* clear up the */ 849 newid();offguard();duprcs(); /* identity crisis */ 850 } 851 else 852 { offguard(); 853 if(forkerr(pidchild,procmailn)) 854 succeed=0; /* tsk, tsk, no cloning today */ 855 else 856 { int excode; /* wait for our significant other? */ 857 if(pwait&& 858 (excode=waitfor(pidchild))!=EXIT_SUCCESS) 859 { if(!(pwait&2)||verbose) /* do we report it? */ 860 progerr(procmailn,excode,pwait&2); 861 succeed=0; 862 } 863 pidchild=0;skiprc+=2; /* skip over the braces */ 864 ifstack.filled-=2; /* retract the stack */ 865 } 866 } 867 } 868 goto setlsucc; /* definitely no logabstract */ 869 } 870 continue; 871 } 872 if(!i) /* no match? */ 873 skiprc|=1; /* temporarily disable subprograms */ 874 if(readparse(chp,getb,0,skiprc)) 875fail: { succeed=0; 876 goto setlsucc; 877 } 878 if(i) 879 { if(ofiltflag) /* protect who use bogus filter-flags */ 880 startchar=themail.p,tobesent=filled; /* whole message */ 881tostdout: rawnonl=flags[RAW_NONL]; 882 if(locknext) /* write to a file or directory */ 883 lcllock(tolock,buf); 884 inittmout(buf); /* to break messed-up kernel locks */ 885 if(writefolder(buf,strchr(buf,'\0')+1,startchar,tobesent, 886 ignwerr,0)&& 887 (succeed=1,!flags[CONTINUE])) 888frmailed: { if(ifstack.vals) 889 free(ifstack.vals); 890 return rcs_DELIVERED; 891 } 892logsetlsucc: if(succeed&&flags[CONTINUE]&&lgabstract==2) 893 logabstract(tgetenv(lastfolder)); 894setlsucc: rawnonl=0;lastsucc=succeed;lasttell= -1; /* for comsat */ 895 resettmout(); /* clear any pending timer */ 896 } 897 skiprc&=~1; /* reenable subprograms */ 898 } 899 } /* { */ 900 else if(testB('}')) /* end block */ 901 { if(skiprc>1) /* just skipping */ 902 skiprc-=2; /* decrease level */ 903 else if(ifstack.filled>ifdepth) /* restore lastcond from stack */ 904 { lastcond=acc_vali(ifstack,--ifstack.filled); 905 prevcond=acc_vali(ifstack,--ifstack.filled); /* prevcond */ 906 } /* as well */ 907 else 908 nlog("Closing brace unexpected\n"); /* stack empty */ 909 } 910 else /* then it must be an assignment */ 911 { char*end=buf+linebuf; 912 if(!(chp=gobenv(buf,end))) 913 { if(!*buf) /* skip a word first */ 914 getbl(buf,end); /* then a line */ 915 skipped(buf); /* display leftovers */ 916 continue; 917 } 918 if(chp==end) /* overflow => give up */ 919 break; 920 skipspace(); 921 if(testB('=')) /* removal or assignment? */ 922 { *chp='='; 923 if(readparse(++chp,getb,1,skiprc)) 924 continue; 925 } 926 else 927 *++chp='\0'; /* throw in a second terminator */ 928 if(!skiprc) 929 { const char*p; 930 p=sputenv(buf); 931 chp[-1]='\0'; 932 asenv(p); 933 } 934 } 935 if(rc<0) /* abnormal exit from the rcfile? */ 936 return rcs_HOST; 937 } 938 while(!testB(EOF)||(skiprc=0,poprc())); 939 return rcs_EOF; 940} 941