/*
 $Id: main.c 319 2008-09-21 23:23:55Z sten $
*/

#include "main.h"
#include "util.h"
#include <ctype.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <signal.h>

extern unsigned int loglevel;
unsigned int do_fork = 1;
unsigned int do_debug = 0;
struct output_buffer obuf; 

int main(int argc, char *argv[]) {

    int ch;
    int fd = -1, sfd = -1;
    char *progname = argv[0];
    char *username = DEFAULT_USER;
    char *pidfile = PACKAGE_PID_FILE;
    char pidstr[16];
    struct passwd *pwd = NULL;

    // socket
    char *fifoname = NULL;
    FILE *input;
    struct stat s;

    // line length
    size_t llen;

    // syslog
    char *shost = NULL;
    int sfac = 0, slen = 0, sflen = 0;
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    char sline[MAXSYSLOG];

    memset(&obuf, 0, sizeof(obuf));
    memset(&sline, 0, sizeof(sline));

    // reset line length and pointer
    obuf.line = &obuf.buffer;
    obuf.len = 0;


    while ((ch = getopt(argc, argv, "fhi:o:m:s:u:v")) != -1) {
	switch(ch) {
	    case 'f':
		do_fork = 0;
		break;
	    case 'i':
		fifoname = optarg;
		break;
	    case 'o':
		obuf.file = optarg;
		break;
	    case 'm':
		shost = optarg;
		break;
	    case 's':
		sfac = atoi(optarg);
		if ((0 > sfac) || (sfac > 7)) {
		    my_log(CRIT, "invalid syslog facility: %s", optarg);
		    usage(progname);
		}
		break;
	    case 'u':
		username = optarg;
		break;
	    case 'v':
		loglevel++;
		break;
	    default:
		usage(progname);
	}
    }

    // reject missing fifo/file
    if (fifoname == NULL || 
	((obuf.file == NULL) && (shost == NULL)))
	usage(progname);

    // validate username
    if ((do_debug == 0) && (pwd = getpwnam(username)) == NULL) {
	my_log(CRIT, "User %s does not exist", username);
	exit(EXIT_FAILURE);
    }

    // change umask
    umask(UMASK);


    // open pidfile
    if (do_fork == 1) {
	fd = open(pidfile, O_WRONLY|O_CREAT, 0666);
	if (fd == -1) {
	    my_log(CRIT, "failed to open pidfile %s: %s",
			pidfile, strerror(errno));
	    exit(EXIT_FAILURE);	
	}
	if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
	    my_log(CRIT,PACKAGE_NAME " already running (%s locked)",pidfile);
	    exit(EXIT_FAILURE);	
	}
    }

    
    // create / open fifo
    if (stat(fifoname, &s) == -1) {
	if(errno != ENOENT) {
	    my_log(CRIT, "unable to stat fifo %s: %s",
			fifoname, strerror(errno));
	    exit(EXIT_FAILURE);
	} else if (mkfifo(fifoname, S_IRWXU) == -1) {
	    my_log(CRIT, "unable to create fifo %s: %s",
			fifoname, strerror(errno));
	    exit(EXIT_FAILURE);
        }
    } else if (!S_ISFIFO(s.st_mode)) {
	my_log(CRIT, "expected fifo at %s, got something else", fifoname);
	exit(EXIT_FAILURE);
    }

    if ((input = fopen(fifoname, "r+")) == NULL) {
	my_log(CRIT, "failed to open fifo %s: %s",
		    fifoname, strerror(errno));
	exit(EXIT_FAILURE);
    }

    
    // setuid & setgid
    if (setgid(pwd->pw_gid) == -1){
	my_log(CRIT, "unable to setgid: %s", strerror(errno));
       	exit(EXIT_FAILURE);
    }

    if (setgroups(0, NULL) == -1){
	my_log(CRIT, "unable to setgroups: %s", strerror(errno));
       	exit(EXIT_FAILURE);
    }

    if (setuid(pwd->pw_uid) == -1){
   	my_log(CRIT, "unable to setuid: %s", strerror(errno));
       	exit(EXIT_FAILURE);
    }


    // open logfile
    log_action(LOG_OPEN);


    // create syslog socket
    if (shost != NULL) {
	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = 0;
	hints.ai_protocol = 0;

	if (getaddrinfo(shost, "syslog", &hints, &result) != 0) {
	    my_log(CRIT, "failed to resolve syslog host: %s", 
			 strerror(errno));
	    exit(EXIT_FAILURE);
	}

	for (rp = result; rp != NULL; rp = rp->ai_next) {
	    sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

	    if (sfd == -1)
		continue;

	    if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
		break;

	    close(sfd);
	}

	if (rp == NULL) {
	    my_log(CRIT, "failed to open syslog socket");
	    exit(EXIT_FAILURE);
	}

	freeaddrinfo(result);
    }


    // fork
    if (do_fork == 1) {
	if (daemon(0,0) == -1) {
	    my_log(CRIT, "backgrounding failed: %s", strerror(errno));
	    exit(EXIT_FAILURE);
	}

	if ((snprintf(pidstr, sizeof(pidstr), "%d\n", (int)getpid()) <= 0) ||
	    (write(fd, pidstr, strlen(pidstr)) <= 0)) {
	    my_log(CRIT, "failed to write pidfile: %s", strerror(errno));
	    exit(EXIT_FAILURE);
	}
    }


    // create syslog prio
    if (shost != NULL)
	sflen = sprintf(sline, "<%d>", LOG_MAKEPRI((sfac + 16)<<3, LOG_INFO));


    // create signal handlers
    signal(SIGALRM, log_action);
    signal(SIGHUP, log_action);
    signal(SIGTERM, log_action);

    // startup message
    my_log(CRIT, PACKAGE_STRING " running");


    // main loop
    while (feof(input) == 0) {
	if ((fgets(obuf.line, MAXLINESIZE, input)) != NULL) {
	    llen = strlen(obuf.line);

	    // skip empty lines
	    if (llen == 0)
		continue;

	    // only syslog short complete lines
	    if ((shost != NULL) &&
		(llen < (MAXSYSLOG - sflen)) &&
		(strchr(obuf.line, '\n') != NULL)) {

		// copy the line
		slen = sflen + llen - 1;
		memcpy(sline + sflen, obuf.line, llen);

		// send the packet (without the newline)
		if (write(sfd, sline, slen) != slen)
		    my_log(CRIT, "error sending syslog packet: %s", 
				strerror(errno));
	    }

	    // update the length and lineptr
	    obuf.len += llen;
	    obuf.line += llen;
	}

	// write the data if we can't fit another line
	if (obuf.len > (MEMBUFFER - MAXLINESIZE)) {
	    log_action(LOG_FLUSH);

	    // reset line length and pointer
	    obuf.line = &obuf.buffer;
	    obuf.len = 0;
	}
    }

    return (EXIT_FAILURE);
}

void log_action(int action) {

    if (obuf.file == NULL)
	return;

    if (action != LOG_OPEN) {
	// write output
	if (write(obuf.fd, obuf.buffer, obuf.len) < obuf.len) {
	    my_log(CRIT, "writing to file %s failed: %s", 
			obuf.file, strerror(errno));
	}

	// reset line length and pointer
	obuf.line = &obuf.buffer;
	obuf.len = 0;

	// return if closing isn't needed
	if (action == LOG_FLUSH)
	    return;
    }

    // close logfile
    if (action == LOG_CLOSE || action == LOG_REOPEN) {

	// sync logfile to disk
	if (fsync(obuf.fd) != 0) {
	    my_log(CRIT, "syncing file %s failed: %s", 
		    obuf.file, strerror(errno));
	    exit(EXIT_FAILURE);
	}

	// close file
	if (close(obuf.fd) != 0) {
	    my_log(CRIT, "closing file %s failed: %s", 
		    obuf.file, strerror(errno));
	    exit(EXIT_FAILURE);
	}

	// quit when closing
	if (action == LOG_CLOSE)
	    exit(EXIT_SUCCESS);
    }

    // (re-)open logfile
    obuf.fd = open(obuf.file, O_WRONLY|O_CREAT|O_APPEND, 0666);
    if (obuf.fd == -1) {
	my_log(CRIT, "failed to open filename %s: %s",
		obuf.file, strerror(errno));
	exit(EXIT_FAILURE);
    }

    return;
}


void usage(const char *fn) {

    fprintf(stderr, "%s version %s\n" 
	"Usage: %s -i <fifo> [-o <file> | -m <host>]\n"
	    "\t-f = Run in the foreground\n"
	    "\t-h = Print this message\n"
	    "\t-i <fifo> = Input fifo\n"
	    "\t-o <file> = Output file\n"
	    "\t-m <host> = Syslog host\n"
	    "\t-s 0-7 = Syslog facility\n"
	    "\t-u <user> = Setuid User (defaults to %s)\n"
	    "\t-v = Increase logging verbosity\n",
	    PACKAGE_NAME, PACKAGE_VERSION, fn, DEFAULT_USER);

    exit(EXIT_FAILURE);
}

