chiark / gitweb /
Bump version to 7.0.1~iwj0
[chiark-utils.git] / cprogs / prefork.c
1 /* common stuff for cgi-fcgi-interp and prefork-interp */
2 /*
3  * Copyright 2016-2022 Ian Jackson and contributors to chiark-utils
4  * SPDX-License-Identifier: GPL-3.0-or-later
5  * There is NO WARRANTY.
6  */
7
8 #include "prefork.h"
9
10 const char *interp, *ident, *script, *socket_path, *lock_path;
11 bool logging;
12 struct sha256_ctx identsc;
13 const char *run_base;
14
15 static uid_t us;
16 static const char *run_base_mkdir_p;
17
18 void common_diee(const char *m) { diee("%s", m); }
19 void common_die (const char *m) { die ("%s", m); }
20
21 void vmsgcore(int estatus, int errnoval, const char *fmt, va_list al) {
22   int r;
23
24   if (logging) {
25     const char *fmt_use = fmt;
26     char *fmt_free = 0;
27     if (errnoval!=-1) {
28       r = asprintf(&fmt_free, "%s: %%m", fmt);
29       if (r) {
30         fmt_free = 0;
31       } else {
32         fmt_use = fmt_free;
33       }
34     }
35     vsyslog(LOG_ERR, fmt_use, al);
36     free(fmt_free);
37   } else {
38     fprintf(stderr, "%s: ", our_name);
39     vfprintf(stderr,fmt,al);
40     if (errnoval!=-1) fprintf(stderr,": %s",strerror(errnoval));
41     fputc('\n',stderr);
42   }
43   if (estatus) exit(estatus);
44 }
45
46 void usagemessage(void) { fusagemessage(stderr); }
47
48 void of_help(const struct cmdinfo *ci, const char *val) {
49   fusagemessage(stdout);
50   if (ferror(stdout)) diee("write usage message to stdout");
51   exit(0);
52 }
53
54 void of_iassign(const struct cmdinfo *ci, const char *val) {
55   long v;
56   char *ep;
57   errno= 0; v= strtol(val,&ep,10);
58   if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
59     badusage("bad integer argument `%s' for --%s",val,ci->olong);
60   *ci->iassignto = v;
61 }
62
63 void ident_add_key_byte(char key) {
64   sha256_update(&identsc,1,&key);
65 }
66
67 void ident_addstring(char key, const char *string) {
68   ident_add_key_byte(key);
69   sha256_update(&identsc,strlen(string)+1,string);
70 }
71
72 void off_ident_addstring(const struct cmdinfo *ci, const char *string) {
73   ident_addstring('G', string);
74 }
75
76 void off_ident_addenv(const struct cmdinfo *ci, const char *name) {
77   ident_addstring('E', name);
78   const char *val = getenv(name);
79   if (val) {
80     ident_addstring('v', val);
81   } else {
82     ident_add_key_byte(0);
83   }
84 }
85
86 bool stabs_same_inode(struct stat *a, struct stat *b) {
87   return (a->st_dev == b->st_dev &&
88           a->st_ino == b->st_ino);
89 }
90
91 bool find_run_base_var_run(void) {
92   struct stat stab;
93   char *try;
94   int r;
95
96   try = m_asprintf("%s/%lu", "/var/run/user", us);
97   r = lstat(try, &stab);
98   if (r<0) {
99     if (errno == ENOENT ||
100         errno == ENOTDIR ||
101         errno == EACCES ||
102         errno == EPERM)
103       return 0; /* oh well */
104     diee("stat /var/run/user/UID");
105   }
106   if (!S_ISDIR(stab.st_mode)) {
107     warning("%s not a directory, falling back to ~\n", try);
108     return 0;
109   }
110   if (stab.st_uid != us) {
111     warning("%s not owned by uid %lu, falling back to ~\n", try,
112             (unsigned long)us);
113     return 0;
114   }
115   if (stab.st_mode & 0077) {
116     warning("%s writeable by group or other, falling back to ~\n", try);
117     return 0;
118   }
119   run_base = m_asprintf("%s/%s", try, our_name);
120   return 1;
121 }
122
123 static bool find_run_base_home(void) {
124   struct passwd *pw;
125   struct utsname ut;
126   char *dot, *try;
127   int r;
128
129   pw = getpwuid(us);  if (!pw) diee("getpwent(uid)");
130
131   r = uname(&ut);   if (r) diee("uname(2)");
132   dot = strchr(ut.nodename, '.');
133   if (dot) *dot = 0;
134   if (sizeof(ut.nodename) > 32)
135     ut.nodename[32] = 0;
136
137   run_base_mkdir_p = m_asprintf("%s/.%s", pw->pw_dir, our_name);
138   try = m_asprintf("%s/%s", run_base_mkdir_p, ut.nodename);
139   run_base = try;
140   return 1;
141 }
142
143 void find_socket_path(void) {
144   struct sockaddr_un sun;
145   int r;
146
147   us = getuid();  if (us==(uid_t)-1) diee("getuid");
148
149   find_run_base_var_run() ||
150     find_run_base_home() ||
151     (abort(),0);
152
153   int maxidentlen = sizeof(sun.sun_path) - strlen(run_base) - 10 - 2;
154
155   if (!ident) {
156     if (maxidentlen < MINHEXHASH)
157       die("base directory `%s'"
158           " leaves only %d characters for id hash"
159           " which is too little (<%d)",
160           run_base, maxidentlen, MINHEXHASH);
161
162     int identlen = maxidentlen > 64 ? 64 : maxidentlen;
163     char *hexident = xmalloc(identlen + 2);
164     unsigned char bbuf[32];
165     int i;
166
167     ident_addstring('i', interp);
168     if (script)
169       ident_addstring('s', script);
170     sha256_digest(&identsc,sizeof(bbuf),bbuf);
171
172     for (i=0; i<identlen; i += 2)
173       sprintf(hexident+i, "%02x", bbuf[i/2]);
174
175     hexident[identlen] = 0;
176     ident = hexident;
177   }
178
179   if (strlen(ident) > maxidentlen)
180     die("base directory `%s' plus ident `%s' too long"
181         " (with spare) for socket (max ident %d)\n",
182         run_base, ident, maxidentlen);
183
184   r = mkdir(run_base, 0700);
185   if (r && errno==ENOENT && run_base_mkdir_p) {
186     r = mkdir(run_base_mkdir_p, 0700);
187     if (r) diee("mkdir %s (since %s was ENOENT)",run_base_mkdir_p,run_base);
188     r = mkdir(run_base, 0700);
189   }
190   if (r) {
191     if (!(errno == EEXIST))
192       diee("mkdir %s",run_base);
193   }
194
195   socket_path = m_asprintf("%s/s%s",run_base,ident);
196 }  
197
198 // Returns fd
199 int flock_file(const char *lock_path) {
200   int r;
201   int lockfd = -1;
202   struct stat stab_fd;
203   struct stat stab_path;
204
205   for (;;) {
206     if (lockfd >= 0) { close(lockfd); lockfd = -1; }
207
208     lockfd = open(lock_path, O_CREAT|O_RDWR, 0600);
209     if (lockfd<0) diee("create lock (%s)", lock_path);
210
211     r = flock(lockfd, LOCK_EX);
212     if (r && errno == EINTR) continue;
213     if (r) diee("lock lock (%s)", lock_path);
214
215     r = fstat(lockfd, &stab_fd);
216     if (r) diee("fstat locked lock");
217
218     r = stat(lock_path, &stab_path);
219     if (!r) {
220       if (stabs_same_inode(&stab_path, &stab_fd)) break;
221     } else {
222       if (!(errno == ENOENT)) diee("re-stat locked lock (%s)", lock_path);
223     }
224   }
225
226   return lockfd;
227 }
228
229 // Returns fd
230 int acquire_lock(void) {
231   lock_path = m_asprintf("%s/l%s",run_base,ident);
232   return flock_file(lock_path);
233 }
234
235 static void shbang_opts(const char *const **argv_io,
236                         const struct cmdinfo *cmdinfos) {
237   myopt(argv_io, cmdinfos);
238
239   interp = *(*argv_io)++;
240   if (!interp) badusage("need interpreter argument");
241 }
242
243 void process_opts(const char *const **argv_io) {
244   const char *smashedopt;
245
246   sha256_init(&identsc);
247   ident_addinit();
248
249   if ((*argv_io)[0] &&
250       (smashedopt = (*argv_io)[1]) &&
251       smashedopt[0]=='-' &&
252       (strchr(smashedopt,' ') || strchr(smashedopt,','))) {
253     /* single argument containg all the options and <interp> */
254     *argv_io += 2; /* eat argv[0] and smashedopt */
255     const char *split_args[MAX_OPTS+1];
256     int split_argc = 0;
257     split_args[split_argc++] = (*argv_io)[0];
258     for (;;) {
259       if (split_argc >= MAX_OPTS) die("too many options in combined arg");
260       split_args[split_argc++] = smashedopt;
261       if (smashedopt[0] != '-') /* never true on first iteration */
262         break;
263       char *delim = strchr(smashedopt,' ');
264       if (!delim) delim = strchr(smashedopt,',');
265       if (!delim) badusage("combined arg lacks <interpreter>");
266       *delim = 0;
267       smashedopt = delim+1;
268     }
269     assert(split_argc <= MAX_OPTS);
270     split_args[split_argc++] = 0;
271
272     const char *const *split_argv = split_args;
273
274     shbang_opts(&split_argv, cmdinfos);
275     /* sets interp */
276
277     if (!**argv_io)
278       badusage("no script argument (expected after combined #! options)");
279   } else {
280     shbang_opts(argv_io, cmdinfos);
281   }
282
283   if (**argv_io)
284     script = *(*argv_io)++;
285 }