1/************************************************************************ 2 * Environment and variable handling routines used by procmail * 3 * * 4 * Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands * 5 * Copyright (c) 2000-2001, Philip Guenther, The United States * 6 * of America * 7 * #include "../README" * 8 ************************************************************************/ 9#ifdef RCS 10static /*const*/char rcsid[]= 11 "$Id: variables.c,v 1.22 2001/08/27 08:53:15 guenther Exp $"; 12#endif 13#include "procmail.h" 14#include "acommon.h" /* for hostname() */ 15#include "common.h" /* for ultstr() */ 16#include "cstdio.h" 17#include "robust.h" 18#include "shell.h" 19#include "authenticate.h" 20#include "goodies.h" 21#include "misc.h" 22#include "locking.h" /* for lockit() */ 23#include "comsat.h" 24#include "sublib.h" 25#include "variables.h" 26 27struct varval strenvvar[]={{"LOCKSLEEP",DEFlocksleep}, 28 {"LOCKTIMEOUT",DEFlocktimeout},{"SUSPEND",DEFsuspend}, 29 {"NORESRETRY",DEFnoresretry},{"TIMEOUT",DEFtimeout},{"VERBOSE",DEFverbose}, 30 {"LOGABSTRACT",DEFlogabstract}}; 31struct varstr strenstr[]={{"SHELLMETAS",DEFshellmetas},{"LOCKEXT",DEFlockext}, 32 {"MSGPREFIX",DEFmsgprefix},{"TRAP",empty}, 33 {"SHELLFLAGS",DEFshellflags},{"DEFAULT",DEFdefault},{"SENDMAIL",DEFsendmail}, 34 {"SENDMAILFLAGS",DEFflagsendmail},{"PROCMAIL_VERSION",PM_VERSION}}; 35 36#define MAXvarvals maxindex(strenvvar) 37#define MAXvarstrs maxindex(strenstr) 38 39const char lastfolder[]="LASTFOLDER",maildir[]="MAILDIR",scomsat[]="COMSAT", 40 offvalue[]="no"; 41int didchd; 42long Stdfilled; 43char*Stdout; 44 45static void asenvtext P((const char*const chp)); /* needed by retStdout */ 46 47static const char slinebuf[]="LINEBUF",pmoverflow[]="PROCMAIL_OVERFLOW=yes", 48 exitcode[]="EXITCODE"; 49static int setxit; 50 51static struct dynstring*myenv; 52static char**lastenv; 53 /* smart putenv, the way it was supposed to be */ 54const char*sputenv(a)const char*const a; 55{ static int alloced;size_t eq,i;int remove;const char*split;char**preenv; 56 struct dynstring*curr,**last; 57 yell("Assigning",a);remove=0; 58 if(!(split=strchr(a,'='))) /* assignment or removal? */ 59 remove=1,split=strchr(a,'\0'); 60 eq=split-a; /* is it */ 61 for(curr= *(last= &myenv);curr;curr= *(last= &curr->enext)) /* one I made */ 62 if(!strncmp(a,curr->ename,eq)&&((char*)curr->ename)[eq]=='=') 63 { split=curr->ename;*last=curr->enext;free(curr); /* earlier? */ 64 for(preenv=environ;*preenv!=split;preenv++); 65 goto wipenv; 66 } 67 for(preenv=environ;*preenv;preenv++) /* is it in the standard */ 68 if(!strncmp(a,*preenv,eq)&&(*preenv)[eq]=='=') /* environment? */ 69wipenv: 70 { while(*preenv=preenv[1]) /* wipe this entry out of the environment */ 71 preenv++; 72 break; 73 } 74 i=(preenv-environ+2)*sizeof*environ; 75 if(alloced) /* have we ever alloced the environ array before? */ 76 environ=realloc(environ,i); 77 else 78 alloced=1,environ=tmemmove(malloc(i),environ,i-sizeof*environ); 79 if(!remove) /* if not remove, then add it to both environments */ 80 { for(preenv=environ;*preenv;preenv++); 81 preenv[1]=0;*(lastenv=preenv)=(char*)(split=newdynstring(&myenv,a)); 82 return split+eq+1; 83 } 84 return empty; 85} 86 /* between calling primeStdout() and retStdout() *no* environment */ 87void primeStdout(varname)const char*const varname; /* changes are allowed! */ 88{ if(!Stdout) 89 sputenv(varname); 90 Stdout=(char*)myenv; 91 Stdfilled=ioffsetof(struct dynstring,ename[0])+strlen(varname); 92} 93 94void retStdout(newmyenv,fail,unset) /* see note on primeStdout() */ 95 char*const newmyenv;const int fail,unset; 96{ char*var,*p; 97 if(fail&&unset) /* on second thought... */ 98 { myenv=((struct dynstring*)newmyenv)->enext; /* pull it back out */ 99 free(newmyenv);*lastenv=Stdout=0; 100 return; 101 } 102 else if(!fail&&newmyenv[Stdfilled-1]=='\n') /* strip one trailing newline */ 103 Stdfilled--; 104 retbStdout(newmyenv); 105 var=newmyenv+ioffsetof(struct dynstring,ename[0]); /* setup to copy */ 106 p=strchr(var,'='); /* the variable name into buf */ 107 tmemmove(buf,var,p-var); /* so that we can check */ 108 buf[p-var]='\0'; /* for magic */ 109 if(fail) 110 asenvtext(p+1); /* we always have to update the pointers for these */ 111 else 112 asenv(p+1); /* invoke any magic */ 113} 114 115void retbStdout(newmyenv)char*const newmyenv; /* see note on primeStdout() */ 116{ newmyenv[Stdfilled]='\0';*lastenv=(myenv=(struct dynstring*)newmyenv)->ename; 117 Stdout=0; 118} 119 120 /* Append a space and then `value' to the last variable set */ 121void appendlastvar(value)const char*const value; 122{ size_t len;char*p; 123 Stdout=(char*)value;primeStdout(empty); 124 len=Stdfilled+strlen(Stdout+Stdfilled); /* Skip over the header */ 125 p=realloc(Stdout,(Stdfilled=len+1+strlen(value))+1); 126 p[len]=' ';strcpy(p+len+1,buf);retbStdout(p); /* WARNING: no magic here! */ 127} 128 129const char*eputenv(src,dst)const char*const src;char*const dst; 130{ sgetcp=src; 131 return readparse(dst,sgetc,2,0)?0:sputenv(buf); 132} 133 134void setdef(name,value)const char*const name,*const value; 135{ char*p; 136 strcpy(buf,name); /* insert the variable name */ 137 p=strchr(buf,'\0'); /* (find the end) */ 138 *p++='='; /* then the = */ 139 eputenv(value,p); /* expand the value and call sputenv */ 140} 141 142const char*tgetenv(a)const char*const a; 143{ const char*b; 144 return (b=getenv(a))?b:empty; 145} 146 147void setoverflow P((void)) 148{ sputenv(pmoverflow); 149} 150 151void cleanupenv(preserve)int preserve; 152{ static const char*const keepenv[]=KEEPENV,*const ld_[]=LDENV; 153 const char**emax=(const char**)environ,**ep,*const*pp; 154 register const char*p; 155 size_t len; 156 if(!preserve) /* drop the environment */ 157 { for(pp=keepenv;*pp;pp++) /* preserve a happy few */ 158 { len=strlen(*pp); 159 for(ep=emax;p= *ep;ep++) /* scan for this keeper */ 160 if(!strncmp(*pp,p,len)&&(p[len]=='='||p[len-1]=='_')) 161 { *ep= *emax; /* it's fine, swap 'em */ 162 *emax++=p; 163 if(p[len-1]!='_') /* if this wasn't a wildcard match */ 164 break; /* then go on to next keepenv entry */ 165 } 166 } 167 *emax=0; /* drop the rest */ 168 } 169 else 170 { while(*emax) /* find the end of the environment */ 171 emax++; 172 } 173 ep=(const char**)environ; 174 while(*ep) /* check for evil entries */ 175 { p=strchr(*ep,'='); 176 if(!p) /* malformed (no '=')? */ 177drop: { *ep= *--emax;*emax=0; /* copy from the end */ 178 continue; /* check the swapped entry */ 179 } 180 len=p-*ep+1; /* mark how long the actual name is */ 181 for(pp=(const char*const*)environ;pp!=ep;pp++) /* duplicate entry? */ 182 if(!strncmp(*ep,*pp,len)) 183 goto drop; 184 for(pp=ld_;p= *pp;pp++) /* does it start with LD_ or similar? */ 185 if(!strncmp(*ep,p,strlen(p))) 186 goto drop; 187 ep++; 188 } 189} 190 191void initdefenv(pass,fallback,do_presets)auth_identity*pass; 192 const char*fallback;int do_presets; 193{ const char*p; 194 if(pass) 195 { p=auth_username(pass); 196 if(!p||!*p) 197 p=fallback; 198 setdef(lgname,p); 199 p=auth_shell(pass); 200 if(!p||!*p) 201 p=binsh; 202 setdef(shell,p); 203 setdef(home,auth_homedir(pass));setdef(orgmail,auth_mailboxname(pass)); 204 } 205 else 206 { setdef(lgname,fallback);setdef(shell,binsh); 207 setdef(home,ROOT_DIR);setdef(orgmail,DEAD_LETTER); 208 } 209 setlgcs(tgetenv(lgname)); /* make sure sendcomsat has a copy */ 210 if(do_presets) 211 { static const char*const prestenv[]=PRESTENV; 212 const char*const*pp; 213 int i=MAXvarstrs; 214 do /* initialise all non-empty string variables into the environment */ 215 if(*strenstr[i].sval) 216 setdef(strenstr[i].sname,strenstr[i].sval); 217 while(i--); 218 setdef(host,hostname()); /* the other standard presets */ 219 sputenv(lastfolder); 220 sputenv(exitcode); 221 eputenv(defpath,buf); 222 for(pp=prestenv;*pp;pp++) /* non-standard presets */ 223 eputenv(*pp,buf); 224 } 225} 226 227int alphanum(c)const unsigned c; 228{ switch(c) 229 { case '0':case '1':case '2':case '3':case '4': 230 case '5':case '6':case '7':case '8':case '9': 231 return 2; 232 case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':case 'G':case 'H': 233 case 'I':case 'J':case 'K':case 'L':case 'M':case 'N':case 'O':case 'P': 234 case 'Q':case 'R':case 'S':case 'T':case 'U':case 'V':case 'W':case 'X': 235 case 'Y':case 'Z': 236 case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'g':case 'h': 237 case 'i':case 'j':case 'k':case 'l':case 'm':case 'n':case 'o':case 'p': 238 case 'q':case 'r':case 's':case 't':case 'u':case 'v':case 'w':case 'x': 239 case 'y':case 'z': 240 case '_': 241 return 1; 242 default: 243 return 0; 244 } 245} 246 247void setmaildir(newdir)const char*const newdir; /* destroys buf2 */ 248{ char*chp; 249 didchd=1;*(chp=strcpy(buf2,maildir)+STRLEN(maildir))='='; 250 strcpy(++chp,newdir);sputenv(buf2); 251} 252 253void setlastfolder(folder)const char*const folder; 254{ char*chp;size_t len; 255 setlfcs(folder); 256 len=STRLEN(lastfolder)+2+strlen(folder); 257 strcpy(chp=malloc(len),lastfolder); 258 strlcat(chp,"=",len); 259 strlcat(chp,folder,len); 260 sputenv(chp);free(chp); 261} 262 263int setexitcode(trapisset)int trapisset; 264{ char*p;int forceret; 265 if(setxit&&(p=getenv(exitcode))) /* user specified exitcode? */ 266 { if((forceret=renvint(-2L,p))>=0) /* yes, is it positive? */ 267 retval=forceret; /* then override it */ 268 } 269 else 270 { forceret= -1; 271 if(trapisset) /* no EXITCODE set, TRAP found, provide one */ 272 { p=buf2+STRLEN(exitcode); 273 strcpy(buf2,exitcode);*p='='; 274 ultstr(0,(unsigned long)retval,p+1);sputenv(buf2); 275 } 276 } 277 return forceret; 278} 279 280char*gobenv(chp,end)char*chp,*end; 281{ int found,i; 282 found=0;end--; 283 if(alphanum(i=getb())==1) 284 for(found=1;*chp++=i,chp<end&&alphanum(i=getb());); 285 *chp='\0';ungetb(i); 286 if(chp==end) /* overflow */ 287 { nlog(exceededlb);setoverflow(); 288 return end+1; 289 } 290 switch(i) 291 { case ' ':case '\t':case '\n':case '=': 292 if(found) 293 return chp; 294 } 295 return 0; 296} 297 298int asenvcpy(src)char*src; 299{ const char*chp; 300 if(chp=strchr(src,'=')) /* is it an assignment? */ 301 /* 302 * really change the uid now, since it would not be safe to 303 * evaluate the extra command line arguments otherwise 304 */ 305 { size_t len=chp++-src+1; /* variable name + '=' */ 306 erestrict=1;setids(); /* always do this */ 307 if(len>linebuf-XTRAlinebuf-1) /* too long of name? */ 308 { setoverflow(); 309 nlog("Assignment to variable with excessively long name skipped\n"); 310 } 311 else 312 { memcpy(buf,src,len); 313 src=buf+len; 314 if(chp=eputenv(chp,src)) 315 { src[-1]='\0'; 316 asenv(chp); 317 } 318 } 319 return 1; 320 } 321 return 0; 322} 323 324void allocbuffers(lineb,setenv)size_t lineb;int setenv; 325{ if(buf) 326 { char*p=buf; 327 buf=0; /* make sure buf is either valid or NULL */ 328 free(buf2); 329 free(p); 330 } 331 buf=malloc(lineb+XTRAlinebuf);buf2=malloc(lineb+XTRAlinebuf); 332 if(setenv) 333 { char*chp; 334 *(chp=strcpy(buf,slinebuf)+STRLEN(slinebuf))='='; 335 ultstr(0,lineb,chp+1); 336 sputenv(buf); 337 } 338} 339 340static void asenvtext(chp)const char*const chp; 341{ int i=MAXvarstrs; 342 do /* several text assignments */ 343 if(!strcmp(buf,strenstr[i].sname)) 344 strenstr[i].sval=chp; 345 while(i--); 346} 347 348void asenv(chp)const char*const chp; 349{ static const char logfile[]="LOGFILE",Log[]="LOG",sdelivered[]="DELIVERED", 350 includerc[]="INCLUDERC",eumask[]="UMASK",dropprivs[]="DROPPRIVS", 351 shift[]="SHIFT",switchrc[]="SWITCHRC"; 352 if(!strcmp(buf,slinebuf)) 353 { long lineb; /* signed to catch negative numbers */ 354 if((lineb=renvint(0L,chp))<MINlinebuf) 355 lineb=MINlinebuf; /* check minimum size */ 356 allocbuffers(linebuf=lineb,0); 357 } 358 else if(!strcmp(buf,maildir)) 359 { if(chdir(chp)) 360 { chderr(chp); 361 setmaildir(curdir); 362 } 363 else 364 didchd=1; 365 } 366 else if(!strcmp(buf,logfile)) 367 opnlog(chp); 368 else if(!strcmp(buf,Log)) 369 elog(chp); 370 else if(!strcmp(buf,exitcode)) 371 setxit=1; 372 else if(!strcmp(buf,lgname)) 373 setlgcs(chp); 374 else if(!strcmp(buf,lastfolder)) 375 setlfcs(chp); 376 else if(!strcmp(buf,scomsat)) 377 { if(!setcomsat(chp)) 378 setdef(scomsat,offvalue); /* set it to "no" on failure */ 379 } 380 else if(!strcmp(buf,shift)) 381 { int i; 382 if((i=renvint(0L,chp))>0) 383 { if(i>crestarg) 384 i=crestarg; 385 crestarg-=i;restargv+=i; /* shift away arguments */ 386 } 387 } 388 else if(!strcmp(buf,dropprivs)) /* drop privileges */ 389 { if(renvint(0L,chp)) 390 { if(verbose) 391 nlog("Assuming identity of the recipient, VERBOSE=off\n"); 392 setids(); 393 } 394 } 395 else if(!strcmp(buf,sdelivered)) /* fake delivery */ 396 { if(renvint(0L,chp)) /* is it really? */ 397 { onguard(); 398 if((thepid=sfork())>0) 399 _exit(retvl2); /* parent: do not pass go */ 400 if(!forkerr(thepid,procmailn)) 401 fakedelivery=1; 402 newid();offguard(); 403 } 404 } 405 else if(!strcmp(buf,lockfile)) 406 { if(!lockit(tstrdup((char*)chp),&globlock)) 407 sputenv(lockfile); /* unset it on failure */ 408 } 409 else if(!strcmp(buf,eumask)) 410 doumask((mode_t)strtol(chp,(char**)0,8)); 411 else if(!strcmp(buf,includerc)) 412 { if(rc>=0) /* INCLUDERC and SWITCHRC only work */ 413 pushrc(chp); /* inside rcfiles */ 414 } /* and not on the command line */ 415 else if(!strcmp(buf,switchrc)) 416 { if(rc>=0) 417 changerc(chp); 418 } 419 else if(!strcmp(buf,host)) 420 { const char*name; 421 if(strcmp(chp,name=hostname())) 422 { yell("HOST mismatched",name); 423 if(rc<0) /* if no rcfile opened yet */ 424 retval=EXIT_SUCCESS,Terminate(); /* exit gracefully as well */ 425 closerc(); 426 } 427 } 428 else 429 { int i=MAXvarvals; 430 do /* several numeric assignments */ 431 if(!strcmp(buf,strenvvar[i].name)) 432 strenvvar[i].val=renvint(strenvvar[i].val,chp); 433 while(i--); 434 asenvtext(chp); /* delegate the text assignments */ 435 } 436} 437 438long renvint(i,env)const long i;const char*const env; 439{ const char*p;long t; 440 t=strtol(env,(char**)&p,10); /* parse like a decimal nr */ 441 if(p==env) 442 for(;;p++) 443 { switch(*p) 444 { case ' ':case '\t':case '\n':case '\v':case '\f':case '\r': 445 continue; /* skip leading whitespace */ 446 case 'o':case 'O': 447 if(!strncasecmp(p+1,"n",1)) 448 case 'y':case 'Y':case 't':case 'T':case 'e':case 'E': 449 t=1; 450 else if(!strncasecmp(p+1,"ff",2)) 451 case 'n':case 'N':case 'f':case 'F':case 'd':case 'D': 452 t=0; 453 else 454 default: 455 t=i; 456 break; 457 case 'a':case 'A':t=2; 458 break; 459 } 460 break; 461 } 462 return t; 463} 464