2 * "Interpreter" that you can put in #! like this
3 * #!/usr/bin/cgi-fcgi-interp [<options>] <interpreter>
4 * #!/usr/bin/cgi-fcgi-interp [<options>],<interpreter>
7 * cgi-fcgi-interp.[ch] - C helpers common to the whole of chiark-utils
9 * Copyright 2016 Ian Jackson
10 * Copyright 1982,1986,1993 The Regents of the University of California
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public
23 * License along with this file; if not, consult the Free Software
24 * Foundation's website at www.fsf.org, or the GNU Project website at
27 * See below for a BSD 3-clause notice regarding timespeccmp.
30 * The result is a program which looks, when executed via the #!
31 * line, like a CGI program. But the script inside will be executed
32 * via <interpreter> in an fcgi context.
37 * The real interpreter to use. Eg "perl". Need not
38 * be an absolute path; will be fed to execvp.
41 * Use <ident> rather than hex(sha256(<script>))
42 * as the basename of the leafname of the fcgi rendezvous
43 * socket. If <ident> contains only hex digit characters it
44 * ought to be no more than 32 characters. <ident> should
45 * not contain spaces or commas (see below).
48 * Start <numservers> instances of the program. This
49 * determines the maximum concurrency. (Note that unlike
50 * speedy, the specified number of servers is started
51 * right away.) The default is 4.
54 * Debug mode. Do not actually run program. Instead, print
55 * out what we would do.
57 * <options> and <interpreter> can be put into a single argument
58 * to cgi-fcgi-interp, separated by spaces or commas. <interpreter>
61 * cgi-fcgi-interp automatically expires old sockets, including
62 * ones where the named script is out of date.
66 * Uses one of two directories
67 * /var/run/user/<UID>/cgi-fcgi-interp/
68 * ~/.cgi-fcgi-interp/<node>/
69 * and inside there uses these paths
73 * If -M<ident> is not specified then an initial substricg of the
74 * lowercase hex of the sha256 of the <script> (ie, our argv[1]) is
75 * used. The substring is chosen so that the whole path is 10 bytes
76 * shorter than sizeof(sun_path). But always at least 33 characters.
78 * <node> is truncated at the first `.' and after the first 32
82 * - see if /var/run/user exists
83 * if so, lstat /var/run/user/<UID> and check that
84 * we own it and it's X700; if not, fail
85 * if it's ok then <base> is /var/run/user/<UID>
86 * otherwise, look for and maybe create ~/.cgi-fcgi-interp
87 * (where ~ is HOME or from getpwuid)
88 * and then <base> is ~/.cgi-fcgi-interp/<node>
89 * - calculate pathname (checking <ident> length is OK)
90 * - check for and maybe create <base>
91 * - stat and lstat the <script>
92 * - stat the socket and check its timestamp
93 * if it is too old, rename it to g<inum>.<pid> (where
94 * <inum> and <pid> are in decimal)
95 * and run garbage collection
96 * - run cgi-fcgi -connect SOCKET SCRIPT
109 #include <sys/types.h>
110 #include <sys/stat.h>
111 #include <sys/utsname.h>
112 #include <sys/socket.h>
118 #include <nettle/sha.h>
122 #define die common_die
123 #define diee common_diee
125 #define MINHEXHASH 33
127 static const char *interp, *ident;
128 static int numservers, debugmode;
130 void diee(const char *m) {
131 err(127, "error: %s failed", m);
134 static void fusagemessage(FILE *f) {
135 fprintf(f, "usage: #!/usr/bin/cgi-fcgi-interp [<options>]\n");
138 void usagemessage(void) { fusagemessage(stderr); }
140 static void of_help(const struct cmdinfo *ci, const char *val) {
141 fusagemessage(stdout);
142 if (ferror(stdout)) diee("write usage message to stdout");
146 static void of_iassign(const struct cmdinfo *ci, const char *val) {
149 errno= 0; v= strtol(val,&ep,10);
150 if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
151 badusage("bad integer argument `%s' for --%s",val,ci->olong);
157 static const struct cmdinfo cmdinfos[]= {
158 { "help", 0, .call= of_help },
159 { 0, 'g', 1, .sassignto= &ident },
160 { 0, 'M', 1, .call=of_iassign, .iassignto= &numservers },
161 { 0, 'D', 0, .iassignto= &debugmode, .arg= 1 },
166 static const char *run_base, *script, *socket_path;
168 static bool find_run_base_var_run(void) {
173 try = m_asprintf("%s/%lu", "/var/run/user", us);
174 r = lstat(try, &stab);
176 if (errno == ENOENT ||
180 return 0; /* oh well */
181 diee("stat /var/run/user/UID");
183 if (!S_ISDIR(stab.st_mode)) {
184 warnx("%s not a directory, falling back to ~\n", try);
187 if (stab.st_uid != us) {
188 warnx("%s not owned by uid %lu, falling back to ~\n", try,
192 if (stab.st_mode & 0077) {
193 warnx("%s writeable by group or other, falling back to ~\n", try);
196 run_base = m_asprintf("%s/%s", try, "cgi-fcgi-interp");
200 static bool find_run_base_home(void) {
206 pw = getpwuid(us); if (!pw) diee("getpwent(uid)");
208 r = uname(&ut); if (r) diee("uname(2)");
209 dot = strchr(ut.nodename, '.');
211 if (sizeof(ut.nodename) > 32)
214 try = m_asprintf("%s/%s/%s", pw->pw_dir, ".cgi-fcgi-interp", ut.nodename);
219 static void find_socket_path(void) {
220 struct sockaddr_un sun;
223 us = getuid(); if (us==(uid_t)-1) diee("getuid");
225 find_run_base_var_run() ||
226 find_run_base_home() ||
229 int maxidentlen = sizeof(sun.sun_path) - strlen(run_base) - 10 - 2;
232 if (maxidentlen < MINHEXHASH)
233 errx(127,"base directory `%s'"
234 " leaves only %d characters for id hash"
235 " which is too little (<%d)",
236 run_base, maxidentlen, MINHEXHASH);
238 int identlen = maxidentlen > 64 ? 64 : maxidentlen;
239 char *hexident = xmalloc(identlen + 2);
240 struct sha256_ctx sc;
241 unsigned char bbuf[32];
245 sha256_update(&sc,strlen(interp)+1,interp);
246 sha256_update(&sc,strlen(script)+1,script);
247 sha256_digest(&sc,sizeof(bbuf),bbuf);
249 for (i=0; i<identlen; i += 2)
250 sprintf(hexident+i, "%02x", bbuf[i/2]);
252 hexident[identlen] = 0;
256 if (strlen(ident) > maxidentlen)
257 errx(127, "base directory `%s' plus ident `%s' too long"
258 " (with spare) for socket (max ident %d)\n",
259 run_base, ident, maxidentlen);
261 r = mkdir(run_base, 0700);
263 if (!(errno == EEXIST))
264 err(127,"mkdir %s",run_base);
267 socket_path = m_asprintf("%s/g%s",run_base,ident);
271 * Regarding the macro timespeccmp:
273 * Copyright (c) 1982, 1986, 1993
274 * The Regents of the University of California. All rights reserved.
276 * Redistribution and use in source and binary forms, with or without
277 * modification, are permitted provided that the following conditions
279 * 1. Redistributions of source code must retain the above copyright
280 * notice, this list of conditions and the following disclaimer.
281 * 2. Redistributions in binary form must reproduce the above copyright
282 * notice, this list of conditions and the following disclaimer in the
283 * documentation and/or other materials provided with the distribution.
284 * 4. Neither the name of the University nor the names of its contributors
285 * may be used to endorse or promote products derived from this software
286 * without specific prior written permission.
288 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
289 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
290 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
291 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
292 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
293 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
294 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
295 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
296 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
297 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
300 * @(#)time.h 8.5 (Berkeley) 5/4/95
301 * $FreeBSD: head/sys/sys/time.h 275985 2014-12-21 05:07:11Z imp $
304 #define timespeccmp(tvp, uvp, cmp) \
305 (((tvp)->tv_sec == (uvp)->tv_sec) ? \
306 ((tvp)->tv_nsec cmp (uvp)->tv_nsec) : \
307 ((tvp)->tv_sec cmp (uvp)->tv_sec))
308 #endif /*timespeccmp*/
310 static bool stab_isnewer(const struct stat *a, const struct stat *b) {
314 static bool check_garbage(void) {
315 struct stat sock_stab, script_stab;
318 r = lstat(script, &script_stab);
319 if (r) err(127,"lstat script (%s)",script);
321 r = lstat(socket_path, &sock_stab);
323 if ((errno == ENOENT))
324 return 0; /* well, no garbage then */
325 err(127,"stat socket (%s)",socket_path);
328 if (stab_isnewer(&script_stab, &sock_stab))
331 if (S_ISLNK(script_stab.st_mode)) {
332 r = stat(script, &script_stab);
333 if (r) err(127,"stat script (%s0",script);
335 if (stab_isnewer(&script_stab, &sock_stab))
342 static void shbang_opts(const char *const **argv_io,
343 const struct cmdinfo *cmdinfos) {
344 myopt(argv_io, cmdinfos);
346 interp = *(*argv_io)++;
347 if (!interp) errx(127,"need interpreter argument");
350 int main(int argc, const char *const *argv) {
351 const char *smashedopt;
354 (smashedopt = argv[1]) &&
355 smashedopt[0]=='-' &&
356 (strchr(smashedopt,' ') || strchr(smashedopt,','))) {
357 /* single argument containg all the options and <interp> */
358 argv += 2; /* eat argv[0] and smashedopt */
359 const char *split_args[MAX_OPTS+1];
361 split_args[split_argc++] = argv[0];
363 if (split_argc >= MAX_OPTS) errx(127,"too many options in combined arg");
364 split_args[split_argc++] = smashedopt;
365 if (smashedopt[0] != '-') /* never true on first iteration */
367 char *delim = strchr(smashedopt,' ');
368 if (!delim) delim = strchr(smashedopt,',');
370 errx(127,"combined arg lacks <interpreter>");
372 smashedopt = delim+1;
374 assert(split_argc <= MAX_OPTS);
375 split_args[split_argc++] = 0;
377 const char *const *split_argv = split_args;
379 shbang_opts(&split_argv, cmdinfos);
381 if (!split_argv) errx(127,"combined arg too many non-option arguments");
383 shbang_opts(&argv, cmdinfos);
387 if (!script) errx(127,"need script argument");
388 if (*argv) errx(127,"too many arguments");
395 printf("socket: %s\n",socket_path);
396 printf("interp: %s\n",interp);
397 printf("script: %s\n",script);