/*
 * simple http redirect server
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <syslog.h>
#include <ctype.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <limits.h>
#include <signal.h>
#include <sysexits.h>
#include <fcntl.h>

#include <stdarg.h>
#include <time.h>
#include <syslog.h>


char version[] = "one";

char *host;
int   ontty = 1;
int   verbose = 0;
int   debug = 0;
char *pgm = "redirect";


static void
complain(char *fmt, ...)
{
    static int size = 1024;
    static char *bfr = 0;
    va_list ptr;
    int ret;
    int ofs;
    char *myerror = strerror(errno);
    char *expandfmt;
    int count = 0;
    int isusage;
    char *p;

    static int logopen = 0;

    if ( !(ontty||logopen) ) {
	openlog(pgm, 0, LOG_DAEMON);
	logopen = 1;
    }

    if (bfr == 0 && (bfr = malloc(size)) == 0 ) {
	if (ontty) perror("initial bfr alloc");
	else syslog(LOG_INFO, "initial buffer alloc: %m");
	exit(EX_OSERR);
    }


    for (p = fmt; p = strstr(p, "%m"); count++, p++)
	;

    if (count > 0) {
	if ( expandfmt = malloc(1 + strlen(fmt) + (count*strlen(myerror))) ) {
	    char *q;

	    for (p=fmt, q=expandfmt; *p; ) {
		if ( (p[0] == '%') && (p[1] == 'm') ) {
		    strcat(q, myerror);
		    q += strlen(myerror);
		    p += 2;
		}
		else
		    *q++ = *p++;
	    }
	    *q = 0;
	}
    }
    else
	expandfmt = strdup(fmt);

    if (expandfmt == 0) {
	if (ontty) perror("building format string");
	else syslog(LOG_INFO, "building format string: %m");
	exit(EX_OSERR);
    }

    if (strncmp(expandfmt, "%USAGE ", 7) == 0) {
	expandfmt += 7;
	sprintf(bfr, "usage: %s", pgm);
    }
    else if (ontty)
	sprintf(bfr, "%s: ", pgm);
    else
	bfr[0] = 0;
    ofs = strlen(bfr);

    while (1) {
	if (bfr == 0) {
	    if (ontty) perror("buffer alloc");
	    else syslog(LOG_INFO, "buffer alloc: %m");
	    exit(EX_OSERR);
	}

	va_start(ptr, fmt);
	ret = vsnprintf(bfr+ofs, (size-ofs)-1, expandfmt, ptr);
	va_end(ptr);
	if ( (ret < 0) || (ret >= (size-ofs)) ) {
	    size *= 2;
	    bfr = realloc(bfr, size);
	}
	else
	    break;
    }
    free(expandfmt);

    if (ontty) {
	strcat(bfr, "\n");
	fputs(bfr, stderr);
	fflush(stderr);
    }
    else
	syslog(LOG_INFO, bfr);
}


void
daemonize()
{
    pid_t daemon;
    int i, nul;

    for (i=0; i < 2; i++) {
	if ( (daemon = fork()) == -1) {
	    complain("during daemonization: %m");
	    exit(EX_OSERR);
	}
	else if (daemon > 0)
	    exit(EX_OK);
    }
    close(0);
    close(1);
    close(2);

    if ( (nul = open("/dev/null", O_RDWR)) != -1
			    && dup2(nul,0) != -1
			    && dup2(nul,1) != -1
			    && dup2(nul,2) != -1)
	close(nul);
    else {
	complain("Cannot attach to /dev/null: %m");
	exit(EX_OSERR);
    }
    setsid();
    ontty = 0;
}


static int
attach(int port)
{
    struct sockaddr service;
    struct sockaddr_in *af_inet = (struct sockaddr_in*)&service;
    int size;
    int ret;
    int on = 1;

    if ( (ret = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
	if (verbose || ontty) complain("socket failed: %m");;
	return -1;
    }

    setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on);
    setsockopt(ret, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof on);

    memset(&service, 0, sizeof service);

    af_inet->sin_family = AF_INET;
    af_inet->sin_port  = port;

    if (bind(ret, &service, sizeof service) < 0) {
	if (verbose || ontty) complain("bind failed: %m");
	close(ret);
	return -1;
    }

    if (listen(ret, 5) == -1) {
	if (verbose || ontty) complain("listen failed: %m");
	close(ret);
	return -1;
    }
    return ret;
}


static int
connection(int client)
{
    char line[1024];
    char *p, *q;
    FILE *in, *out;


    if ( ! ((in = fdopen(client, "r")) && (out=fdopen(client,"w"))) ) {
	if (verbose || ontty) complain("fdopens failed: %m");
	close(client);
	return;
    }

    if (fgets(line, sizeof line, in) == 0) {
  oops: fclose(in);
	fclose(out);
	close(client);
	return 0;
    }


    if (verbose) {
	strtok(line, "\r\n");
	complain("[%s]", line);
    }

    p = line;

    while (*p && !isspace(*p)) ++p;	/* skip command */
    while (*p && isspace(*p)) ++p;	/* skip space */

    if (*p == 0) 
	goto oops;

    q = p;

    while (*q && !isspace(*q)) ++q;	/* skip pathname */
    if (*q)
	*q++ = 0;

    while (*q && isspace(*q)) ++q;

    if (*q) {
	char timestr[80];
	time_t now;
	struct tm *tm;

	now = time(0);
	tm = gmtime(&now);

	strftime(timestr, sizeof timestr, "%a, %d %b %Y %H:%M:%S GMT", tm);

	fprintf(out,"HTTP/1.1 302 Found\n"
		    "Date: %s\n"
		    "Server: Simple Redirect Daemon v %s\n"
		    "Location: %s%s\n"
		    "Connection: close\n"
		    "Content-Type: text/html; charset=iso-8859-1\n"
		    "\n",  timestr, version, host, p);
    }

    fprintf(out,"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n"
		"<HTML><HEAD>\n"
		"<TITLE>302 Found</TITLE>\n"
		"</HEAD><BODY>\n"
		"<H1>Found</H1>\n"
		"The document has moved to <A HREF=\"%s%s\">%s%s</A>.<P>\n"
		"<HR>\n"
		"</BODY></HTML>\n", host, p, host, p);
    fflush(out);
    fclose(in);
    fclose(out);
    close(client);
    return 1;
}


void
main(argc,argv)
int argc;
char **argv;
{
    int port;
    int sock, client;
    int opt;

    pgm = basename(argv[0]);

    ontty = isatty(fileno(stdin));


    opterr = 0;

    while ( (opt = getopt(argc, argv, "vd")) != EOF ) {
	switch (opt) {
	case 'v': verbose = 1; break;
	case 'd': debug   = 1; break;
	default:  complain("%USAGE [-dv] to-website [listen-port]");
	}
    }
    argc -= optind;
    argv += optind;

    if (argc < 1) {
	complain("%USAGE [-dv] to-website [listen-port]");
	exit(EX_USAGE);
    }

    host = argv[0];

    if (strstr(host, "://") == 0) {
	complain("malformed to-website");
	exit(EX_USAGE);
    }

    if (argc > 1) {
	int rpn = atoi(argv[1]);

	if (rpn == 0) {
	    complain("unrecognisable port number (%s)", argv[1]);
	    exit(EX_USAGE);
	}
	port = htons(rpn);
    }
    else {
	struct servent *proto = getservbyname("www", "tcp");

	port = proto ? proto->s_port : htons(80);
    }

    if ( (sock = attach(port)) == -1) {
	if ( !(ontty || verbose) )
	    complain("could not attach to port %d", ntohs(port));
	exit(EX_OSERR);
    }

    if (!debug) daemonize();

    while (1) {
	struct sockaddr j;
	int js = sizeof j;

	if ( (client = accept(sock, &j, &js)) != -1 )
	    connection(client);
	else if (errno != EINTR) {
	    if (verbose || ontty) complain("accept failed: %m");
	    close(sock);
	    while ( (sock = attach(port)) == -1 ) {
		complain("reattach failed; sleeping and retrying");
		sleep(20);
	    }
	}
    }
}