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