chiark / gitweb /
prefork: Allow script not to be set
[chiark-utils.git] / cprogs / prefork.c
1 /* common stuff for cgi-fcgi-interp and prefork-interp */
2
3 #include "prefork.h"
4
5 const char *interp, *ident;
6 int numservers=4, debugmode;
7 int check_interval=300;
8
9 struct sha256_ctx identsc;
10
11 uid_t us;
12 const char *run_base, *script, *socket_path, *lock_path;
13 const char *run_base_mkdir_p;
14 bool logging;
15
16 void common_diee(const char *m) { diee("%s", m); }
17 void common_die (const char *m) { die ("%s", m); }
18
19 void vmsgcore(int estatus, int errnoval, const char *fmt, va_list al) {
20   int r;
21
22   if (logging) {
23     const char *fmt_use = fmt;
24     char *fmt_free = 0;
25     if (errnoval) {
26       r = asprintf(&fmt_free, "%s: %%m", fmt);
27       if (r) {
28         fmt_free = 0;
29       } else {
30         fmt_use = fmt_free;
31       }
32     }
33     vsyslog(LOG_ERR, fmt_use, al);
34     free(fmt_free);
35   } else {
36     fprintf(stderr, "%s: ", our_name);
37     vfprintf(stderr,fmt,al);
38     if (errnoval!=-1) fprintf(stderr,": %s",strerror(errnoval));
39     fputc('\n',stderr);
40   }
41   if (estatus) exit(estatus);
42 }
43
44 void usagemessage(void) { fusagemessage(stderr); }
45
46 void of_help(const struct cmdinfo *ci, const char *val) {
47   fusagemessage(stdout);
48   if (ferror(stdout)) diee("write usage message to stdout");
49   exit(0);
50 }
51
52 void of_iassign(const struct cmdinfo *ci, const char *val) {
53   long v;
54   char *ep;
55   errno= 0; v= strtol(val,&ep,10);
56   if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
57     badusage("bad integer argument `%s' for --%s",val,ci->olong);
58   *ci->iassignto = v;
59 }
60
61 void ident_addstring(const struct cmdinfo *ci, const char *string) {
62   /* ci may be 0 and is provided so this can be .call */
63   sha256_update(&identsc,strlen(string)+1,string);
64 }
65
66 void off_ident_addenv(const struct cmdinfo *ci, const char *name) {
67   const char *val = getenv(name);
68   if (val) {
69     sha256_update(&identsc,strlen(name),name); /* no nul */
70     sha256_update(&identsc,1,"=");
71     ident_addstring(0,val);
72   } else {
73     ident_addstring(0,name);
74   }
75 }
76
77 bool find_run_base_var_run(void) {
78   struct stat stab;
79   char *try;
80   int r;
81
82   try = m_asprintf("%s/%lu", "/var/run/user", us);
83   r = lstat(try, &stab);
84   if (r<0) {
85     if (errno == ENOENT ||
86         errno == ENOTDIR ||
87         errno == EACCES ||
88         errno == EPERM)
89       return 0; /* oh well */
90     diee("stat /var/run/user/UID");
91   }
92   if (!S_ISDIR(stab.st_mode)) {
93     warning("%s not a directory, falling back to ~\n", try);
94     return 0;
95   }
96   if (stab.st_uid != us) {
97     warning("%s not owned by uid %lu, falling back to ~\n", try,
98             (unsigned long)us);
99     return 0;
100   }
101   if (stab.st_mode & 0077) {
102     warning("%s writeable by group or other, falling back to ~\n", try);
103     return 0;
104   }
105   run_base = m_asprintf("%s/%s", try, our_name);
106   return 1;
107 }
108
109 static bool find_run_base_home(void) {
110   struct passwd *pw;
111   struct utsname ut;
112   char *dot, *try;
113   int r;
114
115   pw = getpwuid(us);  if (!pw) diee("getpwent(uid)");
116
117   r = uname(&ut);   if (r) diee("uname(2)");
118   dot = strchr(ut.nodename, '.');
119   if (dot) *dot = 0;
120   if (sizeof(ut.nodename) > 32)
121     ut.nodename[32] = 0;
122
123   run_base_mkdir_p = m_asprintf("%s/.%s", pw->pw_dir, our_name);
124   try = m_asprintf("%s/%s", run_base_mkdir_p, ut.nodename);
125   run_base = try;
126   return 1;
127 }
128
129 void find_socket_path(void) {
130   struct sockaddr_un sun;
131   int r;
132
133   us = getuid();  if (us==(uid_t)-1) diee("getuid");
134
135   find_run_base_var_run() ||
136     find_run_base_home() ||
137     (abort(),0);
138
139   int maxidentlen = sizeof(sun.sun_path) - strlen(run_base) - 10 - 2;
140
141   if (!ident) {
142     if (maxidentlen < MINHEXHASH)
143       die("base directory `%s'"
144           " leaves only %d characters for id hash"
145           " which is too little (<%d)",
146           run_base, maxidentlen, MINHEXHASH);
147
148     int identlen = maxidentlen > 64 ? 64 : maxidentlen;
149     char *hexident = xmalloc(identlen + 2);
150     unsigned char bbuf[32];
151     int i;
152
153     ident_addstring(0,interp);
154     ident_addstring(0,script);
155     sha256_digest(&identsc,sizeof(bbuf),bbuf);
156
157     for (i=0; i<identlen; i += 2)
158       sprintf(hexident+i, "%02x", bbuf[i/2]);
159
160     hexident[identlen] = 0;
161     ident = hexident;
162   }
163
164   if (strlen(ident) > maxidentlen)
165     die("base directory `%s' plus ident `%s' too long"
166         " (with spare) for socket (max ident %d)\n",
167         run_base, ident, maxidentlen);
168
169   r = mkdir(run_base, 0700);
170   if (r && errno==ENOENT && run_base_mkdir_p) {
171     r = mkdir(run_base_mkdir_p, 0700);
172     if (r) diee("mkdir %s (since %s was ENOENT)",run_base_mkdir_p,run_base);
173     r = mkdir(run_base, 0700);
174   }
175   if (r) {
176     if (!(errno == EEXIST))
177       diee("mkdir %s",run_base);
178   }
179
180   socket_path = m_asprintf("%s/s%s",run_base,ident);
181 }  
182
183 #ifdef st_mtime
184
185 bool stab_isnewer(const struct stat *a, const struct stat *b) {
186   if (debugmode)
187     fprintf(stderr,"stab_isnewer mtim %lu.%06lu %lu.06%lu\n",
188             (unsigned long)a->st_mtim.tv_sec,
189             (unsigned long)a->st_mtim.tv_nsec,
190             (unsigned long)b->st_mtim.tv_sec,
191             (unsigned long)b->st_mtim.tv_nsec);
192   return timespeccmp(&a->st_mtim, &b->st_mtim, >);
193 }
194
195 void stab_mtimenow(struct stat *out) {
196   int r = clock_gettime(CLOCK_REALTIME, &out->st_mtim);
197   if (r) diee("(stage2) clock_gettime");
198   if (debugmode)
199     fprintf(stderr,"stab_mtimenow mtim %lu.%06lu\n",
200             (unsigned long)out->st_mtim.tv_sec,
201             (unsigned long)out->st_mtim.tv_nsec);
202 }
203
204 #else /* !defined(st_mtime) */
205
206 bool stab_isnewer(const struct stat *a, const struct stat *b) {
207   if (debugmode)
208     fprintf(stderr,"stab_isnewer mtime %lu %lu\n",
209             (unsigned long)a->st_mtime,
210             (unsigned long)b->st_mtime);
211   return a->st_mtime > b->st_mtime;
212 }
213
214 void stab_mtimenow(struct stat *out) {
215   out->st_mtime = time(NULL);
216   if (out->st_mtime == (time_t)-1) diee("(stage2) time()");
217   if (debugmode)
218     fprintf(stderr,"stab_mtimenow mtime %lu\n",
219             (unsigned long)out->st_mtime);
220 }
221
222 #endif /* !defined(st_mtime) */
223
224 bool check_garbage_vs(const struct stat *started) {
225   struct stat script_stab;
226   int r;
227
228   r = lstat(script, &script_stab);
229   if (r) diee("lstat script (%s)",script);
230
231   if (stab_isnewer(&script_stab, started))
232     return 1;
233
234   if (S_ISLNK(script_stab.st_mode)) {
235     r = stat(script, &script_stab);
236     if (r) diee("stat script (%s0",script);
237
238     if (stab_isnewer(&script_stab, started))
239       return 1;
240   }
241
242   return 0;
243 }
244
245 bool check_garbage(void) {
246   struct stat sock_stab;
247   int r;
248
249   r = lstat(socket_path, &sock_stab);
250   if (r) {
251     if ((errno == ENOENT))
252       return 0; /* well, no garbage then */
253     diee("stat socket (%s)",socket_path);
254   }
255
256   return check_garbage_vs(&sock_stab);
257 }
258
259 // Returns fd
260 int acquire_lock(void) {
261   int r;
262   int lockfd = -1;
263
264   lock_path = m_asprintf("%s/l%s",run_base,ident);
265
266   lockfd = open(lock_path, O_CREAT|O_RDWR, 0600);
267   if (lockfd<0) diee("create lock (%s)", lock_path);
268
269   r = flock(lockfd, LOCK_EX);
270   if (r) diee("lock lock (%s)", lock_path);
271
272   return lockfd;
273 }
274
275 void tidy_garbage(void) {
276   /* We lock l<ident> and re-check.  The effect of this is that each
277    * stale socket is removed only once.  So unless multiple updates to
278    * the script happen rapidly, we can't be racing with the cgi-fcgi
279    * (which is recreating the socket */
280   int lockfd = -1;
281   int r;
282
283   lockfd = acquire_lock();
284
285   if (check_garbage()) {
286     r = unlink(socket_path);
287     if (r) {
288       if (!(errno == ENOENT))
289         diee("remove out-of-date socket (%s)", socket_path);
290     }
291   }
292
293   r = close(lockfd);
294   if (r) diee("close lock (%s)", lock_path);
295 }
296
297 static void shbang_opts(const char *const **argv_io,
298                         const struct cmdinfo *cmdinfos) {
299   myopt(argv_io, cmdinfos);
300
301   interp = *(*argv_io)++;
302   if (!interp) badusage("need interpreter argument");
303 }
304
305 void process_opts(const char *const **argv_io) {
306   const char *smashedopt;
307
308   sha256_init(&identsc);
309
310   if ((*argv_io)[0] &&
311       (smashedopt = (*argv_io)[1]) &&
312       smashedopt[0]=='-' &&
313       (strchr(smashedopt,' ') || strchr(smashedopt,','))) {
314     /* single argument containg all the options and <interp> */
315     *argv_io += 2; /* eat argv[0] and smashedopt */
316     const char *split_args[MAX_OPTS+1];
317     int split_argc = 0;
318     split_args[split_argc++] = (*argv_io)[0];
319     for (;;) {
320       if (split_argc >= MAX_OPTS) die("too many options in combined arg");
321       split_args[split_argc++] = smashedopt;
322       if (smashedopt[0] != '-') /* never true on first iteration */
323         break;
324       char *delim = strchr(smashedopt,' ');
325       if (!delim) delim = strchr(smashedopt,',');
326       if (!delim) badusage("combined arg lacks <interpreter>");
327       *delim = 0;
328       smashedopt = delim+1;
329     }
330     assert(split_argc <= MAX_OPTS);
331     split_args[split_argc++] = 0;
332
333     const char *const *split_argv = split_args;
334
335     shbang_opts(&split_argv, cmdinfos);
336     /* sets interp */
337   } else {
338     shbang_opts(argv_io, cmdinfos);
339   }
340
341   if (**argv_io)
342     script = *(*argv_io)++;
343 }