#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

#if defined(__NetBSD__)
#include "paths.h"
#endif
#ifndef _PATH_VAR
#define _PATH_VARTMP "/usr/tmp"
#endif

#define DEFAULT_SERVER_NAME "news"
#define DEFAULT_COMICSRC "/mit/outland/lib/default.comicsrc"
#define DEC(c) (((c) - ' ') & 077)

typedef struct entry {
    char *name;
    int high;
    struct entry *next;
} Entry;

static Entry *read_comicsrc(void);
static void read_comics(Entry *entries);
static void process_article(FILE *from);
static void connect_to_server(FILE **from, FILE **to);
void write_new_comicsrc(Entry *entries);
static void *emalloc(size_t size);
static void efgets(char *buf, size_t bufsize, FILE *fp);
static void die(char *fmt, ...);

static char tempfilename[100];

int main(int argc, char **argv)
{
    Entry *entries;

    system("attach -h -n -q graphics");
    sprintf(tempfilename, "%s/comic.%lu", _PATH_VARTMP, getpid());
    entries = read_comicsrc();
    read_comics(entries);
    write_new_comicsrc(entries);
    unlink(tempfilename);
    return 0;
}

static Entry *read_comicsrc()
{
    FILE *fp;
    char *home, filename[200], buf[200], *p;
    Entry *entries = NULL, *entry, *reversed = NULL, *next;
    int len;

    /* Open the .comicsrc file. */
    home = getenv("HOME");
    if (!home)
	die("No HOME environment variable.");
    strcpy(filename, home);
    if (*home && home[strlen(home) - 1] != '/')
	strcat(filename, "/");
    strcat(filename, ".comicsrc");
    fp = fopen(filename, "r");
    if (!fp)
	fp = fopen(DEFAULT_COMICSRC, "r");
    if (!fp) {
	die("Can't open .comicsrc file.  Create one with a line for each\n"
	    "newsgroup you want to see comics in.");
    }

    /* Read in the lines, creating a list of entries. */
    while (fgets(buf, sizeof(buf), fp)) {
	/* Strip off any trailing newline and carriage return. */
	if (*buf && buf[strlen(buf) - 1] == '\n')
	    buf[strlen(buf) - 1] = 0;
	if (*buf && buf[strlen(buf) - 1] == '\r')
	    buf[strlen(buf) - 1] = 0;

	/* Make a new entry, copy in the group name and high water mark. */
	p = strchr(buf, ':');
	len = (p) ? p - buf : strlen(buf);
	entry = (Entry *) emalloc(sizeof(Entry));
	entry->name = (char *) emalloc(len + 1);
	strncpy(entry->name, buf, len);
	entry->name[len] = 0;
	entry->high = (p) ? atoi(p + 1) : 0;

	/* Chain the new entry into the list and return it. */
	entry->next = entries;
	entries = entry;
    }

    /* Reverse the list so that it's in the right order. */
    for (entry = entries; entry; entry = next) {
	next = entry->next;
	entry->next = reversed;
	reversed = entry;
    }
    return reversed;
}

static void read_comics(Entry *entries)
{
    Entry *entry;
    FILE *from, *to;
    char buf[200];
    int num, first, last;

    /* Connect to the server, read the welcome line. */
    connect_to_server(&from, &to);
    efgets(buf, sizeof(buf), from);

    /* For each group, read the comics in it. */
    for (entry = entries; entry; entry = entry->next) {
	fprintf(to, "group %s\r\n", entry->name);
	fflush(to);
	efgets(buf, sizeof(buf), from);
	if (sscanf(buf, "211 %d %d %d", &num, &first, &last) < 3) {
	    fprintf(stderr, "Can't enter group %s, skipping.\n", entry->name);
	    continue;
	}
	fprintf(to, "stat %d\r\n", entry->high);
	fflush(to);
	efgets(buf, sizeof(buf), from);
	while (1) {
	    fprintf(to, "next\r\n");
	    fflush(to);
	    efgets(buf, sizeof(buf), from);
	    if (atoi(buf) != 223)
		break;
	    fprintf(to, "article\r\n");
	    fflush(to);
	    efgets(buf, sizeof(buf), from);
	    if (atoi(buf) != 220) {
		fprintf(stderr, "Failed to read an article, skipping.\n");
		continue;
	    }
	    process_article(from);
	}
	entry->high = last;
    }

    /* Close the NNTP connection. */
    fprintf(to, "quit\r\n");
    fflush(to);
    efgets(buf, sizeof(buf), from);
    fclose(from);
    fclose(to);
}

static void process_article(FILE *from)
{
    static int started_xv = 0;
    static pid_t xv_pid;

    char buf[4096];
    const char *p;
    FILE *tmp;
    int n;
    unsigned char ch;

    /* Search for a line "begin <mode> <filename>" */
    while (1) {
	efgets(buf, sizeof(buf), from);
	if (*buf == '.' && (buf[1] == '\r' || buf[1] == '\n')) {
	    printf("A non-comic article was found and skipped.\n");
	    return;
	}
    }

    /* Decode the article into a temporary file. */
    tmp = fopen(tempfilename, "w");
    if (tmp == NULL)
	die("Can't open temporary file.");
    decode(tmp, from);

    /* Skip any remaining lines in the file. */
    while (1) {
	efgets(buf, sizeof(buf), from);
	if (buf[0] == '.' && (buf[1] == '\r' || buf[1] == '\n'))
	    break;
    }

    /* Done uudecoding. */
    fclose(tmp);

    /* Tell xv to reload the image, if we have one running. */
    if (started_xv) {
	if (kill(xv_pid, SIGQUIT) >= 0)
	    return;
    }

    /* Start a new xv. */
    xv_pid = fork();
    if (xv_pid < 0)
	die("Couldn't fork.");
    if (xv_pid == 0) {
	execl("/mit/graphics/arch/@sys/bin/xv", "xv", tempfilename, NULL);
	exit(1);
    }
    started_xv = 1;

    sleep(5);
}

from64(infile, outfile, digestp, boundaries, boundaryct) 
FILE *infile, *outfile;
char **digestp;
char **boundaries;
int *boundaryct;
{
    int c1, c2, c3, c4;
    int newline = 1, DataDone = 0;
    char buf[3];
    MD5_CTX context;

    if (digestp) MD5Init(&context);
    while ((c1 = getc(infile)) != EOF) {
        if (isspace(c1)) {
            if (c1 == '\n') {
                newline = 1;
            } else {
                newline = 0;
            }
            continue;
        }
        if (newline && boundaries && c1 == '-') {
            char Buf[200], erora;
            /* a dash is NOT base 64, so all bets are off if NOT a boundary */
            ungetc(c1, infile);
            fgets(Buf, sizeof(Buf), infile);
            if (boundaries
                 && (Buf[0] == '-')
                 && (Buf[1] == '-')
                 && PendingBoundary(Buf, boundaries, boundaryct)) {
                break;
            }
            warn("Ignoring unrecognized boundary line");
            continue;
        }
        if (DataDone) continue;
        newline = 0;
        do {
            c2 = getc(infile);
        } while (c2 != EOF && isspace(c2));
        do {
            c3 = getc(infile);
        } while (c3 != EOF && isspace(c3));
        do {
            c4 = getc(infile);
        } while (c4 != EOF && isspace(c4));
        if (c2 == EOF || c3 == EOF || c4 == EOF) {
            warn("Premature EOF");
            break;
        }
        if (c1 == '=' || c2 == '=') {
            DataDone=1;
            continue;
        }
        c1 = char64(c1);
        c2 = char64(c2);
	buf[0] = ((c1<<2) | ((c2&0x30)>>4));
        putc(buf[0], outfile);
        if (c3 == '=') {
	    if (digestp) MD5Update(&context, buf, 1);
            DataDone = 1;
        } else {
            c3 = char64(c3);
	    buf[1] = (((c2&0XF) << 4) | ((c3&0x3C) >> 2));
            putc(buf[1], outfile);
            if (c4 == '=') {
		if (digestp) MD5Update(&context, buf, 2);
                DataDone = 1;
            } else {
                c4 = char64(c4);
		buf[2] = (((c3&0x03) <<6) | c4);
                putc(buf[2], outfile);
		if (digestp) MD5Update(&context, buf, 3);		
            }
        }
    }
    if (digestp) *digestp = md5contextTo64(&context);
}

}

static void connect_to_server(FILE **from, FILE **to)
{
    char *server_name;
    int fd;
    struct hostent *hostent;
    struct servent *servent;
    struct sockaddr_in sin;

    /* Find the address of the news server. */
    server_name = getenv("NNTPSERVER");
    server_name = (server_name) ? server_name : DEFAULT_SERVER_NAME;
    hostent = gethostbyname(server_name);
    if (!hostent)
	die("Can't look up news server address.");
    servent = getservbyname("nntp", "tcp");
    if (!servent)
	die("Can't look up nntp service port.");
    sin.sin_family = AF_INET;
    sin.sin_port = servent->s_port;
    memcpy(&sin.sin_addr, hostent->h_addr, sizeof(struct in_addr));

    /* Get the socket to connect with. */
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
	die("Can't get socket: %s", strerror(errno));

    /* Make the connection. */
    if (connect(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0)
	die("Can't connect to the news server.");

    /* Make file handles from the file descriptor. */
    *from = fdopen(fd, "r");
    *to = fdopen(dup(fd), "w");
    if (!*from || !*to)
	die("Can't open file handles to news server.");
}

void write_new_comicsrc(Entry *entries)
{
    char newfilename[200], filename[200];
    FILE *fp;

    strcpy(filename, getenv("HOME"));
    if (*filename && filename[strlen(filename) - 1] != '/')
	strcat(filename, "/");
    strcat(filename, ".comicsrc");
    strcpy(newfilename, filename);
    strcat(newfilename, ".new");
    fp = fopen(newfilename, "w");
    if (!fp)
	die("Can't open new comicsrc file for writing.");
    for (; entries; entries = entries->next)
	fprintf(fp, "%s:%d\n", entries->name, entries->high);
    fclose(fp);
    if (unlink(filename) < 0 && errno != ENOENT) {
	unlink(newfilename);
	die("Can't unlink old comicsrc for update.");
    }
    if (rename(newfilename, filename) < 0)
	die("WARNING!  Unable to move updated comicsrc file in place.");
}

static void *emalloc(size_t size)
{
    void *ptr;

    ptr = malloc(size);
    if (ptr == NULL)
	die("malloc(%d) failed.\n", (int) size);
    return ptr;
}

static void efgets(char *buf, size_t bufsize, FILE *fp)
{
    if (!fgets(buf, bufsize, fp))
	die("Failure reading from news server.");
}

static void die(char *fmt, ...)
{
    char buf[1000];
    va_list args;

    va_start(args, fmt);
    vsprintf(buf, fmt, args);
    va_end(args);
    fputs(buf, stderr);
    putc('\n', stderr);

    /* Try to avoid leaving a comic temp file around. */
    unlink(tempfilename);

    exit(1);
}

