1 /************************************************************************
2  *	lockfile - The conditional semaphore-file creator		*
3  *									*
4  *	It has been designed to be able to be run sgid mail or		*
5  *	any gid you see fit (in case your mail spool area is *not*	*
6  *	world writable, but group writable), without creating		*
7  *	security holes.							*
8  *									*
9  *	Seems to be relatively bug free.				*
10  *									*
11  *	Copyright (c) 1990-1999, S.R. van den Berg, The Netherlands	*
12  *	Copyright (c) 1999-2001, Philip Guenther, The United States	*
13  *							of America	*
14  *	#include "../README"						*
15  ************************************************************************/
16 #ifdef RCS
17 static /*const*/char rcsid[]=
18  "$Id: lockfile.c,v 1.49 2001/08/04 07:12:16 guenther Exp $";
19 #endif
20 static /*const*/char rcsdate[]="$Date: 2001/08/04 07:12:16 $";
21 #include "includes.h"
22 #include "sublib.h"
23 #include "exopen.h"
24 #include "mcommon.h"
25 #include "authenticate.h"
26 #include "lastdirsep.h"
27 #include "../patchlevel.h"
28 
29 static volatile int exitflag;
30 pid_t thepid;
31 uid_t uid;
32 gid_t sgid;
33 const char dirsep[]=DIRSEP;
34 static const char lockext[]=DEFlockext,
35  nameprefix[]="lockfile: ",lgname[]="LOGNAME";
36 
failure(void)37 static void failure P((void))				      /* signal trap */
38 { exitflag=2;					       /* merely sets a flag */
39 }
40 				    /* see locking.c for comment on xcreat() */
xcreat(name,tim)41 static int xcreat(name,tim)const char*const name;time_t*const tim;
42 { char*p;int j= -1;size_t i;struct stat stbuf;
43   i=lastdirsep(name)-name;
44   if(!(p=malloc(i+UNIQnamelen)))
45      return exitflag=1;
46   memcpy(p,name,i);
47   if(unique(p,p+i,i+UNIQnamelen,LOCKperm,0,doCHECK|doLOCK))
48      stat(p,&stbuf),*tim=stbuf.st_mtime,j=myrename(p,name);
49   free(p);
50   return j;
51 }
52 
elog(a)53 void elog(a)const char*const a;
54 { write(STDERR,a,strlen(a));
55 }
56 
nlog(a)57 void nlog(a)const char*const a;
58 { elog(nameprefix);elog(a);  /* decent error messages should start with this */
59 }
60 
61 static PROGID;
62 
main(argc,argv)63 int main(argc,argv)int argc;const char*const argv[];
64 { const char*const*p;char*cp;uid_t uid;
65   int sleepsec,retries,invert,force,suspend,retval=EXIT_SUCCESS,virgin=1;
66   static const char usage[]="Usage: lockfile -v | -nnn | -r nnn | -l nnn \
67 | -s nnn | -! | -ml | -mu | file ...\n";
68   if(argc<=1)			       /* sanity check, any argument at all? */
69      goto usg;
70   sleepsec=DEFlocksleep;retries= -1;suspend=DEFsuspend;thepid=getpid();force=0;
71   uid=getuid();signal(SIGPIPE,SIG_IGN);
72   if(setuid(uid)||geteuid()!=uid)		  /* resist setuid operation */
73 sp:{ nlog("Unable to give up special permissions");
74      return EX_OSERR;
75    }
76 again:
77   invert=(char*)progid-(char*)progid;qsignal(SIGHUP,failure);
78   qsignal(SIGINT,failure);qsignal(SIGQUIT,failure);qsignal(SIGTERM,failure);
79   for(p=argv;--argc>0;)
80      if(*(cp=(char*)*++p)=='-')
81 	for(cp++;;)
82 	 { char*cp2=cp;int i;
83 	   switch(*cp++)
84 	    { case '!':invert^=1;		      /* invert the exitcode */
85 		 continue;
86 	      case 'r':case 'l':case 's':
87 		 if(!*cp&&(cp=(char*)*++p,!--argc)) /* concatenated/seperate */
88 		  { p--;
89 		    goto eusg;
90 		  }
91 		 i=strtol(cp,&cp,10);
92 		 switch(*cp2)
93 		  { case 'r':retries=i;
94 		       goto checkrdec;
95 		    case 'l':force=i;
96 		       goto checkrdec;
97 		    default:
98 		       if(i<0)			    /* suspend should be >=0 */
99 			  goto eusg;
100 		       suspend=i;
101 		       goto checkrdec;
102 		  }
103 	      case VERSIONOPT:elog("lockfile");elog(VERSION);
104 		    elog("\nYour system mailbox's lockfile:\t");
105 		    elog(auth_mailboxname(auth_finduid(getuid(),0)));
106 		    elog(lockext);elog("\n");
107 		  goto xusg;
108 	      case HELPOPT1:case HELPOPT2:elog(usage);
109 		 elog(
110  "\t-v\tdisplay the version number and exit\
111 \n\t-nnn\twait nnn seconds between locking attempts\
112 \n\t-r nnn\tmake at most nnn retries before giving up on a lock\
113 \n\t-l nnn\tset locktimeout to nnn seconds\
114 \n\t-s nnn\tsuspend nnn seconds after a locktimeout occurred\
115 \n\t-!\tinvert the exitcode of lockfile\
116 \n\t-ml\tlock your system mail-spool file\
117 \n\t-mu\tunlock your system mail-spool file\n");
118 		 goto xusg;
119 	      default:
120 		 if(sleepsec>=0)	    /* is this still the first pass? */
121 		  { if((sleepsec=strtol(cp2,&cp,10))<0)
122 		       goto eusg;
123 checkrdec:	    if(cp2==cp)
124 eusg:		     { elog(usage);		    /* regular usage message */
125 xusg:		       retval=EX_USAGE;
126 		       goto nfailure;
127 		     }
128 		  }
129 		 else		      /* no second pass, so leave sleepsec<0 */
130 		    strtol(cp2,&cp,10);		   /* and discard the number */
131 		 continue;
132 	      case 'm':		  /* take $LOGNAME as a hint, check if valid */
133 	       { auth_identity*pass;static char*ma;
134 		 if(*cp&&cp[1]||ma&&sleepsec>=0)	     /* second pass? */
135 		    goto eusg;
136 		 if(!ma)			/* ma initialised last time? */
137 		  { if(!((ma=(char*)getenv(lgname))&&
138 		      (pass=auth_finduser(ma,0))&&
139 		      auth_whatuid(pass)==uid||
140 		     (pass=auth_finduid(uid,0))))
141 		     { nlog("Can't determine your mailbox, who are you?\n");
142 		       goto nfailure;	 /* panic, you're not in /etc/passwd */
143 		     }
144 		    ;{ const char*p;
145 		       if(!*(p=auth_mailboxname(pass))||
146 			!(ma=malloc(strlen(p)+STRLEN(lockext)+1)))
147 			  goto outofmem;
148 		       strcat(strcpy(ma,p),lockext);
149 		     }
150 		  }
151 		 switch(*cp)
152 		  { default:
153 		       goto eusg;		    /* somebody goofed again */
154 		    case 'l':				 /* lock the mailbox */
155 		       if(sleepsec>=0)			      /* first pass? */
156 			{ cp=ma;
157 			  goto stilv;			    /* yes, lock it! */
158 			}
159 		    case 'u':			       /* unlock the mailbox */
160 		       if(unlink(ma))
161 			{ nlog("Can't unlock \"");elog(ma);elog("\"");
162 			  if(*cp=='l')	 /* they messed up, give them a hint */
163 			     elog(" again,\n already dropped my privileges");
164 			  elog("\n");
165 			}
166 		       else
167 			  virgin=0;
168 		  }
169 
170 	       }
171 	      case '\0':;
172 	    }
173 	   break;
174 	 }
175      else if(sleepsec<0)      /* second pass, release everything we acquired */
176 	unlink(cp);
177      else
178       { time_t t;int permanent;gid_t gid=getgid();
179 	if(setgid(gid)||getegid()!=gid)	      /* just to be on the safe side */
180 	   goto sp;
181 stilv:	virgin=0;permanent=nfsTRY;
182 	while(0>xcreat(cp,&t))				     /* try and lock */
183 	 { struct stat stbuf;
184 	   if(exitflag)					    /* time to stop? */
185 	    { if(exitflag==1)		     /* was it failure() or malloc() */
186 outofmem:	 retval=EX_OSERR,nlog("Out of memory");
187 	      else
188 		 retval=EX_TEMPFAIL,nlog("Signal received");
189 	      goto lfailure;
190 	    }
191 	   switch(errno)		    /* why did the lock not succeed? */
192 	    { case EEXIST:			  /* hmmm..., by force then? */
193 		 if(force&&!lstat(cp,&stbuf)&&force<t-stbuf.st_mtime)
194 		  { nlog(unlink(cp)?"Forced unlock denied on \"":
195 		     "Forcing lock on \"");
196 		    elog(cp);elog("\"\n");sleep(suspend);	/* important */
197 		  }
198 		 else					   /* no forcing now */
199 		    switch(retries)    /* await your turn like everyone else */
200 		     { case 0:nlog("Sorry");retval=EX_CANTCREAT;
201 			  goto lfailure;      /* patience exhausted, give up */
202 		       default:retries--;		      /* count sheep */
203 		       case -1:sleep(sleepsec);		     /* wait and see */
204 		     }
205 		 break;
206 	      case ENOSPC:
207 #ifdef EDQUOT
208 	      case EDQUOT:
209 #endif
210 	      case ENOENT:case ENOTDIR:case EIO:/*case EACCES:*/
211 		 if(!--permanent)	 /* NFS sporadically generates these */
212 		  { sleep(sleepsec);	/* unwarranted, so ignore them first */
213 		    continue;
214 		  }
215 	      default:		     /* but, it seems to persist, so give up */
216 		 nlog("Try praying");retval=EX_UNAVAILABLE;
217 #ifdef ENAMETOOLONG
218 		 goto lfailure;
219 	      case ENAMETOOLONG:
220 		 if(0<(permanent=strlen(cp)-1)&&      /* can we truncate it? */
221 		  !strchr(dirsep,cp[permanent-1]))
222 		  { nlog("Truncating \"");elog(cp);	      /* then try it */
223 		    elog("\" and retrying lock\n");cp[permanent]='\0';
224 		    break;
225 		  }				     /* otherwise, forget it */
226 		 nlog("Filename too long");retval=EX_UNAVAILABLE;
227 #endif
228 lfailure:	 elog(", giving up on \"");elog(cp);elog("\"\n");
229 nfailure:	 sleepsec= -1;argc=p-argv;		    /* mark sleepsec */
230 		 goto again;
231 	    }  /* for second pass, and adjust argc to the no. of args parsed */
232 	   permanent=nfsTRY;	       /* refresh the NFS-error-ignore count */
233 	 }
234       }
235   if(retval==EXIT_SUCCESS&&virgin)	 /* any errors?	 did we do anything? */
236 usg:
237    { elog(usage);
238      return EX_USAGE;
239    }
240   if(invert)
241      switch(retval)			 /* we only invert the regular cases */
242       { case EXIT_SUCCESS:
243 	   return EX_CANTCREAT;
244 	case EX_CANTCREAT:
245 	   return EXIT_SUCCESS;
246       }
247   return retval;			       /* all other exitcodes remain */
248 }
249 
tmalloc(len)250 void*tmalloc(len)const size_t len;				     /* stub */
251 { void*p;
252   if(!(p=malloc(len)))
253      exitflag=1;				     /* signal out of memory */
254   return p;
255 }
256 
tfree(p)257 void tfree(p)void*const p;					     /* stub */
258 { free(p);
259 }
260 
ropen(name,mode,mask)261 int ropen(name,mode,mask)const char*const name;const int mode;
262  const mode_t mask;
263 { return open(name,mode,mask);					     /* stub */
264 }
265 
rwrite(fd,a,len)266 int rwrite(fd,a,len)const int fd;const void*const a;const int len;   /* stub */
267 { return write(fd,a,len);
268 }
269 
rclose(fd)270 int rclose(fd)const int fd;					     /* stub */
271 { return close(fd);
272 }
273 
writeerr(a)274 void writeerr(a)const char*const a;				     /* stub */
275 {
276 }
277 
cstr(a,b)278 char*cstr(a,b)char*const a;const char*const b;			     /* stub */
279 { return 0;
280 }
281 
ssleep(seconds)282 void ssleep(seconds)const unsigned seconds;			     /* stub */
283 { sleep(seconds);
284 }
285