aboutsummaryrefslogblamecommitdiffstats
path: root/slowcgi.c
blob: ce1530cc4614aaf46705073e98268993405ad75a (plain) (tree)
1
                                                                  


















                                                                           
                       
                     
                     

                     
                      
                
                  

                  
                   

                   
                   




                   
                
 
                           

                  





                                             







                                                                







                                        
                                    
                                              
                                      





                                                                               



                                                          





















                                                   

                                 
                     





















                                              
                                                               












                                                       
                
                                              







                                                              
                                                   
                                           
                                                              






                                                        


                                                         

                                                        
                                                               

  
                                  

                     
                                         
                                           







                                    
                              



                                        
 
                            
                                                        
                                                   
                                                                                


                                                     
                                                                               



                                                                          
                              







                                                                              

                                                 

                                                     



                                                          
                                                  
 
                











                                                       










                                   













                                                                 






















                                               
                       
                                                                           
                                       





                                                         
                                    
                               
                                                                   



                            
                                
                                  
                            

                               
                                           
                                                  
                                                     
                                
















                                                                        
 
                                                                

                            
                                
                              


                                            


                                             





                                                                             


                                           


                                              


                                  








                                                
                                         

                                 



                                                                    
 

                                           
                       
                                                  
 
                                             
 
                                                 

                                    
                                                     
 

                                        
 






                                                  
 




                                                            
                  

                                                             
      
 
                                          








                                                                       


                                                                          
                                 

                                                   



                         

   
                                             
 
                                     
                                           

                                    

                                                                             



                                                  

                                                                
                                                 




                                                                   
                                                                   





                                                                 
                                                      

                                                           


                                  
                                   
                  








                                               

                                                                     
                              


                                        
                                                              
                                                



                               

                                                                                





                                                                 




                                               
                                         
                                   



                                     

                            


                         
                                                           
                                                      







                                          
                                                         





                                          

                                  
                                               
                         
                               

                       



                               
                                                                           
                                      




                                                                       
                                                                              

                                                 
                                                           




                                                
                                               




                                                    
                                   
                                   



                                        


                      
                                                                   

                                                            
                                              


                                                                               
                         
 



                                                                       
 


                                                                           
 

                                                           

                                                 
                      

                                                      
                      



         

                                                                   














                                                                         




                                                          

                                                 
                                           

                                                
                                           




                                                                 
                              
                                                          

                                                                           
                              
                                                              
                                       
                                           



                                    
                                          







                                                                     
                                           







                                                
                           

                                

                
 
                                                      
                                                      
 

















                                            
                        
 





                                                                   
            
                                                                          



                                               
                                          
                             
                                                               



                               
                           


    
                                                                             
 
                                       





                                                                  
                                                 




                                                            




                            
 
    
                                                                      













                                                                           



                                                        



                            
 






                                          

                                                                            












                                                 


                                                                            




                                                 


                               















                                                                               

                                

                                                
                                                                


                                                                       
                                                                       
                                              
                                                            
                                                       


                                               


                                                                     

                                                             
                                                                    




                               
                                                                     


















                                                                    

                                                     


      
                                                       
 
                                           





                                                  
                      
                                        









                                                                         
                                                                            


                                                            
                                                                     


                                                            
                                                                    



                                                            
                      





                                                                         





                                                                     
    
                           

                                   
                                                        


                                 
                              


              


                                                                     


                                                                     

                                      
                                           


                               

                                         
                              













                                                                            


                                   
                  

                                                           
      

                                
                                
 
                                                      
                                 
                                                        

                                                        
                                 




                                

                                         

                                                    








                                                                   

                 







                                                                            
                                                   
                         

         
 
                           

                        
                        
 

                                             

                                             


                                                 




                                                 




                                                                      






                                                                    
                                    


                                                
                                                     
 
                                                                       







                                                         
                                    

                                

                                                                    

                                                             


                                     
                           
                                                               
                                              
                                      


    
                                                                    


                                                
                                           
 
                                                                       










                                                                    

                               



                                
                                   







                                                                          
                                      








                                                                   
                                             


                              



                                                





                                              
                                





                                                     
                                





                                                         
                                   
                                      
                                   




                                                                    
                                  
                                               


                                                                           
                              
                                                              





                                                       
                                          



                                                                  
                                       


    
                                  



                                            






                                               

                                                       


                                                   

                                                           


                                                     

                                                             

















                                                                
                              

                                        


                
    





                                                             
                                                               

                                             
                                                             


    
                                                                    









                                                                  













                                                                              











                                                                         
           




                                           
                                                   



                    
           




                                            
                                   









                                 
                                                  








                                  
                                  















                                   





                                            
 
/*	$OpenBSD: slowcgi.c,v 1.64 2022/08/07 07:43:53 op Exp $ */
/*
 * Copyright (c) 2013 David Gwynne <dlg@openbsd.org>
 * Copyright (c) 2013 Florian Obser <florian@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <err.h>
#include <fcntl.h>
#include <errno.h>
#include <event.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <grp.h>

#include "getdtablecount.h"
#include "queue.h"

#ifdef strlcpy
#define HAVE_STRLCPY
#else
size_t strlcpy(char *, const char *, size_t);
#endif

#ifdef strtonum
#define HAVE_STRTONUM
#else
long long
strtonum(const char *numstr, long long minval, long long maxval,
    const char **errstrp);
#endif

#ifndef __packed
#define __packed __attribute__((packed))
#endif

#ifndef __dead
#define __dead __attribute__((noreturn))
#endif

#define TIMEOUT_DEFAULT		 120
#define TIMEOUT_MAX		 (86400 * 365)
#define SLOWCGI_USER		 "www"

#define FCGI_CONTENT_SIZE	 65535
#define FCGI_PADDING_SIZE	 255
#define FCGI_RECORD_SIZE	 \
    (sizeof(struct fcgi_record_header) + FCGI_CONTENT_SIZE + FCGI_PADDING_SIZE)

#define FCGI_ALIGNMENT		 8
#define FCGI_ALIGN(n)		 \
    (((n) + (FCGI_ALIGNMENT - 1)) & ~(FCGI_ALIGNMENT - 1))

#define STDOUT_DONE		 1
#define STDERR_DONE		 2
#define SCRIPT_DONE		 4

#define FCGI_BEGIN_REQUEST	 1
#define FCGI_ABORT_REQUEST	 2
#define FCGI_END_REQUEST	 3
#define FCGI_PARAMS		 4
#define FCGI_STDIN		 5
#define FCGI_STDOUT		 6
#define FCGI_STDERR		 7
#define FCGI_DATA		 8
#define FCGI_GET_VALUES		 9
#define FCGI_GET_VALUES_RESULT	10
#define FCGI_UNKNOWN_TYPE	11
#define FCGI_MAXTYPE		(FCGI_UNKNOWN_TYPE)

#define FCGI_REQUEST_COMPLETE	0
#define FCGI_CANT_MPX_CONN	1
#define FCGI_OVERLOADED		2
#define FCGI_UNKNOWN_ROLE	3

#define FD_RESERVE		5
#define FD_NEEDED		6
int cgi_inflight = 0;

struct listener {
	struct event	ev, pause;
};

struct env_val {
	SLIST_ENTRY(env_val)	 entry;
	char			*val;
};
SLIST_HEAD(env_head, env_val);

struct fcgi_record_header {
	uint8_t		version;
	uint8_t		type;
	uint16_t	id;
	uint16_t	content_len;
	uint8_t		padding_len;
	uint8_t		reserved;
}__packed;

struct fcgi_response {
	TAILQ_ENTRY(fcgi_response)	entry;
	uint8_t				data[FCGI_RECORD_SIZE];
	size_t				data_pos;
	size_t				data_len;
};
TAILQ_HEAD(fcgi_response_head, fcgi_response);

struct fcgi_stdin {
	TAILQ_ENTRY(fcgi_stdin)	entry;
	uint8_t			data[FCGI_RECORD_SIZE];
	size_t			data_pos;
	size_t			data_len;
};
TAILQ_HEAD(fcgi_stdin_head, fcgi_stdin);

struct request {
	LIST_ENTRY(request)		entry;
	struct event			ev;
	struct event			resp_ev;
	struct event			tmo;
	int				fd;
	uint8_t				buf[FCGI_RECORD_SIZE];
	size_t				buf_pos;
	size_t				buf_len;
	struct fcgi_response_head	response_head;
	struct fcgi_stdin_head		stdin_head;
	uint16_t			id;
	char				script_name[PATH_MAX];
	struct env_head			env;
	int				env_count;
	pid_t				script_pid;
	int				script_status;
	struct event			script_ev;
	struct event			script_err_ev;
	struct event			script_stdin_ev;
	int				stdin_fd_closed;
	int				stdout_fd_closed;
	int				stderr_fd_closed;
	uint8_t				script_flags;
	uint8_t				request_started;
	int				inflight_fds_accounted;
};

LIST_HEAD(requests_head, request);

struct slowcgi_proc {
	struct requests_head	requests;
	struct event		ev_sigchld;
};

struct fcgi_begin_request_body {
	uint16_t	role;
	uint8_t		flags;
	uint8_t		reserved[5];
}__packed;

struct fcgi_end_request_body {
	uint32_t	app_status;
	uint8_t		protocol_status;
	uint8_t		reserved[3];
}__packed;

__dead void	usage(void);
int		slowcgi_listen(char *, struct passwd *);
void		slowcgi_paused(int, short, void *);
int		accept_reserve(int, struct sockaddr *, socklen_t *, int, int *);
void		slowcgi_accept(int, short, void *);
void		slowcgi_request(int, short, void *);
void		slowcgi_response(int, short, void *);
void		slowcgi_add_response(struct request *, struct fcgi_response *);
void		slowcgi_timeout(int, short, void *);
void		slowcgi_sig_handler(int, short, void *);
size_t		parse_record(uint8_t * , size_t, struct request *);
void		parse_begin_request(uint8_t *, uint16_t, struct request *,
		    uint16_t);
void		parse_params(uint8_t *, uint16_t, struct request *, uint16_t);
void		parse_stdin(uint8_t *, uint16_t, struct request *, uint16_t);
void		exec_cgi(struct request *);
void		script_in(int, struct event *, struct request *, uint8_t);
void		script_std_in(int, short, void *);
void		script_err_in(int, short, void *);
void		script_out(int, short, void *);
void		create_end_record(struct request *);
void		dump_fcgi_record(const char *,
		    struct fcgi_record_header *);
void		dump_fcgi_record_header(const char *,
		    struct fcgi_record_header *);
void		dump_fcgi_begin_request_body(const char *,
		    struct fcgi_begin_request_body *);
void		dump_fcgi_end_request_body(const char *,
		    struct fcgi_end_request_body *);
void		cleanup_request(struct request *);

struct loggers {
	__dead void (*err)(int, const char *, ...)
	    __attribute__((__format__ (printf, 2, 3)));
	__dead void (*errx)(int, const char *, ...)
	    __attribute__((__format__ (printf, 2, 3)));
	void (*warn)(const char *, ...)
	    __attribute__((__format__ (printf, 1, 2)));
	void (*warnx)(const char *, ...)
	    __attribute__((__format__ (printf, 1, 2)));
	void (*info)(const char *, ...)
	    __attribute__((__format__ (printf, 1, 2)));
	void (*debug)(const char *, ...)
	    __attribute__((__format__ (printf, 1, 2)));
};

const struct loggers conslogger = {
	err,
	errx,
	warn,
	warnx,
	warnx, /* info */
	warnx /* debug */
};

__dead void	syslog_err(int, const char *, ...)
		    __attribute__((__format__ (printf, 2, 3)));
__dead void	syslog_errx(int, const char *, ...)
		    __attribute__((__format__ (printf, 2, 3)));
void		syslog_warn(const char *, ...)
		    __attribute__((__format__ (printf, 1, 2)));
void		syslog_warnx(const char *, ...)
		    __attribute__((__format__ (printf, 1, 2)));
void		syslog_info(const char *, ...)
		    __attribute__((__format__ (printf, 1, 2)));
void		syslog_debug(const char *, ...)
		    __attribute__((__format__ (printf, 1, 2)));
void		syslog_vstrerror(int, int, const char *, va_list)
		    __attribute__((__format__ (printf, 3, 0)));

const struct loggers syslogger = {
	syslog_err,
	syslog_errx,
	syslog_warn,
	syslog_warnx,
	syslog_info,
	syslog_debug
};

const struct loggers *logger = &conslogger;

#define lerr(_e, _f...) logger->err((_e), _f)
#define lerrx(_e, _f...) logger->errx((_e), _f)
#define lwarn(_f...) logger->warn(_f)
#define lwarnx(_f...) logger->warnx(_f)
#define linfo(_f...) logger->info(_f)
#define ldebug(_f...) logger->debug(_f)

__dead void
usage(void)
{
	extern char *__progname;
	fprintf(stderr,
	    "usage: %s [-dv] [-p path] [-s socket] [-t timeout] [-U user] "
	    "[-u user]\n", __progname);
	exit(1);
}

struct timeval		timeout = { TIMEOUT_DEFAULT, 0 };
struct slowcgi_proc	slowcgi_proc;
int			debug = 0;
int			verbose = 0;
int			on = 1;
char			*fcgi_socket = "/var/www/run/slowcgi.sock";

int
main(int argc, char *argv[])
{
	extern char *__progname;
	struct listener	*l = NULL;
	struct passwd	*pw;
	struct stat	 sb;
	int		 c, fd;
	const char	*chrootpath = NULL;
	const char	*sock_user = SLOWCGI_USER;
	const char	*slowcgi_user = SLOWCGI_USER;
	const char	*errstr;

	/*
	 * Ensure we have fds 0-2 open so that we have no fd overlaps
	 * in exec_cgi() later. Just exit on error, we don't have enough
	 * fds open to output an error message anywhere.
	 */
	for (c=0; c < 3; c++) {
		if (fstat(c, &sb) == -1) {
			if ((fd = open("/dev/null", O_RDWR)) != -1) {
				if (dup2(fd, c) == -1)
					exit(1);
				if (fd > c)
					close(fd);
			} else
				exit(1);
		}
	}

	while ((c = getopt(argc, argv, "dp:s:t:U:u:v")) != -1) {
		switch (c) {
		case 'd':
			debug++;
			break;
		case 'p':
			chrootpath = optarg;
			break;
		case 's':
			fcgi_socket = optarg;
			break;
		case 't':
			timeout.tv_sec = strtonum(optarg, 1, TIMEOUT_MAX, 
			    &errstr);
			if (errstr != NULL)
				errx(1, "timeout is %s: %s", errstr, optarg);
			break;
		case 'U':
			sock_user = optarg;
			break;
		case 'u':
			slowcgi_user = optarg;
			break;
		case 'v':
			verbose++;
			break;
		default:
			usage();
			/* NOTREACHED */
		}
	}

	if (geteuid() != 0)
		errx(1, "need root privileges");

	if (!debug && daemon(0, 0) == -1)
		err(1, "daemon");

	if (!debug) {
		openlog(__progname, LOG_PID|LOG_NDELAY, LOG_DAEMON);
		logger = &syslogger;
	}

	ldebug("sock_user: %s", sock_user);
	pw = getpwnam(sock_user);
	if (pw == NULL)
		lerrx(1, "no %s user", sock_user);

	fd = slowcgi_listen(fcgi_socket, pw);

	ldebug("slowcgi_user: %s", slowcgi_user);
	pw = getpwnam(slowcgi_user);
	if (pw == NULL)
		lerrx(1, "no %s user", slowcgi_user);

	if (chrootpath == NULL)
		chrootpath = pw->pw_dir;

	if (chroot(chrootpath) == -1)
		lerr(1, "chroot(%s)", chrootpath);

	ldebug("chroot: %s", chrootpath);

	if (chdir("/") == -1)
		lerr(1, "chdir(/)");

	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
		lerr(1, "unable to revoke privs");

#ifdef __OpenBSD__
	if (pledge("stdio rpath unix proc exec", NULL) == -1)
		lerr(1, "pledge");
#endif

	LIST_INIT(&slowcgi_proc.requests);
	event_init();

	l = calloc(1, sizeof(*l));
	if (l == NULL)
		lerr(1, "listener ev alloc");

	event_set(&l->ev, fd, EV_READ | EV_PERSIST, slowcgi_accept, l);
	event_add(&l->ev, NULL);
	evtimer_set(&l->pause, slowcgi_paused, l);

	signal_set(&slowcgi_proc.ev_sigchld, SIGCHLD, slowcgi_sig_handler,
	    &slowcgi_proc);
	signal(SIGPIPE, SIG_IGN);

	signal_add(&slowcgi_proc.ev_sigchld, NULL);

	event_dispatch();
	return (0);
}

int
slowcgi_listen(char *path, struct passwd *pw)
{
	struct sockaddr_un	 sun;
	mode_t			 old_umask;
	int			 fd;

	if ((fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,
	    0)) == -1)
		lerr(1, "slowcgi_listen: socket");

	bzero(&sun, sizeof(sun));
	sun.sun_family = AF_UNIX;
	if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
	    sizeof(sun.sun_path))
		lerrx(1, "socket path too long");

	if (unlink(path) == -1)
		if (errno != ENOENT)
			lerr(1, "slowcgi_listen: unlink %s", path);

	old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);

	if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1)
		lerr(1,"slowcgi_listen: bind: %s", path);

	umask(old_umask);

	if (chown(path, pw->pw_uid, pw->pw_gid) == -1)
		lerr(1, "slowcgi_listen: chown: %s", path);

	if (listen(fd, 5) == -1)
		lerr(1, "listen");

	ldebug("socket: %s", path);
	return fd;
}

void
slowcgi_paused(int fd, short events, void *arg)
{
	struct listener	*l = arg;
	event_add(&l->ev, NULL);
}

int
accept_reserve(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
    int reserve, int *counter)
{
	int ret;
	if (getdtablecount() + reserve +
	    ((*counter + 1) * FD_NEEDED) >= getdtablesize()) {
		ldebug("inflight fds exceeded");
		errno = EMFILE;
		return -1;
	}

	if ((ret = accept4(sockfd, addr, addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC))
	    > -1) {
		(*counter)++;
		ldebug("inflight incremented, now %d", *counter);
	}
	return ret;
}

void
slowcgi_accept(int fd, short events, void *arg)
{
	struct listener		*l;
	struct sockaddr_storage	 ss;
	struct timeval		 backoff;
	struct request		*c;
	socklen_t		 len;
	int			 s;

	l = arg;
	backoff.tv_sec = 1;
	backoff.tv_usec = 0;
	c = NULL;

	len = sizeof(ss);
	if ((s = accept_reserve(fd, (struct sockaddr *)&ss,
	    &len, FD_RESERVE, &cgi_inflight)) == -1) {
		switch (errno) {
		case EINTR:
		case EWOULDBLOCK:
		case ECONNABORTED:
			return;
		case EMFILE:
		case ENFILE:
			event_del(&l->ev);
			evtimer_add(&l->pause, &backoff);
			return;
		default:
			lerr(1, "accept");
		}
	}

	c = calloc(1, sizeof(*c));
	if (c == NULL) {
		lwarn("cannot calloc request");
		close(s);
		cgi_inflight--;
		return;
	}
	c->fd = s;
	c->buf_pos = 0;
	c->buf_len = 0;
	c->request_started = 0;
	c->stdin_fd_closed = c->stdout_fd_closed = c->stderr_fd_closed = 0;
	c->inflight_fds_accounted = 0;
	TAILQ_INIT(&c->response_head);
	TAILQ_INIT(&c->stdin_head);

	event_set(&c->ev, s, EV_READ | EV_PERSIST, slowcgi_request, c);
	event_add(&c->ev, NULL);
	event_set(&c->resp_ev, s, EV_WRITE | EV_PERSIST, slowcgi_response, c);
	evtimer_set(&c->tmo, slowcgi_timeout, c);
	evtimer_add(&c->tmo, &timeout);
	LIST_INSERT_HEAD(&slowcgi_proc.requests, c, entry);
}

void
slowcgi_timeout(int fd, short events, void *arg)
{
	cleanup_request((struct request*) arg);
}

void
slowcgi_sig_handler(int sig, short event, void *arg)
{
	struct request		*c;
	struct slowcgi_proc	*p;
	pid_t			 pid;
	int			 status;

	p = arg;

	switch (sig) {
	case SIGCHLD:
		while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
			LIST_FOREACH(c, &p->requests, entry)
				if (c->script_pid == pid)
					break;
			if (c == NULL) {
				lwarnx("caught exit of unknown child %i", pid);
				continue;
			}

			if (WIFSIGNALED(status))
				c->script_status = WTERMSIG(status);
			else
				c->script_status = WEXITSTATUS(status);

			if (c->script_flags == (STDOUT_DONE | STDERR_DONE))
				create_end_record(c);
			c->script_flags |= SCRIPT_DONE;

			ldebug("wait: %s", c->script_name);
		}
		if (pid == -1 && errno != ECHILD)
			lwarn("waitpid");
		break;
	default:
		lerr(1, "unexpected signal: %d", sig);
		break;
	}
}

void
slowcgi_add_response(struct request *c, struct fcgi_response *resp)
{
	struct fcgi_record_header	*header;
	size_t				 padded_len;

	header = (struct fcgi_record_header*)resp->data;

	/* The FastCGI spec suggests to align the output buffer */
	padded_len = FCGI_ALIGN(resp->data_len);
	if (padded_len > resp->data_len) {
		/* There should always be FCGI_PADDING_SIZE bytes left */
		if (padded_len > FCGI_RECORD_SIZE)
			lerr(1, "response too long");
		header->padding_len = padded_len - resp->data_len;
		resp->data_len = padded_len;
	}

	TAILQ_INSERT_TAIL(&c->response_head, resp, entry);
	event_add(&c->resp_ev, NULL);
}

void
slowcgi_response(int fd, short events, void *arg)
{
	struct request			*c;
	struct fcgi_record_header	*header;
	struct fcgi_response		*resp;
	ssize_t				 n;

	c = arg;

	while ((resp = TAILQ_FIRST(&c->response_head))) {
		header = (struct fcgi_record_header*) resp->data;
		if (debug > 1)
			dump_fcgi_record("resp ", header);

		n = write(fd, resp->data + resp->data_pos, resp->data_len);
		if (n == -1) {
			if (errno == EAGAIN || errno == EINTR)
				return;
			cleanup_request(c);
			return;
		}
		resp->data_pos += n;
		resp->data_len -= n;
		if (resp->data_len == 0) {
			TAILQ_REMOVE(&c->response_head, resp, entry);
			free(resp);
		}
	}

	if (TAILQ_EMPTY(&c->response_head)) {
		if (c->script_flags == (STDOUT_DONE | STDERR_DONE |
		    SCRIPT_DONE))
			cleanup_request(c);
		else
			event_del(&c->resp_ev);
	}
}

void
slowcgi_request(int fd, short events, void *arg)
{
	struct request	*c;
	ssize_t		 n;
	size_t		 parsed;

	c = arg;

	n = read(fd, c->buf + c->buf_pos + c->buf_len,
	    FCGI_RECORD_SIZE - c->buf_pos-c->buf_len);

	switch (n) {
	case -1:
		switch (errno) {
		case EINTR:
		case EAGAIN:
			return;
		default:
			goto fail;
		}
		break;

	case 0:
		ldebug("closed connection");
		goto fail;
	default:
		break;
	}

	c->buf_len += n;

	/*
	 * Parse the records as they are received. Per the FastCGI
	 * specification, the server need only receive the FastCGI
	 * parameter records in full; it is free to begin execution
	 * at that point, which is what happens here.
	 */
	do {
		parsed = parse_record(c->buf + c->buf_pos, c->buf_len, c);
		c->buf_pos += parsed;
		c->buf_len -= parsed;
	} while (parsed > 0 && c->buf_len > 0);

	/* Make space for further reads */
	if (c->buf_len > 0) {
		bcopy(c->buf + c->buf_pos, c->buf, c->buf_len);
		c->buf_pos = 0;
	}
	return;
fail:
	cleanup_request(c);
}

void
parse_begin_request(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
{
	/* XXX -- FCGI_CANT_MPX_CONN */
	if (c->request_started) {
		lwarnx("unexpected FCGI_BEGIN_REQUEST, ignoring");
		return;
	}

	if (n != sizeof(struct fcgi_begin_request_body)) {
		lwarnx("wrong size %d != %lu", n,
		    sizeof(struct fcgi_begin_request_body));
		return;
	}

	c->request_started = 1;

	c->id = id;
	SLIST_INIT(&c->env);
	c->env_count = 0;
}

void
parse_params(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
{
	struct env_val			*env_entry;
	uint32_t			 name_len, val_len;

	if (!c->request_started) {
		lwarnx("FCGI_PARAMS without FCGI_BEGIN_REQUEST, ignoring");
		return;
	}

	if (c->id != id) {
		lwarnx("unexpected id, ignoring");
		return;
	}

	/*
	 * If this is the last FastCGI parameter record,
	 * begin execution of the CGI script.
	 */
	if (n == 0) {
		exec_cgi(c);
		return;
	}

	while (n > 0) {
		if (buf[0] >> 7 == 0) {
			name_len = buf[0];
			n--;
			buf++;
		} else {
			if (n > 3) {
				name_len = ((buf[0] & 0x7f) << 24) +
				    (buf[1] << 16) + (buf[2] << 8) + buf[3];
				n -= 4;
				buf += 4;
			} else
				return;
		}

		if (n > 0) {
			if (buf[0] >> 7 == 0) {
				val_len = buf[0];
				n--;
				buf++;
			} else {
				if (n > 3) {
					val_len = ((buf[0] & 0x7f) << 24) +
					    (buf[1] << 16) + (buf[2] << 8) +
					     buf[3];
					n -= 4;
					buf += 4;
				} else
					return;
			}
		} else
			return;

		if (n < name_len + val_len)
			return;

		if ((env_entry = malloc(sizeof(struct env_val))) == NULL) {
			lwarnx("cannot allocate env_entry");
			return;
		}

		if ((env_entry->val = calloc(sizeof(char), name_len + val_len +
		    2)) == NULL) {
			lwarnx("cannot allocate env_entry->val");
			free(env_entry);
			return;
		}

		bcopy(buf, env_entry->val, name_len);
		buf += name_len;
		n -= name_len;

		env_entry->val[name_len] = '\0';
		if (val_len < PATH_MAX && strcmp(env_entry->val,
		    "SCRIPT_NAME") == 0 && c->script_name[0] == '\0') {
			bcopy(buf, c->script_name, val_len);
			c->script_name[val_len] = '\0';
		} else if (val_len < PATH_MAX && strcmp(env_entry->val,
		    "SCRIPT_FILENAME") == 0) {
			bcopy(buf, c->script_name, val_len);
			c->script_name[val_len] = '\0';
		}
		env_entry->val[name_len] = '=';

		bcopy(buf, (env_entry->val) + name_len + 1, val_len);
		buf += val_len;
		n -= val_len;

		SLIST_INSERT_HEAD(&c->env, env_entry, entry);
		ldebug("env[%d], %s", c->env_count, env_entry->val);
		c->env_count++;
	}
}

void
parse_stdin(uint8_t *buf, uint16_t n, struct request *c, uint16_t id)
{
	struct fcgi_stdin	*node;

	if (c->id != id) {
		lwarnx("unexpected id, ignoring");
		return;
	}

	if ((node = calloc(1, sizeof(struct fcgi_stdin))) == NULL) {
		lwarnx("cannot calloc stdin node");
		return;
	}

	bcopy(buf, node->data, n);
	node->data_pos = 0;
	node->data_len = n;

	TAILQ_INSERT_TAIL(&c->stdin_head, node, entry);

	if (event_initialized(&c->script_stdin_ev))
		event_add(&c->script_stdin_ev, NULL);
}

size_t
parse_record(uint8_t *buf, size_t n, struct request *c)
{
	struct fcgi_record_header	*h;

	if (n < sizeof(struct fcgi_record_header))
		return (0);

	h = (struct fcgi_record_header*) buf;

	if (debug > 1)
		dump_fcgi_record("", h);

	if (n < sizeof(struct fcgi_record_header) + ntohs(h->content_len)
	    + h->padding_len)
		return (0);

	if (h->version != 1)
		lerrx(1, "wrong version");

	switch (h->type) {
	case FCGI_BEGIN_REQUEST:
		parse_begin_request(buf + sizeof(struct fcgi_record_header),
		    ntohs(h->content_len), c, ntohs(h->id));
		break;
	case FCGI_PARAMS:
		parse_params(buf + sizeof(struct fcgi_record_header),
		    ntohs(h->content_len), c, ntohs(h->id));
		break;
	case FCGI_STDIN:
		parse_stdin(buf + sizeof(struct fcgi_record_header),
		    ntohs(h->content_len), c, ntohs(h->id));
		break;
	default:
		lwarnx("unimplemented type %d", h->type);
		break;
	}

	return (sizeof(struct fcgi_record_header) + ntohs(h->content_len)
	    + h->padding_len);
}

/*
 * Fork a new CGI process to handle the request, translating
 * between FastCGI parameter records and CGI's environment variables,
 * as well as between the CGI process' stdin/stdout and the
 * corresponding FastCGI records.
 */
void
exec_cgi(struct request *c)
{
	struct env_val	*env_entry;
	int		 s_in[2], s_out[2], s_err[2], i;
	pid_t		 pid;
	char		*argv[2];
	char		**env;
	char		*path;

	i = 0;

	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_in) == -1)
		lerr(1, "socketpair");
	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_out) == -1)
		lerr(1, "socketpair");
	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, s_err) == -1)
		lerr(1, "socketpair");
	cgi_inflight--;
	c->inflight_fds_accounted = 1;
	ldebug("fork: %s", c->script_name);

	switch (pid = fork()) {
	case -1:
		c->script_status = errno;

		lwarn("fork");

		close(s_in[0]);
		close(s_out[0]);
		close(s_err[0]);

		close(s_in[1]);
		close(s_out[1]);
		close(s_err[1]);

		c->stdin_fd_closed = c->stdout_fd_closed =
		    c->stderr_fd_closed = 1;
		c->script_flags = (STDOUT_DONE | STDERR_DONE | SCRIPT_DONE);

		create_end_record(c);
		return;
	case 0:
		/* Child process */
#ifdef __OpenBSD__
		if (pledge("stdio rpath exec", NULL) == -1)
			lerr(1, "pledge");
#endif
		close(s_in[0]);
		close(s_out[0]);
		close(s_err[0]);

		if (dup2(s_in[1], STDIN_FILENO) == -1)
			_exit(1);
		if (dup2(s_out[1], STDOUT_FILENO) == -1)
			_exit(1);
		if (dup2(s_err[1], STDERR_FILENO) == -1)
			_exit(1);

		close(s_in[1]);
		close(s_out[1]);
		close(s_err[1]);

		signal(SIGPIPE, SIG_DFL);

		path = strrchr(c->script_name, '/');
		if (path != NULL) {
			if (path != c->script_name) {
				*path = '\0';
				if (chdir(c->script_name) == -1)
					lwarn("cannot chdir to %s",
					    c->script_name);
				*path = '/';
			} else
				if (chdir("/") == -1)
					lwarn("cannot chdir to /");
		}

		argv[0] = c->script_name;
		argv[1] = NULL;
		if ((env = calloc(c->env_count + 1, sizeof(char*))) == NULL)
			_exit(1);
		SLIST_FOREACH(env_entry, &c->env, entry)
			env[i++] = env_entry->val;
		env[i++] = NULL;
		execve(c->script_name, argv, env);
		lwarn("execve %s", c->script_name);
		_exit(1);

	}

	/* Parent process*/
	close(s_in[1]);
	close(s_out[1]);
	close(s_err[1]);

	fcntl(s_in[0], F_SETFD, FD_CLOEXEC);
	fcntl(s_out[0], F_SETFD, FD_CLOEXEC);
	fcntl(s_err[0], F_SETFD, FD_CLOEXEC);

	if (ioctl(s_in[0], FIONBIO, &on) == -1)
		lerr(1, "script ioctl(FIONBIO)");
	if (ioctl(s_out[0], FIONBIO, &on) == -1)
		lerr(1, "script ioctl(FIONBIO)");
	if (ioctl(s_err[0], FIONBIO, &on) == -1)
		lerr(1, "script ioctl(FIONBIO)");

	c->script_pid = pid;
	event_set(&c->script_stdin_ev, s_in[0], EV_WRITE | EV_PERSIST,
	    script_out, c);
	event_add(&c->script_stdin_ev, NULL);
	event_set(&c->script_ev, s_out[0], EV_READ | EV_PERSIST,
	    script_std_in, c);
	event_add(&c->script_ev, NULL);
	event_set(&c->script_err_ev, s_err[0], EV_READ | EV_PERSIST,
	    script_err_in, c);
	event_add(&c->script_err_ev, NULL);
}

void
create_end_record(struct request *c)
{
	struct fcgi_response		*resp;
	struct fcgi_record_header	*header;
	struct fcgi_end_request_body	*end_request;

	if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
		lwarnx("cannot malloc fcgi_response");
		return;
	}
	header = (struct fcgi_record_header*) resp->data;
	header->version = 1;
	header->type = FCGI_END_REQUEST;
	header->id = htons(c->id);
	header->content_len = htons(sizeof(struct
	    fcgi_end_request_body));
	header->padding_len = 0;
	header->reserved = 0;
	end_request = (struct fcgi_end_request_body *) (resp->data +
	    sizeof(struct fcgi_record_header));
	end_request->app_status = htonl(c->script_status);
	end_request->protocol_status = FCGI_REQUEST_COMPLETE;
	end_request->reserved[0] = 0;
	end_request->reserved[1] = 0;
	end_request->reserved[2] = 0;
	resp->data_pos = 0;
	resp->data_len = sizeof(struct fcgi_end_request_body) +
	    sizeof(struct fcgi_record_header);
	slowcgi_add_response(c, resp);
}

void
script_in(int fd, struct event *ev, struct request *c, uint8_t type)
{
	struct fcgi_response		*resp;
	struct fcgi_record_header	*header;
	ssize_t				 n;

	if ((resp = calloc(1, sizeof(struct fcgi_response))) == NULL) {
		lwarnx("cannot malloc fcgi_response");
		return;
	}
	header = (struct fcgi_record_header*) resp->data;
	header->version = 1;
	header->type = type;
	header->id = htons(c->id);
	header->padding_len = 0;
	header->reserved = 0;

	n = read(fd, resp->data + sizeof(struct fcgi_record_header),
	    FCGI_CONTENT_SIZE);

	if (n == -1) {
		switch (errno) {
		case EINTR:
		case EAGAIN:
			free(resp);
			return;
		default:
			n = 0; /* fake empty FCGI_STD{OUT,ERR} response */
		}
	}
	header->content_len = htons(n);
	resp->data_pos = 0;
	resp->data_len = n + sizeof(struct fcgi_record_header);
	slowcgi_add_response(c, resp);

	if (n == 0) {
		if (type == FCGI_STDOUT)
			c->script_flags |= STDOUT_DONE;
		else
			c->script_flags |= STDERR_DONE;

		if (c->script_flags == (STDOUT_DONE | STDERR_DONE |
		    SCRIPT_DONE)) {
			create_end_record(c);
		}
		event_del(ev);
		close(fd);
		if (type == FCGI_STDOUT)
			c->stdout_fd_closed = 1;
		else
			c->stderr_fd_closed = 1;
	}
}

void
script_std_in(int fd, short events, void *arg)
{
	struct request *c = arg;
	script_in(fd, &c->script_ev, c, FCGI_STDOUT);
}

void
script_err_in(int fd, short events, void *arg)
{
	struct request *c = arg;
	script_in(fd, &c->script_err_ev, c, FCGI_STDERR);
}

void
script_out(int fd, short events, void *arg)
{
	struct request		*c;
	struct fcgi_stdin	*node;
	ssize_t			 n;

	c = arg;

	while ((node = TAILQ_FIRST(&c->stdin_head))) {
		if (node->data_len == 0) { /* end of stdin marker */
			close(fd);
			c->stdin_fd_closed = 1;
			break;
		}
		n = write(fd, node->data + node->data_pos, node->data_len);
		if (n == -1) {
			if (errno == EAGAIN || errno == EINTR)
				return;
			event_del(&c->script_stdin_ev);
			return;
		}
		node->data_pos += n;
		node->data_len -= n;
		if (node->data_len == 0) {
			TAILQ_REMOVE(&c->stdin_head, node, entry);
			free(node);
		}
	}
	event_del(&c->script_stdin_ev);
}

void
cleanup_request(struct request *c)
{
	struct fcgi_response	*resp;
	struct fcgi_stdin	*stdin_node;
	struct env_val		*env_entry;

	evtimer_del(&c->tmo);
	if (event_initialized(&c->ev))
		event_del(&c->ev);
	if (event_initialized(&c->resp_ev))
		event_del(&c->resp_ev);
	if (event_initialized(&c->script_ev)) {
		if (!c->stdout_fd_closed)
			close(EVENT_FD(&c->script_ev));
		event_del(&c->script_ev);
	}
	if (event_initialized(&c->script_err_ev)) {
		if (!c->stderr_fd_closed)
			close(EVENT_FD(&c->script_err_ev));
		event_del(&c->script_err_ev);
	}
	if (event_initialized(&c->script_stdin_ev)) {
		if (!c->stdin_fd_closed)
			close(EVENT_FD(&c->script_stdin_ev));
		event_del(&c->script_stdin_ev);
	}
	close(c->fd);
	while (!SLIST_EMPTY(&c->env)) {
		env_entry = SLIST_FIRST(&c->env);
		SLIST_REMOVE_HEAD(&c->env, entry);
		free(env_entry->val);
		free(env_entry);
	}

	while ((resp = TAILQ_FIRST(&c->response_head))) {
		TAILQ_REMOVE(&c->response_head, resp, entry);
		free(resp);
	}
	while ((stdin_node = TAILQ_FIRST(&c->stdin_head))) {
		TAILQ_REMOVE(&c->stdin_head, stdin_node, entry);
		free(stdin_node);
	}
	LIST_REMOVE(c, entry);
	if (! c->inflight_fds_accounted)
		cgi_inflight--;
	free(c);
}

void
dump_fcgi_record(const char *p, struct fcgi_record_header *h)
{
	dump_fcgi_record_header(p, h);

	if (h->type == FCGI_BEGIN_REQUEST)
		dump_fcgi_begin_request_body(p,
		    (struct fcgi_begin_request_body *)(h + 1));
	else if (h->type == FCGI_END_REQUEST)
		dump_fcgi_end_request_body(p,
		    (struct fcgi_end_request_body *)(h + 1));
}

void
dump_fcgi_record_header(const char* p, struct fcgi_record_header *h)
{
	ldebug("%sversion:         %d", p, h->version);
	ldebug("%stype:            %d", p, h->type);
	ldebug("%srequestId:       %d", p, ntohs(h->id));
	ldebug("%scontentLength:   %d", p, ntohs(h->content_len));
	ldebug("%spaddingLength:   %d", p, h->padding_len);
	ldebug("%sreserved:        %d", p, h->reserved);
}

void
dump_fcgi_begin_request_body(const char *p, struct fcgi_begin_request_body *b)
{
	ldebug("%srole             %d", p, ntohs(b->role));
	ldebug("%sflags            %d", p, b->flags);
}

void
dump_fcgi_end_request_body(const char *p, struct fcgi_end_request_body *b)
{
	ldebug("%sappStatus:       %d", p, ntohl(b->app_status));
	ldebug("%sprotocolStatus:  %d", p, b->protocol_status);
}

void
syslog_vstrerror(int e, int priority, const char *fmt, va_list ap)
{
	char *s;

	if (vasprintf(&s, fmt, ap) == -1) {
		syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror");
		exit(1);
	}
	syslog(priority, "%s: %s", s, strerror(e));
	free(s);
}

__dead void
syslog_err(int ecode, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	syslog_vstrerror(errno, LOG_CRIT, fmt, ap);
	va_end(ap);
	exit(ecode);
}

__dead void
syslog_errx(int ecode, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsyslog(LOG_CRIT, fmt, ap);
	va_end(ap);
	exit(ecode);
}

void
syslog_warn(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	syslog_vstrerror(errno, LOG_ERR, fmt, ap);
	va_end(ap);
}

void
syslog_warnx(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsyslog(LOG_ERR, fmt, ap);
	va_end(ap);
}

void
syslog_info(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vsyslog(LOG_INFO, fmt, ap);
	va_end(ap);
}

void
syslog_debug(const char *fmt, ...)
{
	if (verbose > 0) {
		va_list ap;
		va_start(ap, fmt);
		vsyslog(LOG_DEBUG, fmt, ap);
		va_end(ap);
	}
}