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