/*
 * mread (c) 2004 Adam Wysocki 
 * gophi at apcoh.org
 *
 * Składnia: mread message-id [host [port]]
 *
 * Changelog:
 *
 *   * [26.11.2004] Wersja pierwsza, dziewicza.
 */

#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>

extern int errno;

void xp (char do_exit, char *fmt, ...)
{
	va_list args;
	char buf[512];

	va_start (args, fmt);
	vsnprintf (buf, sizeof(buf), fmt, args);
	va_end (args);

	fprintf (stderr, "mread: %s\n", buf);
	if (do_exit)
		exit (-1);
}

int read_resp (int fd, char *buf)
{
	int i = 0;
	char ch, done = 0;

	memset (buf, 0, 4);

	fprintf (stderr, "<< ");

	while (!done) {
		switch (read(fd, &ch, sizeof(ch))) {
			case 0:	/* eof */
				return 0;
			case 1:
				if (i < 3)
					buf[i++] = ch;

				if (ch != 0x0D)
					fputc (ch, stderr);

				if (ch == 0x0A)
					done = 1;

				break;
			case -1:
				if (errno == EAGAIN)
					break;

				xp (0, "read(): Błąd odczytu: %m.");

				return 0;
		}
	}

	return atoi(buf);
}

void send_data (int fd, char *fmt, ...)
{
	va_list args;
	char buf[512];

	va_start (args, fmt);
	vsnprintf (buf, sizeof(buf) - 2, fmt, args);
	va_end (args);

	fprintf (stderr, ">> %s\n", buf);
	strcat (buf, "\r\n");

	write (fd, buf, strlen(buf));
}

char dehex (char ch)
{
	if (ch >= '0' && ch <= '9')
		return ch - '0';
	else if (ch >= 'A' && ch <= 'F')
		return ch - 'A' + 10;
	else if (ch >= 'a' && ch <= 'f')
		return ch - 'a' + 10;

	return 0;
}

void get_article (int fd)
{
	char *art = (char *) NULL;
	char *head, *body;
	char buf[256], done = 0;
	int len = 0, rs, i, nrs;
	char enc = '8';		/* 8, q, b */

	while (!done) {
		rs = read(fd, buf, sizeof(buf));
		if (rs == -1) {
			xp (0, "read(): Błąd odczytu: %m.");
			return;
		} else if (!rs) {
			xp (0, "read(): Zamknięte gniazdo.");
			return;
		}

		i = 0;
		nrs = 0;
		while (i < rs) {
			if (buf[i++] == 0x0D)
				continue;

			nrs++;
		}

		art = (char *) realloc(art, len + nrs);
		if (!art) {
			xp (0, "realloc(): Brak pamięci, len=%u, rs=%u, tot=%u.", len, rs, len + rs);
			return;
		}

		for (i = 0; i < rs; i++)
			if (buf[i] != 0x0D) {
				art[len++] = buf[i];
				if (len > 3 && !strncmp(art + len - 3, "\n.\n", 3)) {
					done = 1;
					art[len - 2] = (char) NULL;
					break;
				}
			}
	}

	head = art;
	body = (char *) NULL;

	for (i = 1; i < len; i++)
		if (!body && art[i - 1] == 0x0A && art[i] == 0x0A) {
			art[i] = (char) NULL;
			body = art + i + 1;
		}

	printf ("%s\n", head);

	if (strstr(head, "Content-Type: ")) {
		char *ct = strstr(head, "Content-Type: ");
		char ch = ct[strlen("Content-Type: ")];

		if (ch == 'Q' || ch == 'q')
			enc = 'q';
		else if (ch == 'B' || ch == 'b')
			enc = 'b';
	}

	if (enc == '8')
		printf ("%s", body);
	else {
		char qp = 1;	/* 1, 2, znak << 4 */

		len = strlen(body);

		for (i = 0; i < len; i++) {
			char ch = body[i];

			if (qp == 1) {
				if (ch == '%')
					qp = 2;
				else
					fputc (ch, stdout);
			} else if (qp == 2)
				qp = dehex(ch) << 4;
			else {
				qp |= dehex(ch);
				fputc (qp, stdout);
				qp = 1;
			}
		}
	}

	free (art);
}

/*
 * sukces: nie wraca
 * porazka: powrot, nie zamyka fd
 */

void nntp_sess (int fd, char *mid)
{
	char rbuf[4];

	read_resp (fd, rbuf);
	if (rbuf[0] != '2') {
		xp (0, "Błędne powitanie.");
		return;
	}

	send_data (fd, "MODE READER");
	read_resp (fd, rbuf);
	if (rbuf[0] != '2') {
		xp (0, "Błędna odpowiedź.");
		return;
	}

	send_data (fd, "ARTICLE <%s>", mid);
	read_resp (fd, rbuf);
	if (rbuf[0] != '2') {
		xp (0, "Błędna odpowiedź.");
		return;
	}

	get_article (fd);

	send_data (fd, "QUIT");
	read_resp (fd, rbuf);

	close (fd);
	exit (0);
}

/*
 * sukces: nie wraca (bo nntp_sess nie wraca)
 * porazka: wraca
 */

void read_msgid (char *mid, char *serv, int port)
{
	int fd;
	struct hostent *hent;
	struct sockaddr_in addr;
	struct in_addr in_address;

	xp (0, "<%s>, host %s, port %u.", mid, serv, port);

	hent = gethostbyname(serv);
	if (!hent) {
		xp (0, "%s: Błąd translacji nazwy.", serv);
		return;
	}

	fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	memcpy (&in_address, hent->h_addr, sizeof(in_address));
	addr.sin_addr.s_addr = in_address.s_addr;
	addr.sin_port = htons(port);
	addr.sin_family = AF_INET;

	if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
		xp (0, "%s: Błąd połączenia: %m.", serv);
		return;
	}

	nntp_sess (fd, mid);

	close (fd);
	return;
}

int main (int ac, char **av)
{
	char *msgid = av[1];
	char *host = NULL;
	int port = 119;

	if (ac < 2)
		xp (1, "Składnia: mread message-id [host [port]]");

	if (ac > 2)
		host = av[2];

	if (ac > 3) {
		port = atoi(av[3]);

		if (!port)
			port = getservbyname(av[3], "tcp")->s_port;
	}

	if (host)
		read_msgid (msgid, host, port);

	read_msgid (msgid, "news.apcoh.org", 119);
	read_msgid (msgid, "news-archive.icm.edu.pl", 119);
	read_msgid (msgid, "news.aster.pl", 119);
	read_msgid (msgid, "news.ztm.waw.pl", 119);
	read_msgid (msgid, "news.jceel.info", 119);

	xp (1, "Nie mogłem znaleźć artykułu.");

	/* NOTREACHED */

	return -1;
}
