1 /* ========================================================================
2  * Copyright 1988-2008 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	Standalone Mailbox Lock program
16  *
17  * Author:	Mark Crispin
18  *		Networks and Distributed Computing
19  *		Computing & Communications
20  *		University of Washington
21  *		Administration Building, AG-44
22  *		Seattle, WA  98195
23  *		Internet: MRC@CAC.Washington.EDU
24  *
25  * Date:	8 February 1999
26  * Last Edited:	3 March 2008
27  */
28 
29 #include <errno.h>
30 #include <fcntl.h>
31 #include <stdio.h>
32 #include <sysexits.h>
33 #include <syslog.h>
34 #include <grp.h>
35 #include <sys/types.h>
36 #include <sys/file.h>
37 #include <sys/stat.h>
38 #include <sys/param.h>
39 #include <stdlib.h>
40 #include <netdb.h>
41 #include <ctype.h>
42 #include <string.h>
43 
44 #define LOCKTIMEOUT 5		/* lock timeout in minutes */
45 #define LOCKPROTECTION 0664
46 
47 #ifndef MAXHOSTNAMELEN		/* Solaris still sucks */
48 #define MAXHOSTNAMELEN 256
49 #endif
50 
51 /* Fatal error
52  * Accepts: Message string
53  *	    exit code
54  * Returns: code
55  */
56 
die(char * msg,int code)57 int die (char *msg,int code)
58 {
59   syslog (LOG_NOTICE,"(%u) %s",code,msg);
60   write (1,"?",1);		/* indicate "impossible" failure */
61   return code;
62 }
63 
64 
main(int argc,char * argv[])65 int main (int argc,char *argv[])
66 {
67   int ld,i;
68   int tries = LOCKTIMEOUT * 60 - 1;
69   char *s,*dir,*file,*lock,*hitch,tmp[1024];
70   size_t dlen,len;
71   struct stat sb,fsb;
72   struct group *grp = getgrnam ("mail");
73 				/* get syslog */
74   openlog (argv[0],LOG_PID,LOG_MAIL);
75   if (!grp || (grp->gr_gid != getegid ()))
76     return die ("not setgid mail",EX_USAGE);
77   if (argc != 3) return die ("invalid arguments",EX_USAGE);
78   for (s = argv[1]; *s; s++)
79     if (!isdigit (*s)) return die ("invalid fd",EX_USAGE);
80 				/* find directory */
81   if ((*argv[2] != '/') || !(file = strrchr (argv[2],'/')) || !file[1])
82     return die ("invalid path",EX_USAGE);
83 				/* calculate lengths of directory and file */
84   if (!(dlen = file - argv[2])) dlen = 1;
85   len = strlen (++file);
86 				/* make buffers */
87   dir = (char *) malloc (dlen + 1);
88   lock = (char *) malloc (len + 6);
89   hitch = (char *) malloc (len + 6 + 40 + MAXHOSTNAMELEN);
90   if (!dir || !lock || !hitch) return die ("malloc failure",errno);
91   strncpy (dir,argv[2],dlen);	/* connect to desired directory */
92   dir[dlen] = '\0';
93   printf ("dir=%s, file=%s\n",dir,file);
94   chdir (dir);
95 				/* get device/inode of file descriptor */
96   if (fstat (atoi (argv[1]),&fsb)) return die ("fstat failure",errno);
97 				/* better be a regular file */
98   if ((fsb.st_mode & S_IFMT) != S_IFREG)
99     return die ("fd not regular file",EX_USAGE);
100 				/* now get device/inode of file */
101   if (lstat (file,&sb)) return die ("lstat failure",errno);
102 				/* does it match? */
103   if ((sb.st_mode & S_IFMT) != S_IFREG)
104     return die ("name not regular file",EX_USAGE);
105   if ((sb.st_dev != fsb.st_dev) || (sb.st_ino != fsb.st_ino))
106     return die ("fd and name different",EX_USAGE);
107 				/* build lock filename */
108   sprintf (lock,"%s.lock",file);
109   if (!lstat (lock,&sb) && ((sb.st_mode & S_IFMT) != S_IFREG))
110     return die ("existing lock not regular file",EX_NOPERM);
111 
112   do {				/* until OK or out of tries */
113     if (!stat (lock,&sb) && (time (0) > (sb.st_ctime + LOCKTIMEOUT * 60)))
114       unlink (lock);		/* time out lock if enough time has passed */
115     /* SUN-OS had an NFS
116      * As kludgy as an albatross;
117      * And everywhere that it was installed,
118      * It was a total loss.
119      * -- MRC 9/25/91
120      */
121 				/* build hitching post file name */
122     sprintf (hitch,"%s.%lu.%lu.",lock,(unsigned long) time (0),
123 	     (unsigned long) getpid ());
124     len = strlen (hitch);	/* append local host name */
125     gethostname (hitch + len,MAXHOSTNAMELEN);
126 				/* try to get hitching-post file */
127     if ((ld = open (hitch,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) {
128 				/* make sure others can break the lock */
129       chmod (hitch,LOCKPROTECTION);
130 				/* get device/inode of hitch file */
131       if (fstat (ld,&fsb)) return die ("hitch fstat failure",errno);
132       close (ld);		/* close the hitching-post */
133       /* Note: link() may return an error even if it actually succeeded.  So we
134        * always check for success via the link count, and ignore the error if
135        * the link count is right.
136        */
137 				/* tie hitching-post to lock */
138       i = link (hitch,lock) ? errno : 0;
139 				/* success if link count now 2 */
140       if (stat (hitch,&sb) || (sb.st_nlink != 2) ||
141 	  (fsb.st_dev != sb.st_dev) || (fsb.st_ino != sb.st_ino)) {
142 	ld = -1;		/* failed to hitch */
143 	if (i == EPERM) {	/* was it because links not allowed? */
144 	  /* Probably a FAT filesystem on Linux.  It can't be NFS, so try
145 	   * creating the lock file directly.
146 	   */
147 	  if ((ld = open (lock,O_WRONLY|O_CREAT|O_EXCL,LOCKPROTECTION)) >= 0) {
148 	    /* get device/inode of lock file */
149 	    if (fstat (ld,&fsb)) return die ("lock fstat failure",errno);
150 	    close (ld);		/* close the file */
151 	  }
152 				/* give up immediately if protection failure */
153 	  else if (errno != EEXIST) tries = 0;
154 	}
155       }
156       unlink (hitch);		/* flush hitching post */
157     }
158 				/* give up immediately if protection failure */
159     else if (errno == EACCES) tries = 0;
160     if (ld < 0) {		/* lock failed */
161       if (tries--) sleep (1);	/* sleep 1 second and try again */
162       else {
163 	write (1,"-",1);	/* hard failure */
164 	return EX_CANTCREAT;
165       }
166     }
167   } while (ld < 0);
168   write (1,"+",1);		/* indicate that all is well */
169   read (0,tmp,1);		/* read continue signal from parent */
170 				/* flush the lock file */
171   if (!stat (lock,&sb) && (fsb.st_dev == sb.st_dev) &&
172       (fsb.st_ino == sb.st_ino)) unlink (lock);
173   else syslog (LOG_NOTICE,"lock file %s/%s changed dev/inode",dir,lock);
174   return EX_OK;
175 }
176