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