1/************************************************************************
2 *	Routines that deal with understanding the folder types		*
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
10static /*const*/char rcsid[]=
11 "$Id: foldinfo.c,v 1.11 2001/08/04 07:07:42 guenther Exp $";
12#endif
13#include "procmail.h"
14#include "misc.h"
15#include "lastdirsep.h"
16#include "robust.h"
17#include "exopen.h"
18#include "goodies.h"
19#include "locking.h"
20#include "foldinfo.h"
21
22static const char
23 maildirtmp[]=MAILDIRtmp,maildircur[]=MAILDIRcur;
24const char
25 maildirnew[]=MAILDIRnew;
26int accspooldir;		     /* can we write to the spool directory? */
27
28/* determine the requested type, chopping off the type specifier and any
29   extra trailing slashes */
30static int folderparse P((void))
31{ char*chp;int type;
32  type=ft_FILE;chp=strchr(buf,'\0');
33  switch(chp-buf)
34   { case 2:
35	if(chp[-1]==*MCDIRSEP_)				     /* "//" or "x/" */
36	   chp--,type=ft_MAILDIR;
37     case 0:case 1:						/* "" or "x" */
38	goto ret;
39   }			    /* Okay, put chp right before the type specifier */
40  if(chp[-1]==chCURDIR&&chp[-2]==*MCDIRSEP_)			    /* foo/. */
41     chp-=2,type=ft_MH;
42  else if(chp[-1]==*MCDIRSEP_)					     /* foo/ */
43     chp--,type=ft_MAILDIR;
44  else							     /* no specifier */
45     goto ret;
46  while(chp-1>buf&&strchr(dirsep,chp[-1]))		    /* chop extra /s */
47     chp--;
48ret:
49  *chp='\0';
50  return type;
51}
52
53int rnmbogus(name,stbuf,i,dolog)const char*const name;	      /* move a file */
54 const struct stat*const stbuf;const int i,dolog;	   /* out of the way */
55{ static const char renbogus[]="Renamed bogus \"%s\" into \"%s\"",
56   renfbogus[]="Couldn't rename bogus \"%s\" into \"%s\"",
57   bogusprefix[]=BOGUSprefix;char*p;
58  p=strchr(strcpy(strcpy(buf2+i,bogusprefix)+STRLEN(bogusprefix),
59   getenv(lgname)),'\0');
60  *p++='.';ultoan((unsigned long)stbuf->st_ino,p);	  /* i-node numbered */
61  if(dolog)
62   { nlog("Renaming bogus mailbox \"");elog(name);elog("\" info");
63     logqnl(buf2);
64   }
65  if(rename(name,buf2))			   /* try and move it out of the way */
66   { syslog(LOG_ALERT,renfbogus,name,buf2);		 /* danger!  danger! */
67     return 1;
68   }
69  syslog(LOG_CRIT,renbogus,name,buf2);
70  return 0;
71}
72
73/* return the named object's mode, making it a directory if it doesn't exist
74 * and renaming it out of the way if it's not _just_right_ and we're being
75 * paranoid */
76static mode_t trymkdir(dir,paranoid,i)const char*const dir;
77 const int paranoid,i;
78{ struct stat stbuf;int tries=3-1;	     /* minus one for post-decrement */
79  do
80   { if(!(paranoid?lstat(dir,&stbuf):stat(dir,&stbuf)))	   /* does it exist? */
81      { if(!paranoid||		     /* is it okay?  If we're trusting it is */
82	   (S_ISDIR(stbuf.st_mode)&&	      /* else it must be a directory */
83	    (stbuf.st_uid==uid||	       /* and have the correct owner */
84	     !stbuf.st_uid&&!chown(dir,uid,sgid))))  /* or be safely fixable */
85	   return stbuf.st_mode;				   /* bingo! */
86	else if(rnmbogus(dir,&stbuf,i,1))  /* try and move it out of the way */
87	   break;						/* couldn't! */
88      }
89     else if(errno!=ENOENT)	    /* something more fundamental went wrong */
90	break;
91     else if(!mkdir(dir,NORMdirperm))	  /* it's not there, can we make it? */
92      { if(!paranoid)	      /* do we need to double check the permissions? */
93	   return S_IFDIR|NORMdirperm&~cumask;			     /* nope */
94	tries++;		/* the mkdir succeeded, so take another shot */
95      }
96   }while(tries-->0);
97  return 0;
98}
99
100static int mkmaildir(buffer,chp,paranoid)char*const buffer,*const chp;
101 const int paranoid;
102{ mode_t mode;int i;
103  if(paranoid)
104     memcpy(buf2,buffer,i=chp-buffer+1),buf2[i-1]= *MCDIRSEP_,buf2[i]='\0';
105  return
106   (strcpy(chp,maildirnew),mode=trymkdir(buffer,paranoid,i),S_ISDIR(mode))&&
107   (strcpy(chp,maildircur),mode=trymkdir(buffer,paranoid,i),S_ISDIR(mode))&&
108   (strcpy(chp,maildirtmp),mode=trymkdir(buffer,paranoid,i),S_ISDIR(mode));
109}					      /* leave tmp in buf on success */
110
111int foldertype(type,forcedir,modep,paranoid)int type,forcedir;
112 mode_t*const modep;struct stat*const paranoid;
113{ struct stat stbuf;mode_t mode;int i;char*chp;
114  if(!type)
115     type=folderparse();
116  switch(type)
117   { case ft_MAILDIR:i=MAILDIRLEN;break;
118     case ft_MH:i=0;break;
119     case ft_FILE:
120	i=0;					    /* resolve the ambiguity */
121	if(!forcedir)
122	 { if(paranoid?lstat(buf,&stbuf):stat(buf,&stbuf))
123	    { if(paranoid)
124	       { type=ft_NOTYET;
125		 goto ret;
126	       }
127	      goto newfile;
128	    }
129	   else if(mode=stbuf.st_mode,!S_ISDIR(mode))
130	      goto file;
131	 }
132	type=ft_DIR;
133	break;
134     default:					     /* "this cannot happen" */
135	nlog("Internal error: improper type (");
136	ltstr(0,type,buf2);elog(buf2);
137	elog(") passed to foldertype for folder ");logqnl(buf);
138	Terminate();
139   }
140  chp=strchr(buf,'\0');
141  if((chp-buf)+UNIQnamelen+1+i>linebuf)
142   { type=ft_TOOLONG;
143     goto ret;
144   }
145  if(type==ft_DIR&&!forcedir)		  /* we've already checked this case */
146     goto done;
147  if(paranoid)
148     memcpy(buf2,buf,i=lastdirsep(buf)-buf),buf2[i]='\0';
149  mode=trymkdir(buf,paranoid!=0,i);
150  if(!S_ISDIR(mode)||(type==ft_MAILDIR&&
151   (forcedir=1,!mkmaildir(buf,chp,paranoid!=0))))
152   { nlog("Unable to treat as directory");logqnl(buf);	 /* we can't make it */
153     if(forcedir)				     /* fallback or give up? */
154      { *chp='\0';skipped(buf);type=ft_CANTCREATE;
155	goto ret;
156      }
157     if(!mode)
158newfile:mode=S_IFREG|NORMperm&~cumask;
159file:type=ft_FILE;
160   }
161done:
162  if(paranoid)
163     *paranoid=stbuf;
164  else
165     *modep=mode;
166ret:
167  return type;
168}
169
170			     /* lifted out of main() to reduce main()'s size */
171int screenmailbox(chp,egid,Deliverymode)
172 char*chp;const gid_t egid;const int Deliverymode;
173{ char ch;struct stat stbuf;int basetype,type;
174  /*
175   *	  do we need sgidness to access the mail-spool directory/files?
176   */
177  accspooldir=3;	   /* assume we can write to the spool directory and */
178  sgid=gid;	      /* that we don't need to setgid() to create a lockfile */
179  strcpy(buf,chp);
180  basetype=folderparse();			       /* strip off the type */
181  if(buf[0]=='\0')				/* don't even bother with "" */
182     return 0;
183  ch= *(chp=lastdirsep(buf));
184  if(chp>buf)
185     *chp='\0';					   /* strip off the filename */
186  if(!stat(buf,&stbuf))
187   { unsigned wwsdir;
188     accspooldir=(wwsdir=			/* world writable spool dir? */
189	    ((S_IWGRP|S_IXGRP|S_IWOTH|S_IXOTH)&stbuf.st_mode)==
190	     (S_IWGRP|S_IXGRP|S_IWOTH|S_IXOTH)
191	  <<1|						 /* note it in bit 1 */
192	  uid==stbuf.st_uid);	   /* we own the spool dir, note it in bit 0 */
193     if((CAN_toggle_sgid||accspooldir)&&privileged)
194	privileged=priv_DONTNEED;	     /* we don't need root to setgid */
195     if(uid!=stbuf.st_uid&&		 /* we don't own the spool directory */
196	(stbuf.st_mode&S_ISGID||!wwsdir))	  /* it's not world writable */
197      { if(stbuf.st_gid==egid)			 /* but we have setgid privs */
198	   doumask(GROUPW_UMASK);		   /* make it group-writable */
199	goto keepgid;
200      }
201     else if(stbuf.st_mode&S_ISGID)
202keepgid:			   /* keep the gid from the parent directory */
203	if((sgid=stbuf.st_gid)!=egid&&		  /* we were started nosgid, */
204	 setgid(sgid))				     /* but we might need it */
205	   checkroot('g',(unsigned long)sgid);
206   }
207  else				/* panic, mail-spool directory not available */
208   { setids();mkdir(buf,NORMdirperm);	     /* try creating the last member */
209   }
210  *chp=ch;
211 /*
212  *	  check if the default-mailbox-lockfile is owned by the
213  *	  recipient, if not, mark it for further investigation, it
214  *	  might need to be removed
215  */
216  chp=strchr(buf,'\0')-1;
217  for(;;)				     /* what type of folder is this? */
218   { type=foldertype(basetype,0,0,&stbuf);
219     if(type==ft_NOTYET)
220      { if(errno!=EACCES||(setids(),lstat(buf,&stbuf)))
221	   goto nobox;
222      }
223     else if(!ft_checkcloser(type))
224      { setids();
225	if(type<0)
226	   goto fishy;
227	goto nl;					   /* no lock needed */
228      }
229    /*
230     *	  check if the original/default mailbox of the recipient
231     *	  exists, if it does, perform some security checks on it
232     *	  (check if it's a regular file, check if it's owned by
233     *	  the recipient), if something is wrong try and move the
234     *	  bogus mailbox out of the way, create the
235     *	  original/default mailbox file, and chown it to
236     *	  the recipient
237     */
238     ;{ int checkiter=1;
239	for(;;)
240	 { if(stbuf.st_uid!=uid||		      /* recipient not owner */
241	      !(stbuf.st_mode&S_IWUSR)||	     /* recipient can write? */
242	      S_ISLNK(stbuf.st_mode)||			/* no symbolic links */
243	      (S_ISDIR(stbuf.st_mode)?	      /* directories, yes, hardlinks */
244		!(stbuf.st_mode&S_IXUSR):stbuf.st_nlink!=1))	       /* no */
245	     /*
246	      * If another procmail is about to create the new
247	      * mailbox, and has just made the link, st_nlink==2
248	      */
249	      if(checkiter--)		    /* maybe it was a race condition */
250		 suspend();		 /* close eyes, and hope it improves */
251	      else			/* can't deliver to this contraption */
252	       { int i=lastdirsep(buf)-buf;
253		 memcpy(buf2,buf,i);buf2[i]='\0';
254		 if(rnmbogus(buf,&stbuf,i,1))
255		    goto fishy;
256		 goto nobox;
257	       }
258	   else
259	      break;
260	   if(lstat(buf,&stbuf))
261	      goto nobox;
262	 }					/* SysV type autoforwarding? */
263	if(Deliverymode&&(stbuf.st_mode&S_ISUID||
264	 !S_ISDIR(stbuf.st_mode)&&stbuf.st_mode&S_ISGID))
265	 { nlog("Autoforwarding mailbox found\n");
266	   exit(EX_NOUSER);
267	 }
268	else
269	 { if(!(stbuf.st_mode&OVERRIDE_MASK)&&
270	      stbuf.st_mode&cumask&
271	       (accspooldir?~(mode_t)0:~(S_IRGRP|S_IWGRP)))	/* hold back */
272	    { static const char enfperm[]=
273	       "Enforcing stricter permissions on";
274	      nlog(enfperm);logqnl(buf);
275	      syslog(LOG_NOTICE,slogstr,enfperm,buf);setids();
276	      chmod(buf,stbuf.st_mode&=~cumask);
277	    }
278	   break;				  /* everything is just fine */
279	 }
280      }
281nobox:
282     if(!(accspooldir&1))	     /* recipient does not own the spool dir */
283      { if(!xcreat(buf,NORMperm,(time_t*)0,doCHOWN|doCHECK))	   /* create */
284	   break;		   /* mailbox... yes we could, fine, proceed */
285	if(!lstat(buf,&stbuf))			     /* anything in the way? */
286	   continue;			       /* check if it could be valid */
287      }
288     setids();						   /* try some magic */
289     if(!xcreat(buf,NORMperm,(time_t*)0,doCHECK))		/* try again */
290	break;
291     if(lstat(buf,&stbuf))			      /* nothing in the way? */
292fishy:
293      { nlog("Couldn't create");logqnl(buf);
294	return 0;
295      }
296   }
297  if(!S_ISDIR(stbuf.st_mode))
298   { int isgrpwrite=stbuf.st_mode&S_IWGRP;
299     strcpy(chp=strchr(buf,'\0'),lockext);
300     defdeflock=tstrdup(buf);
301     if(!isgrpwrite&&!lstat(defdeflock,&stbuf)&&stbuf.st_uid!=uid&&
302      stbuf.st_uid!=ROOT_uid)
303      { int i=lastdirsep(buf)-buf;
304	memcpy(buf2,buf,i);buf2[i]='\0';      /* try & rename bogus lockfile */
305	rnmbogus(defdeflock,&stbuf,i,0);		   /* out of the way */
306      }
307     *chp='\0';
308   }
309  else
310nl:  defdeflock=empty;					   /* no lock needed */
311  return 1;
312}
313