1 /************************************************************************
2 * Miscellaneous routines used by procmail *
3 * *
4 * Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands *
5 * Copyright (c) 1999-2001, Philip Guenther, The United States *
6 * of America *
7 * #include "../README" *
8 ************************************************************************/
9 #ifdef RCS
10 static /*const*/char rcsid[]=
11 "$Id: misc.c,v 1.117 2001/06/26 08:46:48 guenther Exp $";
12 #endif
13 #include "procmail.h"
14 #include "acommon.h"
15 #include "sublib.h"
16 #include "robust.h"
17 #include "misc.h"
18 #include "pipes.h"
19 #include "common.h"
20 #include "cstdio.h"
21 #include "exopen.h"
22 #include "regexp.h"
23 #include "mcommon.h"
24 #include "goodies.h"
25 #include "locking.h"
26 #include "comsat.h"
27 #include "mailfold.h"
28 #include "lastdirsep.h"
29 #include "memblk.h"
30 #include "authenticate.h"
31 #include "variables.h"
32 #include "shell.h"
33
34 /* line buffered to keep concurrent entries untangled */
elog(newt)35 void elog(newt)const char*const newt;
36 { int lnew;size_t i;static size_t lold,lmax;static char*old;
37 if(lcking&lck_LOGGING) /* reentered via sighandler */
38 lold=lmax=0; /* so give up on any buffered message */
39 i=lold+(lnew=strlen(newt)); /* calculate additional and total lengths */
40 if(lnew&& /* if this is not a forced flush and */
41 (lmax>=i|| /* either we have enough room in the buffer or */
42 (MAXlogbuf>=i&& /* the buffer won't get too big and */
43 !nextexit))) /* we're not in a signal handler, then it's safe */
44 { if(i>lmax) /* to use or expand the buffer */
45 { char*p;size_t newmax=lmax*2; /* exponential expansion by default */
46 if(i>newmax) /* ...unless we need more */
47 newmax=i;
48 if(MINlogbuf>newmax) /* ...or that would be too small */
49 newmax=MINlogbuf;
50 lcking|=lck_LOGGING; /* about to change old or lmax */
51 if(p=lmax?frealloc(old,newmax):fmalloc(newmax))/* fragile allocation */
52 lmax=newmax,old=p; /* update the values */
53 lcking&=~lck_LOGGING; /* okay, they're stable again */
54 if(!p) /* couldn't expand the buffer? */
55 goto flush; /* then flush it now */
56 } /* okay, we now have enough buffer space */
57 tmemmove(old+lold,newt,lnew); /* append the new text */
58 lnew=0;lold=i; /* the text in newt is now in the buffer */
59 if(old[i-1]!='\n') /* if we don't need to flush now */
60 return; /* then we're done */
61 }
62 flush:
63 #ifndef O_CREAT
64 lseek(STDERR,(off_t)0,SEEK_END); /* locking should be done actually */
65 #endif
66 if(lold) /* anything buffered? */
67 { rwrite(STDERR,old,lold);
68 lold=0; /* we never free the buffer */
69 }
70 if(lnew)
71 rwrite(STDERR,newt,lnew);
72 }
73
ignoreterm(void)74 void ignoreterm P((void))
75 { signal(SIGTERM,SIG_IGN);signal(SIGHUP,SIG_IGN);signal(SIGINT,SIG_IGN);
76 signal(SIGQUIT,SIG_IGN);
77 }
78
shutdesc(void)79 void shutdesc P((void))
80 { rclose(savstdout);closelog();closerc();
81 }
82
83 /* On systems with `capabilities', setuid/setgid can fail for root! */
checkroot(c,Xid)84 void checkroot(c,Xid)const int c;const unsigned long Xid;
85 { uid_t eff;
86 if((eff=geteuid())!=ROOT_uid&&getuid()!=ROOT_uid)
87 return;
88 syslog(LOG_CRIT,"set%cid(%lu) failed with ruid/euid = %lu/%lu",c,Xid,
89 (unsigned long)getuid(),(unsigned long)eff);
90 nlog(insufprivs);
91 exit(EX_NOPERM);
92 }
93
setids(void)94 void setids P((void))
95 { if(privileged)
96 { if(setRgid(gid)&& /* due to these !@#$%^&*() POSIX semantics, setgid() */
97 setgid(gid)) /* sets the saved gid as well; we can't use that! */
98 checkroot('g',(unsigned long)gid); /* did setgid fail as root? */
99 setruid(uid);
100 if(setuid(uid)) /* "This cannot happen" */
101 checkroot('u',(unsigned long)uid); /* Whoops... */
102 setegid(gid);
103 privileged=0;
104 #if !DEFverbose
105 verbose=0; /* to avoid peeking in rcfiles using SIGUSR1 */
106 #endif
107 }
108 }
109
writeerr(line)110 void writeerr(line)const char*const line;
111 { nlog(errwwriting);logqnl(line);
112 }
113
forkerr(pid,a)114 int forkerr(pid,a)const pid_t pid;const char*const a;
115 { if(pid==-1)
116 { nlog("Failed forking");logqnl(a);
117 return 1;
118 }
119 return 0;
120 }
121
progerr(line,xitcode,okay)122 void progerr(line,xitcode,okay)const char*const line;int xitcode,okay;
123 { charNUM(num,thepid);
124 nlog(okay?"Non-zero exitcode (":"Program failure (");
125 ltstr(0,(long)xitcode,num);elog(num);elog(okay?") from":") of");
126 logqnl(line);
127 }
128
chderr(dir)129 void chderr(dir)const char*const dir;
130 { nlog("Couldn't chdir to");logqnl(dir);
131 }
132
readerr(file)133 void readerr(file)const char*const file;
134 { nlog("Couldn't read");logqnl(file);
135 }
136
buildpath(name,path,file)137 int buildpath(name,path,file)const char*name,*const path,*const file;
138 { static const char toolong[]=" path too long",
139 notabsolute[]=" is not an absolute path";
140 sgetcp=path;
141 if(readparse(buf,sgetc,2,0))
142 { syslog(LOG_CRIT,"%s%s for LINEBUF for uid \"%lu\"\n",name,toolong,
143 (unsigned long)uid);
144 bad: nlog(name);elog(toolong);elog(newline);
145 return 1;
146 }
147 if(!strchr(dirsep,buf[0]))
148 { nlog(name);elog(notabsolute);elog(newline);
149 syslog(LOG_CRIT,"%s%s for uid \"%lu\"\n",name,notabsolute,
150 (unsigned long)uid);
151 return 1;
152 }
153 if(file)
154 { char*chp=strchr(buf,'\0');
155 if(chp-buf+strlen(file)+2>linebuf) /* +2 for / and \0 */
156 { name="full rcfile"; /* this should be passed in... XXX */
157 goto bad;
158 }
159 *chp++= *MCDIRSEP_;
160 strcpy(chp,file); /* append the filename */
161 }
162 return 0;
163 }
164
verboff(void)165 void verboff P((void))
166 { verbose=0;
167 #ifdef SIGUSR1
168 qsignal(SIGUSR1,verboff);
169 #endif
170 }
171
verbon(void)172 void verbon P((void))
173 { verbose=1;
174 #ifdef SIGUSR2
175 qsignal(SIGUSR2,verbon);
176 #endif
177 }
178
yell(a,b)179 void yell(a,b)const char*const a,*const b; /* log if VERBOSE=on */
180 { if(verbose)
181 nlog(a),logqnl(b);
182 }
183
184 static time_t oldtime;
185
newid(void)186 void newid P((void))
187 { thepid=getpid();oldtime=0;
188 }
189
zombiecollect(void)190 void zombiecollect P((void))
191 { while(waitpid((pid_t)-1,(int*)0,WNOHANG)>0); /* collect any zombies */
192 }
193
nlog(a)194 void nlog(a)const char*const a;
195 { time_t newtime;
196 static const char colnsp[]=": ";
197 elog(procmailn);elog(colnsp);
198 if(verbose&&!nextexit&&oldtime!=(newtime=time((time_t*)0))) /* don't call */
199 { charNUM(num,thepid); /* ctime from a sighandler */
200 elog("[");oldtime=newtime;ultstr(0,(unsigned long)thepid,num);elog(num);
201 elog("] ");elog(ctime(&oldtime));elog(procmailn);elog(colnsp);
202 }
203 elog(a);
204 }
205
logqnl(a)206 void logqnl(a)const char*const a;
207 { elog(oquote);elog(a);elog(cquote);
208 }
209
skipped(x)210 void skipped(x)const char*const x;
211 { if(*x)
212 nlog("Skipped"),logqnl(x);
213 }
214
nextrcfile(void)215 int nextrcfile P((void)) /* next rcfile specified on the command line */
216 { const char*p;int rval=2;
217 while(p= *gargv)
218 { gargv++;
219 if(!strchr(p,'='))
220 { if(strlen(p)>linebuf-1)
221 { nlog("Excessively long rcfile path skipped\n");
222 continue;
223 }
224 rcfile=p;
225 return rval;
226 }
227 rval=1; /* not the first argument encountered */
228 }
229 return 0;
230 }
231
tstrdup(a)232 char*tstrdup(a)const char*const a;
233 { int i;
234 i=strlen(a)+1;
235 return tmemmove(malloc(i),a,i);
236 }
237
cstr(a,b)238 char*cstr(a,b)char*const a;const char*const b; /* dynamic buffer management */
239 { if(a)
240 free(a);
241 return tstrdup(b);
242 }
243
onguard(void)244 void onguard P((void))
245 { lcking|=lck_DELAYSIG;
246 }
247
offguard(void)248 void offguard P((void))
249 { lcking&=~lck_DELAYSIG;
250 if(nextexit==1) /* make sure we are not inside Terminate() already */
251 elog(newline),Terminate();
252 }
253
sterminate(void)254 static void sterminate P((void))
255 { static const char*const msg[]={"memory","fork", /* crosscheck with */
256 "a file descriptor","a kernel-lock"}; /* lck_ defs in procmail.h */
257 ignoreterm();
258 if(pidchild>0) /* don't kill what is not ours, we might be root */
259 kill(pidchild,SIGTERM);
260 if(!nextexit)
261 { nextexit=1;nlog("Terminating prematurely");
262 if(!(lcking&lck_DELAYSIG))
263 { register unsigned i,j;
264 if(i=(lcking&~lck__NOMSG)>>1)
265 { elog(whilstwfor);
266 for(j=0;!((i>>=1)&1);j++);
267 elog(msg[j]);
268 }
269 elog(newline);Terminate();
270 }
271 }
272 }
273
274 int fakedelivery;
275
Terminate(void)276 void Terminate P((void))
277 { ignoreterm();
278 if(getpid()==thepid)
279 { const char*lstfolder;
280 if(retval!=EXIT_SUCCESS)
281 { lasttell= -1; /* mark it for sendcomsat */
282 lstfolder=fakedelivery?"**Lost**":
283 retval==EX_TEMPFAIL?"**Requeued**":"**Bounced**";
284 sendcomsat(lstfolder);
285 }
286 else
287 { lstfolder=tgetenv(lastfolder);
288 sendcomsat(0);
289 }
290 logabstract(lstfolder);
291 if(!nextexit) /* these are unsafe from sighandlers */
292 { shutdesc();
293 exectrap(traps);
294 fdunlock();
295 }
296 nextexit=2;unlock(&loclock);unlock(&globlock);
297 } /* flush the logfile & exit procmail */
298 elog(empty);
299 _exit(retvl2!=EXIT_SUCCESS?retvl2: /* unsuccessful child? */
300 fakedelivery==2?EXIT_SUCCESS: /* told to throw it away? */
301 retval); /* okay, use the real status */
302 }
303
suspend(void)304 void suspend P((void))
305 { ssleep((unsigned)suspendv);
306 }
307
srequeue(void)308 static void srequeue P((void))
309 { retval=EX_TEMPFAIL;sterminate();
310 }
311
slose(void)312 static void slose P((void))
313 { fakedelivery=2;sterminate();
314 }
315
sbounce(void)316 static void sbounce P((void))
317 { retval=EX_CANTCREAT;sterminate();
318 }
319
setupsigs(void)320 void setupsigs P((void))
321 { qsignal(SIGTERM,srequeue);qsignal(SIGINT,sbounce);
322 qsignal(SIGHUP,sbounce);qsignal(SIGQUIT,slose);
323 signal(SIGALRM,(void(*)())ftimeout);
324 }
325
squeeze(target)326 static void squeeze(target)char*target;
327 { int state;char*src;
328 for(state=0,src=target;;target++,src++)
329 { switch(*target= *src)
330 { case '\n':
331 if(state==1)
332 target-=2; /* throw out \ \n pairs */
333 state=2;
334 continue;
335 case '\\':state=1;
336 continue;
337 case ' ':case '\t':
338 if(state==2) /* skip leading */
339 { target--; /* whitespace */
340 continue;
341 }
342 default:state=0;
343 continue;
344 case '\0':;
345 }
346 break;
347 }
348 }
349
egrepin(expr,source,len,casesens)350 char*egrepin(expr,source,len,casesens)char*expr;const char*source;
351 const long len;int casesens;
352 { if(*expr) /* only do the search if the expression is nonempty */
353 { source=(const char*)bregexec((struct eps*)(expr=(char*)
354 bregcomp(expr,!casesens)),(const uchar*)source,(const uchar*)source,
355 len>0?(size_t)len:(size_t)0,!casesens);
356 free(expr);
357 }
358 return (char*)source;
359 }
360
enoughprivs(passinvk,euid,egid,uid,gid)361 int enoughprivs(passinvk,euid,egid,uid,gid)const auth_identity*const passinvk;
362 const uid_t euid,uid;const gid_t egid,gid;
363 { return euid==ROOT_uid||
364 passinvk&&auth_whatuid(passinvk)==uid||
365 euid==uid&&egid==gid;
366 }
367
newdynstring(adrp,chp)368 const char*newdynstring(adrp,chp)struct dynstring**const adrp;
369 const char*const chp;
370 { struct dynstring*curr;size_t len;
371 curr=malloc(ioffsetof(struct dynstring,ename[0])+(len=strlen(chp)+1));
372 tmemmove(curr->ename,chp,len);curr->enext= *adrp;*adrp=curr;
373 return curr->ename;
374 }
375
app_val_(sp,size)376 void*app_val_(sp,size)struct dyna_array*const sp;int size;
377 { if(sp->filled==sp->tspace) /* growth limit reached? */
378 { size_t len=(sp->tspace+=4)*size;
379 sp->vals=sp->vals?realloc(sp->vals,len):malloc(len); /* expand */
380 }
381 return &sp->vals[size*sp->filled++]; /* append to it */
382 }
383
384 /* lifted out of main() to reduce main()'s size */
conditions(flags,prevcond,lastsucc,lastcond,skipping,nrcond)385 int conditions(flags,prevcond,lastsucc,lastcond,skipping,nrcond)char flags[];
386 const int prevcond,lastsucc,lastcond,skipping;int nrcond;
387 { char*chp,*chp2,*startchar;double score;int scored,i,skippedempty;
388 long tobesent;static const char suppressed[]=" suppressed\n";
389 score=scored=0;
390 if(nrcond<0) /* assume appropriate default nr of conditions */
391 nrcond=!flags[ALSO_NEXT_RECIPE]&&!flags[ALSO_N_IF_SUCC]&&!flags[ELSE_DO]&&
392 !flags[ERROR_DO];
393 startchar=themail.p;tobesent=thebody-themail.p;
394 if(flags[BODY_GREP]) /* what needs to be egrepped? */
395 if(flags[HEAD_GREP])
396 tobesent=filled;
397 else
398 { startchar=thebody;tobesent=filled-tobesent;
399 goto noconcat;
400 }
401 if(!skipping)
402 concon(' ');
403 noconcat:
404 i=!skipping; /* init test value */
405 if(flags[ERROR_DO])
406 { i&=prevcond&&!lastsucc;
407 if(flags[ELSE_DO])
408 nlog(conflicting),elog("else-if-flag"),elog(suppressed),
409 flags[ELSE_DO]=0;
410 if(flags[ALSO_N_IF_SUCC])
411 nlog(conflicting),elog("also-if-succeeded-flag"),elog(suppressed),
412 flags[ALSO_N_IF_SUCC]=0;
413 }
414 if(flags[ELSE_DO])
415 i&=!prevcond;
416 if(flags[ALSO_N_IF_SUCC])
417 i&=lastcond&&lastsucc;
418 if(flags[ALSO_NEXT_RECIPE])
419 i=i&&lastcond;
420 for(skippedempty=0;;)
421 { skipspace();--nrcond;
422 if(!testB('*')) /* marks a condition, new syntax */
423 if(nrcond<0) /* keep counting, for backward compatibility */
424 { if(testB('#')) /* line starts with a comment? */
425 { skipline(); /* skip the line */
426 continue;
427 }
428 if(testB('\n')) /* skip empty lines */
429 { skippedempty=1; /* make a note of this fact */
430 continue;
431 }
432 if(skippedempty&&testB(':'))
433 { nlog("Missing action\n");i=2;
434 goto ret;
435 }
436 break; /* no more conditions, time for action! */
437 }
438 skipspace();
439 if(getlline(buf2,buf2+linebuf))
440 i=0; /* assume failure on overflow */
441 if(i) /* check out all conditions */
442 { int negate,scoreany;double weight,xponent,lscore;
443 char*lstartchar=startchar;long ltobesent=tobesent,sizecheck=filled;
444 for(chp=strchr(buf2,'\0');--chp>=buf2;)
445 { switch(*chp) /* strip off whitespace at the end */
446 { case ' ':case '\t':*chp='\0';
447 continue;
448 }
449 break;
450 }
451 negate=scoreany=0;lscore=score;
452 for(chp=buf2+1;;strcpy(buf2,buf))
453 copydone: { switch(*(sgetcp=buf2))
454 { case '0':case '1':case '2':case '3':case '4':case '5':case '6':
455 case '7':case '8':case '9':case '-':case '+':case '.':case ',':
456 { char*chp3;double w;
457 w=strtod(buf2,&chp3);chp2=chp3;
458 if(chp2>buf2&&*(chp2=skpspace(chp2))=='^')
459 { double x;
460 x=strtod(chp2+1,&chp3);
461 if(chp3>chp2+1)
462 { if(score>=MAX32)
463 goto skiptrue;
464 xponent=x;weight=w;scored=scoreany=1;
465 chp2=skpspace(chp3);
466 goto copyrest;
467 }
468 }
469 chp--;
470 goto normalregexp;
471 }
472 default:chp--; /* no special character, backup */
473 { if(alphanum(*(chp2=chp))==1)
474 { char*chp3;
475 while(alphanum(*++chp2));
476 if(!strncmp(chp3=skpspace(chp2),"??",2))
477 { *chp2='\0';lstartchar=themail.p;
478 if(!chp[1])
479 { ltobesent=thebody-themail.p;
480 switch(*chp)
481 { case 'B':lstartchar=thebody;
482 ltobesent=filled-ltobesent;
483 goto partition;
484 case 'H':
485 goto docon;
486 }
487 }
488 else if(!strcmp("HB",chp)||
489 !strcmp("BH",chp))
490 { ltobesent=filled;
491 docon: concon(' ');
492 goto partition;
493 }
494 ltobesent=strlen(lstartchar=(char*)tgetenv(chp));
495 partition: chp2=skpspace(chp3+2);chp++;sizecheck=ltobesent;
496 goto copyrest;
497 }
498 }
499 }
500 case '\\':
501 normalregexp: { int or_nocase; /* case-distinction override */
502 static const struct {const char*regkey,*regsubst;}
503 *regsp,regs[]=
504 { {FROMDkey,FROMDsubstitute},
505 {TO_key,TO_substitute},
506 {TOkey,TOsubstitute},
507 {FROMMkey,FROMMsubstitute},
508 {0,0}
509 };
510 squeeze(chp);or_nocase=0;
511 goto jinregs;
512 do /* find special keyword in regexp */
513 if((chp2=strstr(chp,regsp->regkey))&&
514 (chp2==buf2||chp2[-1]!='\\')) /* escaped? */
515 { size_t lregs,lregk; /* no, so */
516 lregk=strlen(regsp->regkey); /* insert it */
517 tmemmove(chp2+(lregs=strlen(regsp->regsubst)),
518 chp2+lregk,strlen(chp2)-lregk+1);
519 tmemmove(chp2,regsp->regsubst,lregs);
520 if(regsp==regs) /* daemon regexp? */
521 or_nocase=1; /* no case sensitivity! */
522 jinregs: regsp=regs; /* start over and look again */
523 }
524 else
525 regsp++; /* next keyword */
526 while(regsp->regkey);
527 ;{ int igncase;
528 igncase=or_nocase||!flags[DISTINGUISH_CASE];
529 if(scoreany)
530 { struct eps*re;
531 re=bregcomp(chp,igncase);chp=lstartchar;
532 if(negate)
533 { if(weight&&!bregexec(re,(const uchar*)chp,
534 (const uchar*)chp,(size_t)ltobesent,igncase))
535 score+=weight;
536 }
537 else
538 { double oweight=weight*weight;
539 while(weight!=0&&
540 MIN32<score&&
541 score<MAX32&&
542 ltobesent>=0&&
543 (chp2=bregexec(re,(const uchar*)lstartchar,
544 (const uchar*)chp,(size_t)ltobesent,
545 igncase)))
546 { score+=weight;weight*=xponent;
547 if(chp>=chp2) /* break off empty */
548 { if(0<xponent&&xponent<1)
549 score+=weight/(1-xponent);
550 else if(xponent>=1&&weight!=0)
551 score+=weight<0?MIN32:MAX32;
552 break; /* matches early */
553 }
554 ;{ volatile double nweight=weight*weight;
555 if(nweight<oweight&&oweight<1)
556 break;
557 oweight=nweight;
558 }
559 ltobesent-=chp2-chp;chp=chp2;
560 }
561 }
562 free(re);
563 }
564 else /* egrep for it */
565 i=!!egrepin(chp,lstartchar,ltobesent,!igncase)^negate;
566 }
567 break;
568 }
569 case '$':*buf2='"';squeeze(chp);
570 if(readparse(buf,sgetc,2,0)&&(i=0,1))
571 break;
572 strcpy(buf2,skpspace(buf));
573 goto copydone;
574 case '!':negate^=1;chp2=skpspace(chp);
575 copyrest: strcpy(buf,chp2);
576 continue;
577 case '?':pwait=2;metaparse(chp);inittmout(buf);ignwerr=1;
578 pipin(buf,lstartchar,ltobesent,0);
579 resettmout();
580 if(scoreany&&lexitcode>=0)
581 { int j=lexitcode;
582 if(negate)
583 while(--j>=0&&(score+=weight)<MAX32&&score>MIN32)
584 weight*=xponent;
585 else
586 score+=j?xponent:weight;
587 }
588 else if(!!lexitcode^negate)
589 i=0;
590 strcpy(buf2,buf);
591 break;
592 case '>':case '<':
593 { long pivot;
594 if(readparse(buf,sgetc,2,0)&&(i=0,1))
595 break;
596 ;{ char*chp3;
597 pivot=strtol(buf+1,&chp3,10);chp=chp3;
598 }
599 skipped(skpspace(chp));strcpy(buf2,buf);
600 if(scoreany)
601 { double f;
602 if((*buf=='<')^negate)
603 if(sizecheck)
604 f=(double)pivot/sizecheck;
605 else if(pivot>0)
606 goto plusinfty;
607 else
608 goto mininfty;
609 else if(pivot)
610 f=(double)sizecheck/pivot;
611 else
612 goto plusinfty;
613 score+=weight*tpow(f,xponent);
614 }
615 else if(!((*buf=='<'?sizecheck<pivot:sizecheck>pivot)^
616 negate))
617 i=0;
618 }
619 }
620 break;
621 }
622 if(score>MAX32) /* chop off at plus infinity */
623 plusinfty: score=MAX32;
624 if(score<=MIN32) /* chop off at minus infinity */
625 mininfty: score=MIN32,i=0;
626 if(verbose)
627 { if(scoreany) /* not entirely correct, but it will do */
628 { charNUM(num,long);
629 nlog("Score: ");ltstr(7,(long)(score-lscore),num);
630 elog(num);elog(" ");
631 ;{ long iscore=score;
632 ltstr(7,iscore,num);
633 if(!iscore&&score>0)
634 num[7-2]='+'; /* show +0 for (0,1) */
635 }
636 elog(num);
637 }
638 else
639 nlog(i?"M":"No m"),elog("atch on");
640 if(negate)
641 elog(" !");
642 logqnl(buf2);
643 }
644 skiptrue:;
645 }
646 }
647 if(!(lastscore=score)&&score>0) /* save it for $= */
648 lastscore=1; /* round up +0 to 1 */
649 if(scored&&i&&score<=0)
650 i=0; /* it was not a success */
651 ret:
652 return i;
653 }
654