/*
* PLUGDAEMON. Copyright (c) 2004 Peter da Silva. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The names of the program and author may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "includes.h"
#include "config.h"
#include "plug.h"
int daemonized = 0;
char *prog = NULL;
char tag[64];
char *sourceaddr = NULL, *sourceport = NULL;
char *proxyaddr = NULL;
char *httpsaddr = NULL, *httpsport = NULL;
char *session_file = NULL;
int debug = 0;
int verbose = 0;
int background = 1;
int logging = 0;
int onlyone = 0;
int sessionmode = 0;
int keepalive=0;
long timeout = 3600; /* seconds */
long retrytime = 0; /* seconds */
char *pidfile = NULL, *delete_pidfile = NULL;
dest_t *dest_list;
proc_t *proc_list[HASH_SIZE];
client_t *client_list;
rule_t *filter_rules = 0;
int nproxies = 0;
int nclients = 0;
int nprocs = 0;
char *version = "plugdaemon V2.5.3 Copyright (c) 2004 Peter da Silva";
char *usage_string = "[-V] [-P pidfile] [-S sessionfile] [-snklfod[d]...]\n"
"\t[-p proxy-addr] [-i srcaddr] [-a accept_rule]...\n"
"\t[-h HTTPS-proxy[:port]] [-t timeout] [-r retry]\n"
"\nport destaddr[:destport]...";
int
main(int ac, char **av)
{
int srvfd, one;
struct sockaddr_in src_sockaddr, *prx_sockaddr;
struct sockaddr_in *https_sockaddr;
loginfo_t loginfo, *lp;
int pid;
dest_t *target;
int saved_errno;
one = 1;
prx_sockaddr = 0;
https_sockaddr = NULL;
parse_args(ac, av);
if(session_file && session_file[0] == '-' && session_file[1] == '\0')
session_file = NULL;
if(debug || (verbose && !session_file))
background = 0;
if (sourceaddr)
sprintf(tag, "(%.16s %.8s)", sourceaddr, sourceport);
else if(sourceport)
sprintf(tag, "(%.8s)", sourceport);
else
bailout("Can't happen, source port is null!", S_FATAL);
if(debug>1)
fprintf(stderr, "%s: %s\n", prog, tag);
/* arguments parsed, get sockets ready */
if(debug>2) fprintf(stderr, "set up listening socket\n");
if ((srvfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
bailout("server socket", S_FATAL);
if(setsockopt(srvfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one,
sizeof one) < 0) {
bailout("set server socket options", S_FATAL);
}
fill_sockaddr_in(&src_sockaddr,
sourceaddr?inet_addr(sourceaddr):htonl(INADDR_ANY),
htons(atoi(sourceport)));
if(httpsaddr) {
if(debug>2) fprintf(stderr, "set up https proxy socket\n");
if ((httpsport = strchr(httpsaddr, ':')))
*httpsport++ = 0;
else
httpsport = "80";
if (!(https_sockaddr = malloc(sizeof *https_sockaddr))) {
bailout("alloc memory for https proxy socket", S_FATAL);
}
fill_sockaddr_in(https_sockaddr,
inet_addr(httpsaddr),
htons(atoi(httpsport)));
}
if(proxyaddr) {
if(debug>2) fprintf(stderr, "set up outgoing socket\n");
if (!(prx_sockaddr = malloc(sizeof *prx_sockaddr))) {
bailout("alloc memory for proxy socket", S_FATAL);
}
fill_sockaddr_in(prx_sockaddr, inet_addr(proxyaddr), 0);
}
if(debug>2) fprintf(stderr, "set up target socket(s)\n");
for(target = dest_list; target; target=target->next) {
char *destaddr, *destport;
destaddr = target->destname;
if((destport = strchr(destaddr, ':')))
*destport++ = 0;
else
destport = sourceport;
if(https_sockaddr) {
target->addr = *https_sockaddr;
target->connect = malloc(strlen(destaddr) + strlen(destport) + 2);
if(!target->connect)
bailout("malloc", S_FATAL);
sprintf(target->connect, "%s:%s", destaddr, destport);
} else {
fill_sockaddr_in(&(target->addr),
inet_addr(destaddr), htons(atoi(destport)));
target->connect = NULL;
}
target->nclients = 0;
target->status = S_NORMAL;
target->went_bad = (time_t)0;
target->destname = NULL; /* it's been trashed anyway */
}
if(debug>2) fprintf(stderr, "set up logging\n");
if(verbose) {
lp = &loginfo;
memset(lp, 0, sizeof loginfo);
lp->listen = src_sockaddr;
if(prx_sockaddr)
lp->proxy = *prx_sockaddr;
} else
lp = NULL;
daemonize();
prog = tag; /* for logging */
init_signals();
if(debug>2) fprintf(stderr, "bind server socket\n");
/* One ring to rule them all */
if(bind(srvfd, (struct sockaddr *)&src_sockaddr, sizeof src_sockaddr) < 0)
bailout("server bind", S_FATAL);
if(logging)
syslog(LOG_NOTICE, "%.64s: Plugdaemon started.", tag);
listen(srvfd, 5);
if(debug>2) fprintf(stderr, "start main loop\n");
/* wait for connections and service them */
while(1) {
int clifd, prxfd, cli_len, status;
struct sockaddr_in cli_sockaddr;
if(debug>1)
fprintf(stderr, "%d listening for new connections.\n",
(int) getpid());
cli_len = sizeof cli_sockaddr;
do {
clifd = accept(srvfd, (struct sockaddr *)&cli_sockaddr, &cli_len);
saved_errno = errno;
/* If a child process died, we'll get an interrupted
* system call here, so call the undertaker every time
* through the loop.
*/
undertaker();
} while (clifd < 0 && saved_errno == EINTR);
if(clifd < 0)
bailout("client accept", S_FATAL);
if(lp)
gettimeofday(&lp->timeval, &lp->timezone);
if(!(target = select_target(clifd, lp))) {
close(clifd);
continue;
}
if(debug>1)
fprintf(stderr, "%d forwarding %d to %s.\n",
(int) getpid(), ntohs(cli_sockaddr.sin_port),
sa2ascii(&target->addr, NULL));
/* spawn a child and send the parent back to try again */
if((pid = fork()) == -1)
bailout("client fork", S_FATAL);
if(pid) {
close(clifd);
remember_pid(pid, target);
update_pidfile();
continue;
}
/* OK, I'm the child. First, forget about my parent's
* pidfile so I don't accidentally blow it away when I
* exit, and close the server port so if my parent dies
* it can be resurrected without killing me too...
*/
delete_pidfile = 0;
close(srvfd);
if ((prxfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
bailout("proxy socket", S_FATAL);
if (prx_sockaddr) {
if(bind(prxfd, (struct sockaddr *)prx_sockaddr,
sizeof *prx_sockaddr) < 0)
{
bailout("proxy bind", S_FATAL);
}
}
if (connect(prxfd, (struct sockaddr *)&(target->addr),
sizeof (target->addr)) < 0)
bailout("proxy connect", S_CONNECT);
if(debug>1)
fprintf(stderr, "%d connected proxy to %s.\n",
(int) getpid(), sa2ascii(&target->addr, NULL));
if(keepalive) {
if(setsockopt(clifd, SOL_SOCKET, SO_KEEPALIVE,
(char *)&one, sizeof one) < 0) {
bailout("set client socket options", S_FATAL);
}
if(setsockopt(prxfd, SOL_SOCKET, SO_KEEPALIVE,
(char *)&one, sizeof one) < 0) {
bailout("set proxy socket options", S_FATAL);
}
}
if(target->connect) {
char *errmsg = https_chat(prxfd, target->connect);
if(errmsg) {
bailout(errmsg, S_FATAL);
}
}
status = plug(clifd, prxfd, lp);
logout(status, lp);
exit(status);
}
}
char *sa2ascii(struct sockaddr_in *a, char *bufp)
{
static char tempbuf[SA2ASCII_BUFSIZ];
if(!bufp) bufp = tempbuf;
if(a->sin_addr.s_addr == htonl(INADDR_ANY))
sprintf(bufp, "%d", ntohs(a->sin_port));
else if(a->sin_port == 0)
sprintf(bufp, "%.71s", inet_ntoa(a->sin_addr));
else
sprintf(bufp, "%.71s:%d",
inet_ntoa(a->sin_addr), ntohs(a->sin_port));
return bufp;
}
#define JUMP_END(str,size,tmp) (tmp = strlen(str), size -= tmp, str += tmp)
#define ADD_BLANK(str,size) (*(str)++ = ' ', *(str) = 0, (size)++)
void
logout(int status, loginfo_t *lp)
{
char logbuffer[BUFSIZ];
char *buf_ptr = logbuffer;
size_t buf_left = BUFSIZ, len;
time_t t;
struct timeval now;
struct timezone zone;
if(logging) {
if(status != S_NORMAL)
syslog(LOG_NOTICE, "%.64s: Complete, status=%d.", tag, status);
else
syslog(LOG_NOTICE, "%.64s: Complete.", tag);
}
if(!lp) return;
gettimeofday(&now, &zone);
t = time(NULL);
strftime(buf_ptr, buf_left, "[%Y-%m-%d %H:%M:%S] ", localtime(&t));
JUMP_END(buf_ptr,buf_left,len);
sprintf(buf_ptr, "plug[%d] ", getpid());
JUMP_END(buf_ptr,buf_left,len);
sa2ascii(&lp->listen, buf_ptr);
JUMP_END(buf_ptr,buf_left,len);
ADD_BLANK(buf_ptr,buf_left);
lp->peer.sin_port = 0; /* Don't care */
sa2ascii(&lp->peer, buf_ptr);
JUMP_END(buf_ptr,buf_left,len);
ADD_BLANK(buf_ptr,buf_left);
sa2ascii(&lp->target, buf_ptr);
JUMP_END(buf_ptr,buf_left,len);
ADD_BLANK(buf_ptr,buf_left);
sprintf(buf_ptr, "%ld %ld ", (long)lp->nread[0], (long)lp->nread[1]);
JUMP_END(buf_ptr,buf_left,len);
now.tv_sec -= lp->timeval.tv_sec;
now.tv_usec -= lp->timeval.tv_usec;
if(now.tv_usec < 0) {
now.tv_sec--;
now.tv_usec += 1000000L;
}
if(now.tv_sec)
sprintf(buf_ptr, "%ld%06ld\n",
(long)now.tv_sec, (long)now.tv_usec);
else
sprintf(buf_ptr, "%ld\n", (long)now.tv_usec);
JUMP_END(buf_ptr,buf_left,len);
if(session_file) {
int sfd = open(session_file, O_WRONLY|O_CREAT|O_APPEND, 0666);
if(sfd>=0) {
write(sfd, logbuffer, buf_ptr-logbuffer);
close(sfd);
}
} else
write(1, logbuffer, buf_ptr-logbuffer);
}
void
bail_no_val(char *optname)
{
char msgbuf[1024];
sprintf(msgbuf, "Missing value for %s", optname);
bailout(msgbuf, S_SYNTAX);
}
void
bailout(char *message, int status)
{
int save_errno;
char msgbuf[1024];
char *p;
save_errno = errno;
sprintf(msgbuf, "%.64s: %.64s", prog, message);
p = msgbuf + strlen(msgbuf);
if(save_errno) {
sprintf(p, ": %.64s", strerror(save_errno));
} else {
sprintf(p, "\nUsage: %.64s %s", prog, usage_string);
}
if(!daemonized)
fprintf(stderr, "%s\n", msgbuf);
else {
syslog(LOG_ERR, msgbuf);
closelog();
}
if(delete_pidfile)
unlink(delete_pidfile);
exit (status);
}
void
parse_args(int ac, char **av)
{
dest_t *new_dest = NULL, *next_dest = NULL;
if((prog = strrchr(*av, '/')))
prog++;
else
prog = *av;
while (*++av) {
if (**av=='-') {
while(*++*av) switch(**av) {
case 'i':
if(!*++*av && !*++av)
bail_no_val("interface (-i)");
sourceaddr = *av;
goto nextarg;
case 'p':
if(!*++*av && !*++av)
bail_no_val("proxy address (-p)");
proxyaddr = *av;
goto nextarg;
case 'P':
if(!*++*av && !*++av)
bail_no_val("PID file (-P)");
pidfile = *av;
goto nextarg;
case 'h':
if(!*++*av && !*++av)
bail_no_val("HTTP proxy (-h)");
httpsaddr = *av;
goto nextarg;
case 't':
if(!*++*av && !*++av)
bail_no_val("timeout (-t)");
timeout = atol(*av);
goto nextarg;
case 'r':
if(!*++*av && !*++av)
bail_no_val("retry time (-r)");
retrytime = atol(*av);
goto nextarg;
case 'a':
if(!*++*av && !*++av)
bail_no_val("access rule (-a)");
add_filter_rule(*av);
goto nextarg;
case 'S':
if(!*++*av && !*++av)
bail_no_val("session file (-S)");
verbose++;
session_file = *av;
goto nextarg;
case 'k':
keepalive++;
continue;
case 'n':
background = 0;
continue;
case 'l':
logging++;
continue;
case 'd':
debug++;
continue;
case 'f':
sessionmode++;
continue;
case 'o':
onlyone++;
continue;
case 'V':
printf("%s: %s\n", prog, version);
exit(0);
default:
bailout("unknown argument", S_SYNTAX);
}
} else {
if(!sourceport)
sourceport = *av;
else {
if(!httpsaddr) {
char *ptr;
for (ptr = *av; *ptr != '\0'; ptr++)
if (strchr("0123456789:.", *ptr) == NULL)
bailout("target not in addr[:port] format", S_SYNTAX);
}
if(nproxies >= MAX_PROXIES)
bailout("too many targets", S_SYNTAX);
new_dest = malloc(sizeof (dest_t));
if(!new_dest) {
perror("malloc");
bailout("Can't allocate target structure", S_FATAL);
}
new_dest->next = NULL;
new_dest->destname = *av;
if(!dest_list) { /* new list */
dest_list = new_dest;
} else { /* add to end of list */
next_dest->next = new_dest;
}
next_dest = new_dest;
nproxies++;
}
}
nextarg: ;
}
if(nproxies == 0)
bailout("not enough arguments", S_SYNTAX);
if(!sourceport)
bailout("not enough arguments", S_SYNTAX);
}
#define NOSET ((fd_set *) NULL)
#define NOTIME ((struct timeval *) NULL)
int
plug(int fd1, int fd2, loginfo_t *lp)
{
struct connx {
int fd; /* socket (bidirectional) */
char buf[IO_SIZE]; /* Hold STUFF */
int len, off; /* tail, head pointers into buffer */
int open; /* socket still open for reading */
int shutdown_wait; /* other socket closed for reading,
* shut down writing when your buffer
* is done with
*/
} s[2];
fd_set rset, wset, except_set;
fd_set *p_eset;
int nfds, nwr, nrd;
int i;
if(keepalive) p_eset = &except_set;
else p_eset = NULL;
if(fd1>fd2)
nfds=fd1+1;
else
nfds=fd2+1;
s[0].fd = fd1;
s[1].fd = fd2;
s[0].len = s[1].len = s[0].off = s[1].off = 0;
s[0].open = s[1].open = 1;
s[0].shutdown_wait = s[1].shutdown_wait = 0;
while(s[0].open || s[1].open) {
FD_ZERO(&rset);
FD_ZERO(&wset);
if(p_eset) FD_ZERO(p_eset);
for(i = 0; i < 2; i++) {
if(s[i].open) {
if(p_eset) {
FD_SET(s[i].fd, p_eset);
}
if(s[i].len < IO_SIZE) {
FD_SET(s[i].fd, &rset);
}
if(s[i].len > s[i].off) {
FD_SET(s[!i].fd, &wset);
}
}
}
if(select(nfds, &rset, &wset, p_eset, NOTIME) < 0)
bailout("proxy select", S_FATAL);
for(i = 0; i < 2; i++) {
if(p_eset && FD_ISSET(s[i].fd, p_eset)) {
return S_EXCEPT; /* S_NORMAL? */
}
if(FD_ISSET(s[i].fd, &rset)) {
nrd = read(s[i].fd, s[i].buf+s[i].len, IO_SIZE-s[i].len);
if(nrd == -1) { /* Shouldn't happen */
nrd = 0; /* fake EOF */
}
if(nrd == 0) {
shutdown(s[i].fd, 0);
if(s[i].len > s[i].off)
s[!i].shutdown_wait = 1;
else
shutdown(s[!i].fd, 1);
s[i].open = 0;
}
s[i].len += nrd;
if(lp) lp->nread[i] += nrd;
}
if(FD_ISSET(s[!i].fd, &wset)) {
nwr = write(s[!i].fd, s[i].buf+s[i].off, s[i].len-s[i].off);
if(nwr == 0) {
shutdown(s[!i].fd, 1);
shutdown(s[i].fd, 0);
s[i].open = 0;
} else if (nwr < 0) {
if(errno == EAGAIN)
nwr = 0;
else
bailout("proxy write", S_EXCEPT);
}
s[i].off += nwr;
if(s[i].off == s[i].len) {
s[i].off = s[i].len = 0;
if(s[!i].shutdown_wait) {
shutdown(s[!i].fd, 1);
s[!i].shutdown_wait = 0;
}
}
if(lp) lp->nwrite[i] += nwr;
}
}
}
if(debug>1) fprintf(stderr, "%d completed.\n", (int) getpid());
return S_NORMAL;
}
void
logclient(struct in_addr peer, char *status)
{
char *s;
s = inet_ntoa(peer);
syslog(LOG_NOTICE, "%.64s: Connect from %.64s %s", tag, s, status);
}
void
logerror(char *event)
{
char *msg = strerror(errno);
if(!daemonized)
fprintf(stderr, "%.64s: %s", event, msg);
syslog(LOG_ERR, "%.64s: %.64s: %s", tag, event, msg);
}
void
fill_sockaddr_in(struct sockaddr_in *buffer, u_long addr, u_short port)
{
memset(buffer, 0, sizeof *buffer);
buffer->sin_family = AF_INET;
buffer->sin_addr.s_addr = addr;
buffer->sin_port = port;
}
int
check_peer(struct in_addr source)
{
rule_t *p;
u_long saddr;
if(debug>1) {
fprintf(stderr, "check_peer(%s)\n", inet_ntoa(source));
}
saddr = ntohl(source.s_addr);
for(p = filter_rules; p; p=p->next) {
if(debug>2) {
fprintf(stderr, "Comparing %08lx&%08lx to %08lx\n",
(long)saddr, (long)p->netmask, (long)p->addr);
}
if( (saddr & p->netmask) == p->addr ) {
return 1;
}
}
return 0;
}
void
add_filter_rule(char *network)
{
rule_t *p;
char *subnet;
in_addr_t addr;
u_short bits;
subnet = strchr(network, '/');
if(subnet) {
*subnet++ = 0;
bits = atoi(subnet);
} else
bits = 32;
addr = inet_addr(network);
p = malloc(sizeof *p);
if(!p) bailout("malloc", S_FATAL);
p->addr = ntohl(addr);
p->netmask = -1 << (32-bits);
p->next = filter_rules;
filter_rules = p;
}
void
daemonize(void)
{
int pid;
if(background) {
if((pid = fork()) == -1)
bailout("daemon fork", S_FATAL);
if(pid)
exit(S_NORMAL);
}
write_pidfile();
(void)openlog(prog, LOG_PID|LOG_CONS, LOG_DAEMON);
if(background) {
close(0);
close(1);
close(2);
setsid();
daemonized = 1;
}
}
void
cleanup(int sig)
{
if(delete_pidfile)
unlink(delete_pidfile);
exit(0);
}
/* OK, all waiter() does now is squirrel away the PIDs of the dying
* binomes, so the undertaker can deliver them to the Principle Office
* so their resources can be reused by later children. Doing this
* keeps them from going viral and crashing the process table, I
* suspect Megabyte is involved somewhere.
*
* The undertaker runs at a strategic spot in the main loop where it's
* most likely that this bit of code will have been recently triggered.
*
* There's room for GRAVESITES-1 PIDs. If it ever gets to the point that
* anything like that many processes are dying at once this code will
* already be running into problems due to the undertaker itself getting
* interrupted. The result of that will be a slow memory leak in plug.
*
* There's actually two arrays, and undertaker flips the index when it
* starts work so new dead kids get deposited in the other one, this way
* the waiter won't end up dropping a body on top of the undertaker.
*/
void
#ifdef WAITER_ALTDEF
waiter(int sig, void * scp)
#else
waiter(int sig, SA_HANDLER_ARG2_T code, void * scp)
#endif
{
int status, pid;
while (1) {
pid = waitpid(-1, &status, WNOHANG);
if(pid == 0 || pid == -1)
break;
if(debug>1)
fprintf(stderr, "%d child %d died, status is %d.\n",
(int) getpid(), pid, status);
inform_undertaker(pid, status);
}
}
void
init_signals(void)
{
struct sigaction zombiesig, junksig;
/* Wait for dead kids */
zombiesig.SA_HANDLER = waiter;
sigemptyset(&zombiesig.sa_mask);
zombiesig.sa_flags = SA_NOCLDSTOP | SA_RESTART;
if(sigaction(SIGCHLD, &zombiesig, &junksig) < 0)
bailout("zombie signal", S_FATAL);
signal(SIGTERM, cleanup);
}
void delete_client (client_t *client, client_t *back_ptr)
{
if(client->dest)
client->dest->nclients--;
nclients--;
if(back_ptr)
back_ptr->next = client->next;
else
client_list = client->next;
free(client);
}
struct dtab *select_target(int clifd, loginfo_t *lp)
{
struct sockaddr_in p_addr;
int len;
client_t *client = 0;
dest_t *target = NULL;
static dest_t *dest_next = 0;
time_t now;
if(lp || sessionmode || logging || filter_rules) {
len = sizeof p_addr;
if(getpeername( clifd, (struct sockaddr *)&p_addr, &len) == -1) {
/* Log instead of bailing because we're the parent. */
logerror("getpeername");
return 0;
}
if(lp)
lp->peer = p_addr;
if(filter_rules) {
if(check_peer(p_addr.sin_addr) == 0) {
logclient(p_addr.sin_addr, "Refused.");
return 0;
}
}
}
now = time((time_t *)0);
if(sessionmode) {
client_t *back_ptr = 0;
/* find a client and get rid of expired ones */
client = client_list;
while(client != NULL) {
/* Clean up expired clients as we go */
if(now - client->last_touched > timeout) {
delete_client(client, back_ptr);
client = back_ptr;
if(!client)
break;
} else if(client->addr == p_addr.sin_addr.s_addr) {
/* check to see if the dest is in failover */
if(client->dest &&
client->dest->status != S_NORMAL) {
delete_client(client, back_ptr);
client = NULL;
}
break;
}
back_ptr = client;
client = client->next;
}
if(client) { /* Old client, destination good. */
target = client->dest;
} else if(nclients < MAX_CLIENTS) {
nclients++;
client = malloc(sizeof (client_t));
if(!client) {
logerror("malloc");
logclient(p_addr.sin_addr,
"aborted: out of memory, FATAL");
bailout("Out of memory allocating client table", S_FATAL);
}
client->next = client_list;
client_list = client;
client->addr = p_addr.sin_addr.s_addr;
client->status = 1;
client->dest = (struct dtab *)0;
} else {
logclient(p_addr.sin_addr, "Too many clients.");
}
if(client)
client->last_touched = now;
}
/* New client or we're not tracking sessions, find a target */
if(!target) {
int try;
/* select a proxy. Dumb code to cycle them */
if (onlyone) /* always start at head of list -- ADB */
dest_next = NULL;
for(try = 0; try < nproxies; try++) {
if(dest_next)
dest_next = dest_next->next;
if(!dest_next)
dest_next = dest_list;
if(dest_next->status == S_NORMAL) {
target = dest_next;
break;
}
/* retry destinations periodically -- ADB */
if(retrytime &&
now > dest_next->went_bad + retrytime) {
if(debug>1) {
fprintf(stderr,
"retrying destination %s.\n",
sa2ascii(&dest_next->addr, NULL));
}
target = dest_next;
dest_next->status = S_NORMAL;
break;
}
}
}
if(!target) { /* disaster! They're all bad! */
struct dtab *dp;
/* punt, mark them all good and pick the first.
* This is actually not a bad strategy if the client
* is a web browser, since they'll just get soft
* failures until one comes up.
*/
for(dp = dest_list; dp; dp = dp->next) {
dp->status = S_NORMAL;
}
target = dest_next = dest_list;
dest_next = dest_next->next;
}
if(client && !client->dest) {
client->dest = target;
target->nclients++;
}
if(logging) {
char tmp[128]; /* big enough for IPv6, in ":" fmt */
sprintf(tmp, "to %s", inet_ntoa(target->addr.sin_addr));
logclient(p_addr.sin_addr, tmp);
}
if(lp) lp->target = target->addr;
return target;
}
struct ptab *lookup_pid(int pid)
{
proc_t *p;
for(p = proc_list[hash(pid)]; p; p=p->next)
if(p->pid == pid)
break;
return p;
}
void
remember_pid(int pid, struct dtab * target)
{
proc_t *p = lookup_pid(pid);
if(!p) {
int bucket = hash(pid);
if(nprocs>=MAX_CLIENTS*USAGE_FACTOR) {
syslog(LOG_ERR,
"%s: Client table full, pid %d", tag, pid);
return;
}
if(!(p = malloc(sizeof (proc_t))))
return;
p->next = proc_list[bucket];
proc_list[bucket] = p;
nprocs++;
}
p->pid = pid;
p->dest = target;
}
int graveyard = 0;
struct { int pid, status; } dead_children[2][GRAVESITES];
int next_dead_child[2] = { 0, 0 };
void inform_undertaker(int pid, int status) {
int child;
if(next_dead_child[graveyard] >= GRAVESITES) {
bailout("Too many dead_children in inform_undertaker", S_FATAL);
}
child = next_dead_child[graveyard]++;
dead_children[graveyard][child].pid = pid;
dead_children[graveyard][child].status = status;
}
void undertaker(void)
{
int funeral;
for(funeral = 0; funeral < 2; funeral++) {
graveyard = !graveyard;
burials(!graveyard);
}
}
void burials(where)
{
int pid, status, child;
if(next_dead_child[where] > 0) {
while(next_dead_child[where] > 0) {
child = --next_dead_child[where];
pid = dead_children[where][child].pid;
status = dead_children[where][child].status;
status = WIFEXITED(status)
? WEXITSTATUS(status)
: S_FATAL;
switch(status) {
case S_CONNECT:
case S_EXCEPT:
tag_dest_bad(pid, status);
case S_NORMAL:
default:
;
}
forget_pid(pid);
}
}
update_pidfile();
}
void
forget_pid(int pid)
{
proc_t *back_ptr = NULL;
proc_t *p = proc_list[hash(pid)];
while(p) {
if(p->pid == pid)
break;
back_ptr = p;
p = p->next;
}
if(!p)
return;
if(back_ptr)
back_ptr->next = p->next;
else
proc_list[hash(pid)]=p->next;
free(p);
}
void
tag_dest_bad(int pid, int status)
{
proc_t *p = lookup_pid(pid);
if(!p)
return;
/* OK, we know this destination has failed. Change its status */
p->dest->status = status;
p->dest->went_bad = time((time_t *)0);
if(debug>1)
fprintf(stderr, "destination %s bad status %d.\n",
sa2ascii(&p->dest->addr, NULL), status);
}
void write_pidfile(void)
{
FILE *fp;
if(!pidfile) return;
if (!(fp = fopen(pidfile, "w"))) {
static char msgbuf[256]; /* text + 2 #s */
sprintf(msgbuf, "PID file %.128s: Error %d", pidfile, errno);
bailout(msgbuf, S_FATAL);
}
fprintf(fp, "%d\n", getpid());
fclose(fp);
delete_pidfile = pidfile;
}
void update_pidfile(void)
{
FILE *fp;
proc_t *p;
int hindex;
if(!pidfile) return;
if(!delete_pidfile) return;
if (!(fp = fopen(pidfile, "w")))
return;
fprintf(fp, "%d\n", getpid());
for(hindex = 0; hindex < HASH_SIZE; hindex++) {
for(p = proc_list[hindex]; p; p=p->next)
fprintf(fp, "%d\n", p->pid);
}
fclose(fp);
delete_pidfile = pidfile;
}
char *https_chat(int fd, char *name)
{
static char chatbuf[IO_SIZE];
char *errmsg;
char *s;
int result;
if(debug>1)
fprintf(stderr, "https_chat(%d, '%s');\n", fd, name);
sprintf(chatbuf, "CONNECT %s HTTP/1.1\r\n\r\n", name);
if(debug>1)
fprintf(stderr, "%s\n", chatbuf);
write(fd, chatbuf, strlen(chatbuf));
if((errmsg = raw_readline(fd, chatbuf, IO_SIZE)))
return errmsg;
s = strchr(chatbuf, ' ');
if(!s)
goto synerr;
if(!*++s)
goto synerr;
result = 0;
while(isdigit(*s)) {
result = result * 10 + *s - '0';
s++;
}
if(result == 200) {
while(1) {
if((errmsg = raw_readline(fd, chatbuf, IO_SIZE)))
return errmsg;
for(s = chatbuf; isspace(*s); s++)
continue;
if(!*s)
return NULL;
}
}
while(isspace(*s))
s++;
if(*s)
return s;
synerr:
return "Malformed response from HTTPS proxy";
}
char *raw_readline(int fd, char *buffer, int maxlen)
{
int n = 0;
char *errmsg = NULL;
char *s;
char lastc = 0;
if(debug > 1)
fprintf(stderr, "raw_readline(%d, buffer, %d);\n", fd, maxlen);
s = buffer;
while(s - buffer < maxlen) {
n = read(fd, s, 1);
if(n<=0) {
errmsg = n ? "Error on socket" : "Connection closed";
break;
}
if(lastc == '\r') {
if(*s == '\n') {
*s = 0;
if(debug > 1 && *buffer)
fprintf(stderr, "%s\n", buffer);
return 0;
}
/* slide a CR in under whatever the character is */
*(s+1) = *s;
*s = '\r';
s++;
}
lastc = *s;
if(*s != '\r')
s++;
}
if(debug) {
if(n) perror("read");
*s = 0;
if(*buffer)
fprintf(stderr, "%s\n", buffer);
if(!n)
fprintf(stderr, "<EOF>\n");
}
return errmsg;
}
syntax highlighted by Code2HTML, v. 0.9.1