1 /*
2 ** Copyright 2007 Double Precision, Inc.
3 ** See COPYING for distribution information.
4 */
5 
6 /*
7 */
8 #include	"cgi.h"
9 #include	<stdio.h>
10 #include	<stdlib.h>
11 #include	<string.h>
12 
13 #if	HAVE_UNISTD_H
14 #include	<unistd.h>
15 #endif
16 
17 #if	TIME_WITH_SYS_TIME
18 #include	<sys/time.h>
19 #include	<time.h>
20 #else
21 #if	HAVE_SYS_TIME_H
22 #include	<sys/time.h>
23 #else
24 #include	<time.h>
25 #endif
26 #endif
27 #if HAVE_SYS_WAIT_H
28 #include	<sys/wait.h>
29 #endif
30 #include	<errno.h>
31 #include	<fcntl.h>
32 #include	<sys/types.h>
33 #include	<sys/stat.h>
34 #include        <sys/socket.h>
35 #include	<sys/uio.h>
36 #include        <sys/un.h>
37 
38 static int read_environ(int);
39 
40 static int start_daemon(const char *lockfilename);
41 
42 static void run_daemon(int fd, int termfd, int connfd, void (*handler)(void *),
43 		       void *dummy);
44 
45 static void run_prefork(int fd, size_t ndaemons, void (*handler)(void *),
46 			void *dummy);
47 
cgi_daemon(int nprocs,const char * lockfile,void (* postinit)(void *),void (* handler)(void *),void * dummy)48 void cgi_daemon(int nprocs, const char *lockfile,
49 		void (*postinit)(void *), void (*handler)(void *),
50 		void *dummy)
51 {
52 	int fd=start_daemon(lockfile);
53 
54 	if (postinit)
55 		(*postinit)(dummy);
56 
57 	if (nprocs > 0)
58 		run_prefork(fd, nprocs, handler, dummy);
59 	else
60 		run_daemon(fd, -1, -1, handler, dummy);
61 }
62 
63 /* Start in daemon mode.  Return listening socket file descriptor */
64 
start_daemon(const char * lockfile)65 static int start_daemon(const char *lockfile)
66 {
67 	int     fd;
68 	struct  sockaddr_un skun;
69 
70 	unlink(lockfile);
71 
72 	fd=socket(PF_UNIX, SOCK_STREAM, 0);
73         if (fd < 0)
74 	{
75 		perror("socket");
76 		return (-1);
77 	}
78 
79         skun.sun_family=AF_UNIX;
80         strcpy(skun.sun_path, lockfile);
81         if (bind(fd, (const struct sockaddr *)&skun, sizeof(skun)) ||
82                 listen(fd, SOMAXCONN) ||
83                 chmod(skun.sun_path, 0777) ||
84                 fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
85         {
86                 perror(lockfile);
87                 close(fd);
88                 return (-1);
89         }
90 	return fd;
91 }
92 
93 static int prefork(int listenfd, int *allpipes, size_t ndaemos,
94 		   int *termpipe, void (*handler)(void *), void *dummy);
95 
run_prefork(int fd,size_t ndaemons,void (* handler)(void *),void * dummy)96 static void run_prefork(int fd, size_t ndaemons, void (*handler)(void *),
97 			void *dummy)
98 {
99 	int *cpipes; /* Completion pipes from preforked processes */
100 	int termpipe[2]; /* Termination pipe to preforked processes */
101 	size_t i;
102 
103 	if ((cpipes=malloc(sizeof(int)*ndaemons)) == NULL)
104 	{
105 		fprintf(stderr,
106 		       "CRIT: malloc failed: %s\n",
107 		       strerror(errno));
108 		exit(1);
109 	}
110 
111 	if (pipe(termpipe) < 0)
112 	{
113 		fprintf(stderr,
114 			"CRIT: pipe failed: %s\n",
115 			strerror(errno));
116 		exit(1);
117 	}
118 
119 
120 	/* Start the initial set of preforked daemons */
121 
122 	for (i=0; i<ndaemons; i++)
123 		cpipes[i]= -1;
124 
125 	for (i=0; i<ndaemons; i++)
126 		cpipes[i]=prefork(fd, cpipes, ndaemons, termpipe, handler,
127 				  dummy);
128 
129 
130 	for (;;)
131 	{
132 		fd_set	fdr;
133 		int	maxfd=0;
134 
135 		FD_ZERO(&fdr);
136 
137 		for (i=0; i<ndaemons; i++)
138 		{
139 			if (cpipes[i] >= maxfd)
140 				maxfd=cpipes[i]+1;
141 
142 			FD_SET(cpipes[i], &fdr);
143 		}
144 
145 		if (select(maxfd, &fdr, NULL, NULL, NULL) <= 0)
146 			continue;
147 
148 		/*
149 		** When child process gets a connection, it closes its
150 		** completion pipe, which makes the pipe selectable for
151 		** read.
152 		*/
153 
154 		for (i=0; i<ndaemons; i++)
155 		{
156 			if (FD_ISSET(cpipes[i], &fdr))
157 			{
158 				close(cpipes[i]);
159 				cpipes[i]= -1;
160 				cpipes[i]=prefork(fd, cpipes,
161 						  ndaemons, termpipe, handler,
162 						  dummy);
163 			}
164 		}
165 	}
166 }
167 
168 /* Start a preforked process */
169 
prefork(int listenfd,int * allpipes,size_t ndaemons,int * termpipe,void (* handler)(void *),void * dummy)170 static int prefork(int listenfd, int *allpipes, size_t ndaemons,
171 		   int *termpipe, void (*handler)(void *), void *dummy)
172 {
173 	int newpipe[2];
174 	pid_t p;
175 	int waitstat;
176 	size_t i;
177 
178 	if (pipe(newpipe) < 0)
179 	{
180 		fprintf(stderr,
181 			"CRIT: pipe failed: %s\n", strerror(errno));
182 		exit(1);
183 	}
184 
185 	while ((p=fork()) < 0)
186 	{
187 		fprintf(stderr,
188 			"CRIT: fork failed: %s\n", strerror(errno));
189 		sleep(5);
190 	}
191 
192 	if (p) /* parent */
193 	{
194 		close(newpipe[1]);
195 
196 		/* Wait for first child process to go away */
197 
198 		while (wait(&waitstat) != p)
199 			;
200 
201 		return (newpipe[0]);
202 	}
203 
204 	close(newpipe[0]);
205 	close(termpipe[1]);
206 
207 	/* New child doesn't need pipes from other children */
208 
209 	for (i=0; i<ndaemons; i++)
210 		if (allpipes[i] >= 0)
211 			close(allpipes[i]);
212 
213 	/* Fork once more, so that the parent process can continue */
214 
215 	if (fork())
216 		exit(0);
217 
218 	run_daemon(listenfd, termpipe[0], newpipe[1], handler, dummy);
219 	return (-1);
220 }
221 
run_daemon(int fd,int termfd,int acceptedfd,void (* handler)(void *),void * dummy)222 static void run_daemon(int fd, int termfd, int acceptedfd,
223 		       void (*handler)(void *), void *dummy)
224 {
225 	int cfd;
226 
227 	for (;;)
228 	{
229 		fd_set	fdr;
230 		pid_t p;
231 		int maxfd;
232 
233 		FD_ZERO(&fdr);
234 
235 		FD_SET(fd, &fdr);
236 
237 		maxfd=fd;
238 
239 		if (termfd >= 0)
240 		{
241 			if (termfd > maxfd)
242 				maxfd=termfd;
243 
244 			FD_SET(termfd, &fdr);
245 		}
246 
247 		if (select(maxfd+1, &fdr, NULL, NULL, NULL) <= 0)
248 			continue;
249 		if (termfd >= 0 &&
250 		    FD_ISSET(termfd, &fdr)) /* Terminate all child procs */
251 			exit(0);
252 
253 		if (!FD_ISSET(fd, &fdr))
254 			continue;
255 
256 		cfd=accept(fd, NULL, 0);
257 
258 		if (cfd < 0)
259 			continue;
260 
261 		if (acceptedfd >= 0) /* preforked daemon */
262 		{
263 			if (termfd >= 0)
264 				close(termfd);
265 			close(acceptedfd);
266 			break;
267 		}
268 
269 		p=fork();
270 
271 		if (p < 0)
272 		{
273 			fprintf(stderr,
274 			       "CRIT: fork() failed: %s\n", strerror(errno));
275 			continue;
276 		}
277 
278 		if (p)
279 		{
280 			int dummy;
281 
282 			close(cfd);
283 			while (wait(&dummy) != p)
284 				continue;
285 			continue;
286 		}
287 
288 		/* Child forks once more, parent exits */
289 
290 		if (fork())
291 			exit(0);
292 
293 		break;
294 
295 	}
296 
297 	/* child */
298 
299 	if (fcntl(cfd, F_SETFL, 0) < 0)
300 	{
301 		fprintf(stderr,
302 		       "CRIT: fcntl(): %s\n", strerror(errno));
303 		exit(0);
304 	}
305 
306 	close(fd);
307 	if (read_environ(cfd))
308 	{
309 		close(0);
310 		close(1);
311 		if (dup(cfd) != 0 || dup(cfd) != 1)
312 		{
313 			fprintf(stderr,
314 			       "CRIT: dup() did not work as expected\n");
315 			exit(0);
316 		}
317 		close(cfd);
318 	}
319 
320 	if (fcntl(0, F_SETFL, 0) < 0 ||
321 	    fcntl(1, F_SETFL, 0) < 0)
322 	{
323 		fprintf(stderr,
324 		       "CRIT: fcntl() failed: %s\n", strerror(errno));
325 		exit(0);
326 	}
327 
328 	(*handler)(dummy);
329 	exit(0);
330 }
331 
332 
333 /* Read environment from the sqwebmail stub */
334 
force_read(int cfd,char * p,size_t l)335 static void force_read(int cfd, char *p, size_t l)
336 {
337 	int m;
338 
339 	while (l)
340 	{
341 		m=read(cfd, p, l);
342 		if (m <= 0)
343 		{
344 			fprintf(stderr,
345 			       "WARN: socket closed while reading"
346 			       " environment.\n");
347 			exit(0);
348 		}
349 
350 		p += m;
351 		l -= m;
352 	}
353 }
354 
355 /* Receive CGI environment */
356 
read_environ(int cfd)357 static int read_environ(int cfd)
358 {
359 	static char buf[SOCKENVIRONLEN];
360 	size_t l;
361 	char *p;
362 	int passfd;
363 
364 	force_read(cfd, buf, 1+sizeof(l));
365 
366 	memcpy(&l, buf, sizeof(l));
367 	passfd=buf[sizeof(l)];
368 
369 	if (l >= sizeof(buf))
370 	{
371 		fprintf(stderr,
372 		       "WARN: invalid environment received via socket.\n");
373 		exit(0);
374 	}
375 
376 	alarm(10); /* Just in case - punt */
377 	force_read(cfd, buf, l);
378 	buf[l]=0;
379 	alarm(0);
380 
381 	/* Vet environment strings for only known good strings */
382 
383 	p=buf;
384 	while (p < buf+l)
385 	{
386 		if (strchr(p, '=') == NULL ||
387 		    !VALIDCGIVAR(p))
388 		{
389 			fprintf(stderr,
390 			       "WARN: invalid environment received"
391 			       " via socket: %s\n" , p);
392 			exit(0);
393 		}
394 
395 		putenv(p);
396 
397 		while (*p++)
398 			;
399 	}
400 
401 	/* Receive file descriptors, if supported by the platform */
402 
403 #if	CGI_PASSFD
404 
405 	if (passfd)
406 	{
407 		struct iovec iov;
408 		char dummy;
409 
410 #if CGI_PASSFD_MSGACCRIGHTS
411 
412 		int fdbuf[2];
413 		struct msghdr msg;
414 #endif
415 
416 #if CGI_PASSFD_MSGCONTROL
417 
418 		int fdbuf[2];
419 		struct msghdr msg;
420 		struct cmsghdr *cmsg;
421 		char buf[CMSG_SPACE(sizeof(fdbuf))];
422 #endif
423 
424 #if CGI_PASSFD_MSGACCRIGHTS
425 		memset(&iov, 0, sizeof(iov));
426 		msg.msg_accrights=(caddr_t)fdbuf;
427 		msg.msg_accrightslen=sizeof(fdbuf);
428 #endif
429 
430 
431 #if CGI_PASSFD_MSGCONTROL
432 		memset(&msg, 0, sizeof(msg));
433 		msg.msg_control=buf;
434 		msg.msg_controllen=sizeof(buf);
435 #endif
436 
437 		msg.msg_iov=&iov;
438 		msg.msg_iovlen=1;
439 
440 		iov.iov_base=&dummy;
441 		iov.iov_len=1;
442 
443 		if (recvmsg(cfd, &msg, 0) <= 0)
444 		{
445 			perror("Internal error - recvmsg() failed");
446 			exit(0);
447 		}
448 
449 #if CGI_PASSFD_MSGACCRIGHTS
450 
451 		if (msg.msg_accrightslen < sizeof(fdbuf))
452 		{
453 			perror("Internal error - malformed recvmsg()");
454 			exit(0);
455 		}
456 
457 #endif
458 
459 #if CGI_PASSFD_MSGCONTROL
460 		if (msg.msg_controllen < sizeof(buf) ||
461 		    (cmsg = CMSG_FIRSTHDR(&msg))->cmsg_level != SOL_SOCKET ||
462 		    cmsg->cmsg_type != SCM_RIGHTS ||
463 		    cmsg->cmsg_len != CMSG_LEN(sizeof(fdbuf)))
464 		{
465 			perror("Internal error - malformed recvmsg()");
466 			exit(0);
467 		}
468 
469 		memcpy(fdbuf, CMSG_DATA(cmsg), sizeof(fdbuf));
470 #endif
471 		close(0);
472 		close(1);
473 		if (dup(fdbuf[0]) != 0 || dup(fdbuf[1]) != 1)
474 			fprintf(stderr,
475 			       "CRIT: dup() did not work as expected in"
476 			       " read_environ()\n");
477 		close(fdbuf[0]);
478 		close(fdbuf[1]);
479 		return 0;
480 	}
481 #endif
482 
483 	return 1;
484 }
485