chiark / gitweb /
prefork-interp: New protocol: C: more negotiation-ness etc.
[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
9 static uid_t us;
10 static const char *run_base;
11 static const char *run_base_mkdir_p;
12
13 static const char header_magic[4] = "PFI\n";
14
15 void common_diee(const char *m) { diee("%s", m); }
16 void common_die (const char *m) { die ("%s", m); }
17
18 void vmsgcore(int estatus, int errnoval, const char *fmt, va_list al) {
19   int r;
20
21   if (logging) {
22     const char *fmt_use = fmt;
23     char *fmt_free = 0;
24     if (errnoval!=-1) {
25       r = asprintf(&fmt_free, "%s: %%m", fmt);
26       if (r) {
27         fmt_free = 0;
28       } else {
29         fmt_use = fmt_free;
30       }
31     }
32     vsyslog(LOG_ERR, fmt_use, al);
33     free(fmt_free);
34   } else {
35     fprintf(stderr, "%s: ", our_name);
36     vfprintf(stderr,fmt,al);
37     if (errnoval!=-1) fprintf(stderr,": %s",strerror(errnoval));
38     fputc('\n',stderr);
39   }
40   if (estatus) exit(estatus);
41 }
42
43 void usagemessage(void) { fusagemessage(stderr); }
44
45 void of_help(const struct cmdinfo *ci, const char *val) {
46   fusagemessage(stdout);
47   if (ferror(stdout)) diee("write usage message to stdout");
48   exit(0);
49 }
50
51 void of_iassign(const struct cmdinfo *ci, const char *val) {
52   long v;
53   char *ep;
54   errno= 0; v= strtol(val,&ep,10);
55   if (!*val || *ep || errno || v<INT_MIN || v>INT_MAX)
56     badusage("bad integer argument `%s' for --%s",val,ci->olong);
57   *ci->iassignto = v;
58 }
59
60 void ident_addstring(const struct cmdinfo *ci, const char *string) {
61   /* ci may be 0 and is provided so this can be .call */
62   sha256_update(&identsc,strlen(string)+1,string);
63 }
64
65 void off_ident_addenv(const struct cmdinfo *ci, const char *name) {
66   const char *val = getenv(name);
67   if (val) {
68     sha256_update(&identsc,strlen(name),name); /* no nul */
69     sha256_update(&identsc,1,"=");
70     ident_addstring(0,val);
71   } else {
72     ident_addstring(0,name);
73   }
74 }
75
76 bool find_run_base_var_run(void) {
77   struct stat stab;
78   char *try;
79   int r;
80
81   try = m_asprintf("%s/%lu", "/var/run/user", us);
82   r = lstat(try, &stab);
83   if (r<0) {
84     if (errno == ENOENT ||
85         errno == ENOTDIR ||
86         errno == EACCES ||
87         errno == EPERM)
88       return 0; /* oh well */
89     diee("stat /var/run/user/UID");
90   }
91   if (!S_ISDIR(stab.st_mode)) {
92     warning("%s not a directory, falling back to ~\n", try);
93     return 0;
94   }
95   if (stab.st_uid != us) {
96     warning("%s not owned by uid %lu, falling back to ~\n", try,
97             (unsigned long)us);
98     return 0;
99   }
100   if (stab.st_mode & 0077) {
101     warning("%s writeable by group or other, falling back to ~\n", try);
102     return 0;
103   }
104   run_base = m_asprintf("%s/%s", try, our_name);
105   return 1;
106 }
107
108 static bool find_run_base_home(void) {
109   struct passwd *pw;
110   struct utsname ut;
111   char *dot, *try;
112   int r;
113
114   pw = getpwuid(us);  if (!pw) diee("getpwent(uid)");
115
116   r = uname(&ut);   if (r) diee("uname(2)");
117   dot = strchr(ut.nodename, '.');
118   if (dot) *dot = 0;
119   if (sizeof(ut.nodename) > 32)
120     ut.nodename[32] = 0;
121
122   run_base_mkdir_p = m_asprintf("%s/.%s", pw->pw_dir, our_name);
123   try = m_asprintf("%s/%s", run_base_mkdir_p, ut.nodename);
124   run_base = try;
125   return 1;
126 }
127
128 void find_socket_path(void) {
129   struct sockaddr_un sun;
130   int r;
131
132   us = getuid();  if (us==(uid_t)-1) diee("getuid");
133
134   find_run_base_var_run() ||
135     find_run_base_home() ||
136     (abort(),0);
137
138   int maxidentlen = sizeof(sun.sun_path) - strlen(run_base) - 10 - 2;
139
140   if (!ident) {
141     if (maxidentlen < MINHEXHASH)
142       die("base directory `%s'"
143           " leaves only %d characters for id hash"
144           " which is too little (<%d)",
145           run_base, maxidentlen, MINHEXHASH);
146
147     int identlen = maxidentlen > 64 ? 64 : maxidentlen;
148     char *hexident = xmalloc(identlen + 2);
149     unsigned char bbuf[32];
150     int i;
151
152     ident_addstring(0,interp);
153     if (script)
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 // Returns fd
184 int acquire_lock(void) {
185   int r;
186   int lockfd = -1;
187
188   lock_path = m_asprintf("%s/l%s",run_base,ident);
189
190   lockfd = open(lock_path, O_CREAT|O_RDWR, 0600);
191   if (lockfd<0) diee("create lock (%s)", lock_path);
192
193   r = flock(lockfd, LOCK_EX);
194   if (r) diee("lock lock (%s)", lock_path);
195
196   return lockfd;
197 }
198
199 static void shbang_opts(const char *const **argv_io,
200                         const struct cmdinfo *cmdinfos) {
201   myopt(argv_io, cmdinfos);
202
203   interp = *(*argv_io)++;
204   if (!interp) badusage("need interpreter argument");
205 }
206
207 void process_opts(const char *const **argv_io) {
208   const char *smashedopt;
209
210   sha256_init(&identsc);
211   ident_addinit();
212
213   if ((*argv_io)[0] &&
214       (smashedopt = (*argv_io)[1]) &&
215       smashedopt[0]=='-' &&
216       (strchr(smashedopt,' ') || strchr(smashedopt,','))) {
217     /* single argument containg all the options and <interp> */
218     *argv_io += 2; /* eat argv[0] and smashedopt */
219     const char *split_args[MAX_OPTS+1];
220     int split_argc = 0;
221     split_args[split_argc++] = (*argv_io)[0];
222     for (;;) {
223       if (split_argc >= MAX_OPTS) die("too many options in combined arg");
224       split_args[split_argc++] = smashedopt;
225       if (smashedopt[0] != '-') /* never true on first iteration */
226         break;
227       char *delim = strchr(smashedopt,' ');
228       if (!delim) delim = strchr(smashedopt,',');
229       if (!delim) badusage("combined arg lacks <interpreter>");
230       *delim = 0;
231       smashedopt = delim+1;
232     }
233     assert(split_argc <= MAX_OPTS);
234     split_args[split_argc++] = 0;
235
236     const char *const *split_argv = split_args;
237
238     shbang_opts(&split_argv, cmdinfos);
239     /* sets interp */
240
241     if (!**argv_io)
242       badusage("no script argument (expected after combined #! options)");
243   } else {
244     shbang_opts(argv_io, cmdinfos);
245   }
246
247   if (**argv_io)
248     script = *(*argv_io)++;
249 }