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