1    | /*  spawn.c
2    | 
3    |     Source file for spawn operations for  PGPsendmail  (wrapper to sendmail).
4    | 
5    |     Copyright (C) 1994-1998  Richard Gooch
6    | 
7    |     This program is free software; you can redistribute it and/or modify
8    |     it under the terms of the GNU General Public License as published by
9    |     the Free Software Foundation; either version 2 of the License, or
10   |     (at your option) any later version.
11   | 
12   |     This program is distributed in the hope that it will be useful,
13   |     but WITHOUT ANY WARRANTY; without even the implied warranty of
14   |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   |     GNU General Public License for more details.
16   | 
17   |     You should have received a copy of the GNU General Public License
18   |     along with this program; if not, write to the Free Software
19   |     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20   | 
21   |     Richard Gooch may be reached by email at  rgooch@atnf.csiro.au
22   |     The postal address is:
23   |       Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
24   | */
25   | 
26   | /*  This programme intercepts messages sent by user mail agents to the
27   |     sendmail daemon and checks to see if messages can be encrypted using the
28   |     recipient's PGP public keys.
29   | 
30   | 
31   |     Written by      Richard Gooch   31-MAY-1994
32   | 
33   |     Updated by      Richard Gooch   31-MAY-1994: Extracted from  pgpsendmail.c
34   | 
35   |     Updated by      Richard Gooch   18-JUN-1994: Made error messages more
36   |   explicit.
37   | 
38   |     Updated by      Richard Gooch   27-JUN-1994: Copied  set_env  from
39   |   pgpdaemon.c
40   | 
41   |     Updated by      Richard Gooch   5-JUL-1994: Changed to use of  m_copy  .
42   | 
43   |     Updated by      Richard Gooch   14-JUL-1994: Moved  copy_data  and  set_env
44   |   to  misc.c
45   | 
46   |     Updated by      Richard Gooch   3-DEC-1994: Fixed bug for externally set
47   |   error descriptor.
48   | 
49   |     Updated by      Richard Gooch   25-SEP-1997: Used new ERRSTRING macro.
50   | 
51   |     Last updated by Richard Gooch   10-JUL-1998: Removed definitions of system
52   |   errlist array.
53   | 
54   | 
55   | */
56   | #include <stdio.h>
57   | #include <stdlib.h>
58   | #include <errno.h>
59   | #include <sys/types.h> 
60   | #include <unistd.h>
61   | #include <string.h>
62   | #include <sys/stat.h>
63   | #include <fcntl.h>
64   | #include <time.h>
65   | 
66   | /* #include "pgpsendmail.h" */
67   | 
68   | #define ERRSTRING strerror(errno)
69   | 
70   | #define LINE_LENGTH 1024
71   | #define STRING_LENGTH 255
72   | 
73   | enum NFSL {
74   |   NFSL_SYSF,
75   |   NFSL_SECV,
76   |   NFSL_LOCKED,
77   |   NFSL_OK,
78   |   NFSL_STOLEN,
79   |   NFSL_LOST
80   | };
81   | 
82   | 
83   | int sd1[2] /*, sd2[2] */;  /* sd2 is used to have a collaborative 
84   | 		                  dialogue between processes          */
85   | int spawn_job (char *path, char *argv[], int *in_fd, int *out_fd, int *err_fd)
86   | /*  This routine will fork(2) and execvp(2) a process.
87   |     The file to execute must be pointed to by  path  .
88   |     The NULL terminated list of arguments which will be passed to  main  must
89   |     be pointed to by  argv  .
90   |     The input file descriptor (fd = 0) for the process must be pointed to by
91   |     in_fd  .If the value here is less than 0, then a pipe to the process is
92   |     opened and the writeable end is written to the storage pointed to by  in_fd
93   |     The standard output file descriptor (fd = 1) for the process must be
94   |     pointed to by  out_fd  .If the value here is less than 0, then a pipe to
95   |     the process is opened and the readable end is written to the storage
96   |     pointed to by  out_fd  .
97   |     The standard error output file descriptor (fd = 2) for the process must be
98   |     pointed to by  err_fd  .If the value here is less than 0, then a pipe to
99   |     the process is opened and the readable end is written to the storage
100  |     pointed to by  err_fd  .
101  |     The routine returns the child process ID on success, else it returns -1.
102  | */
103  | {
104  |     int child_pid;
105  |     /*     char txt[LINE_LENGTH];  */
106  | 
107  |     if (pipe(sd1) == -1)
108  |       {
109  | 	perror("pipe failed");
110  | 	return(1);
111  |       }
112  | 
113  |     /*  Fork and exec  */
114  |     switch ( child_pid = fork () )
115  |     {
116  |       case 0:
117  | 	/*  Child: exec  */
118  | 	close(sd1[0]); 
119  | 
120  | 	dup2( sd1[1], 1 );   /* stdout */
121  | 
122  | 	/*	fprintf (stderr, "dup2 1 result: %s\n", ERRSTRING); */
123  | 	dup2( sd1[1], 2 );    /* stderr */
124  | 	/*	fprintf (stderr, "dup2 2 result: %s\n", ERRSTRING); */
125  | 
126  | 	execvp (path, argv);
127  | 
128  | 	fprintf (stderr, "Could not exec: \"%s\"\t%s\n", path, ERRSTRING);
129  | 	exit (1); 
130  | 	break;
131  |       case -1:
132  | 	/*  Error  */
133  | 	fprintf (stderr, "Could not fork\t%s\n", ERRSTRING);
134  | 	return (-1);
135  | 	break;
136  |       default:
137  | 	/*  Parent  */
138  | 	break;
139  |     }
140  |     /*  Parent only  */
141  | 
142  |     close(sd1[1]); 
143  | 
144  |     dup2 (sd1[0], 0); 
145  |     
146  |     /*     fprintf (stderr, "dup2 0 result: %s\n", ERRSTRING);  */
147  |     /*     fprintf(stderr, "Reading child output\n");
148  |     while (read(0, txt, 1000) != 0)
149  |       fprintf(stderr, "child read %s\n", txt);
150  |     
151  |       fprintf(stderr, "Finished reading child output\n");   */
152  |     
153  |     return (child_pid);
154  | }   /*  End Function spawn_job  */
155  | 
156  | 
157  | #define DEFAULT_LOCKTIME 300;
158  | 
159  | time_t nfslock(char *path, char *namelock, int max_age, int notify)
160  | {
161  |   int tries = 0, oldlck = 0, tmpfd;
162  |   struct stat old_stat, our_tmp;
163  |   char tmp[32];
164  |   char *tpath, *fullpath;
165  | 
166  |   srandom(getpid()+time(0));
167  |   max_age = max_age ? max_age : DEFAULT_LOCKTIME;
168  | 
169  |   /*
170  |    * 1. create a tmp file with a psuedo random file name. we also make
171  |    *    tpath which is a buffer to store the full pathname of the tmp file.
172  |    */
173  | 
174  |   sprintf(tmp,"slock%d.%d", (int)getpid(), (int)random());
175  | 
176  |   tpath = malloc(strlen(path) + 1 + strlen(tmp) + 1);
177  |   if (tpath == NULL) return(NFSL_SYSF);
178  |   sprintf(tpath,"%s/%s", path, tmp);
179  | 
180  |   tmpfd = open(tpath, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);
181  | 
182  |   if (tmpfd < 0) {      /* open failed */
183  |     close(tmpfd);
184  |     unlink(tpath);
185  |     free(tpath);
186  |     return(NFSL_SYSF);
187  |   }
188  | 
189  |   if (getuid()==0) {    /* we're root, so be careful! */
190  |     if (fstat(tmpfd, &our_tmp) < 0) {   /* stat failed... shouldn't happen */
191  |       close(tmpfd);
192  |       unlink(tpath);
193  |       free(tpath);
194  |       return(NFSL_SYSF);
195  |     }
196  |     if (our_tmp.st_nlink != 1) {        /* someone's trying to mess with us */
197  |       fprintf(stderr,"nfslock: bad link count on %s\n",tpath);
198  |       close(tmpfd);
199  |       unlink(tpath);
200  |       free(tpath);
201  |       return(NFSL_SECV);
202  |     }
203  |   }
204  | 
205  |   /*
206  |    * 2. make fullpath, a buffer for the full pathname of the lock file.
207  |    *    then start looping trying to lock it
208  |    */
209  | 
210  |   fullpath = malloc(strlen(path) + 1 + strlen(namelock) + 1);
211  |   if (fullpath == NULL) {
212  |     close(tmpfd);
213  |     unlink(tpath);
214  |     free(tpath);
215  |     return(NFSL_SYSF);
216  |   }
217  |   sprintf(fullpath,"%s/%s", path, namelock);
218  | 
219  |   while (tries < 10) {
220  | 
221  |     /*
222  |      * 3. link tmp file to lock file.  if it goes, we win and we clean
223  |      *    up and return the st_ctime of the lock file.
224  |      */
225  | 
226  |     if (link(tpath, fullpath) == 0) {
227  |       unlink(tpath); /* got it! */
228  |       free(tpath);
229  |       close(tmpfd);
230  |       if (lstat(fullpath, &our_tmp) < 0) {      /* stat failed... shouldn't happen */
231  |         unlink(fullpath);
232  |         free(fullpath);
233  |         return (NFSL_SYSF);
234  |       }
235  |       free(fullpath);
236  |       return(our_tmp.st_ctime);
237  |     }
238  | 
239  |     /*
240  |      * 4. the lock failed.  check for a stale lock file, being mindful
241  |      *    of NFS and the fact the time is set from the NFS server.  we
242  |      *    do a write on the tmp file to update its time to the server's
243  |      *    idea of "now."
244  |      */
245  | 
246  |     oldlck = lstat(fullpath, &old_stat);
247  | 
248  |     if (write(tmpfd, "\0", 1) != 1 || fstat(tmpfd, &our_tmp) < 0)
249  |       break; /* something bogus is going on */
250  | 
251  |     if (oldlck != -1 && old_stat.st_ctime + max_age < our_tmp.st_ctime) {
252  |       unlink(fullpath); /* break the stale lock */
253  |       if (notify) fprintf(stderr,"Breaking stale lock on %s.\n",fullpath);
254  |       tries++;
255  |       sleep(1+(random() % 10)); /* It is CRITICAL that we sleep after breaking
256  |                                  * the lock. Otherwise, we could race with
257  |                                  * another process and unlink it's newly-
258  |                                  * created file.
259  |                                  */
260  |       continue;
261  |     }
262  | 
263  |     /*
264  |      * 5. try again
265  |      */
266  | 
267  |     if (notify) {
268  |       if (tries==0) fprintf(stderr,"Waiting for lock on file %s.\n",fullpath);
269  |       else fprintf(stderr,"Still waiting...\n");
270  |     }
271  |     tries++;
272  |     sleep(1+(random() % 10));
273  |   }
274  | 
275  |   /*
276  |    * 6. give up, failure.
277  |    */
278  | 
279  |   errno = EEXIST;
280  |   unlink(tpath);
281  |   free(tpath);
282  |   free(fullpath);
283  |   close(tmpfd);
284  |   return(NFSL_LOCKED);
285  | }
286  | 
287  | int nfsunlock(char *path, char *namelock, int max_age, time_t birth)
288  | {
289  |   int tmpfd;
290  |   struct stat old_stat, our_tmp;
291  |   char *tpath, *fullpath;
292  | 
293  |   srandom(getpid()+time(0));
294  |   max_age = max_age ? max_age : DEFAULT_LOCKTIME;
295  | 
296  |   /*
297  |    * 1. Build a temp file and stat that to get an idea of what the server
298  |    *    thinks the current time is (our_tmp.st_ctime)..
299  |    */
300  | 
301  |   tpath = malloc(strlen(path) + 25);    /* small slop factor- 23 s/b enough */
302  |   if (tpath == NULL) return(NFSL_SYSF);
303  |   sprintf(tpath,"%s/slock%d.%d", path, (int)getpid(), (int)random());
304  | 
305  |   tmpfd = open(tpath, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);
306  | 
307  |   if ((tmpfd < 0) || (write(tmpfd, "\0", 1) != 1)
308  |                   || (fstat(tmpfd, &our_tmp) < 0)) {
309  |     /* The open failed, or we can't write the file, or we can't stat it */
310  |     close(tmpfd);
311  |     unlink(tpath);
312  |     free(tpath);
313  |     return(NFSL_SYSF);
314  |   }
315  | 
316  |   close(tmpfd);         /* We don't need this once we have our_tmp.st_ctime. */
317  |   unlink(tpath);
318  |   free(tpath);
319  | 
320  |   /*
321  |    * 2. make fullpath, a buffer for the full pathname of the lock file
322  |    */
323  | 
324  |   fullpath = malloc(strlen(path) + 1 + strlen(namelock) + 1);
325  |   if (fullpath == NULL)
326  |     return(NFSL_SYSF);
327  |   sprintf(fullpath,"%s/%s", path, namelock);
328  | 
329  |   /*
330  |    * 3. If the ctime hasn't been modified, unlink the file and return. If the
331  |    *    lock has expired, sleep the usual random interval before returning.
332  |    *    If we didn't sleep, there could be a race if the caller immediately
333  |    *    tries to relock the file.
334  |    */
335  | 
336  |   if ( !lstat(fullpath, &old_stat) &&   /* stat succeeds so file is there */
337  |       (old_stat.st_ctime == birth)) {   /* hasn't been modified since birth */
338  |     unlink(fullpath);                   /* so the lock is ours to remove */
339  |     if (our_tmp.st_ctime >= birth + max_age)    /* the lock has expired */
340  |       sleep(1+(random() % 10));         /* so sleep a bit */
341  |     free(fullpath);
342  |     return(NFSL_OK);                    /* success */
343  |   }
344  | 
345  |   free(fullpath);       /* we don't use fullpath anymore */
346  | 
347  |   /*
348  |    * 4. Either ctime has been modified, or the entire lock file is missing.
349  |    *    If the lock should still be ours, based on the ctime of the temp
350  |    *    file, return with NFSL_STOLEN. If not, then our lock is expired and
351  |    *    someone else has grabbed the file, so return NFSL_LOST.
352  |    */
353  | 
354  |   if (our_tmp.st_ctime < birth + max_age)       /* lock was stolen */
355  |     return(NFSL_STOLEN);
356  | 
357  |   return(NFSL_LOST);    /* The lock must have expired first. */
358  | }
359  | 
360  | 
361  |