summaryrefslogtreecommitdiff
path: root/nntp-get.c
blob: 9fbfd82c4cdba78a0cd3fd482cdc8058b8e70a4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netdb.h>

enum state {
	STATE_CONNECTED,
	STATE_GROUP_JOINED,
	STATE_ARTICLE_RECV,
	STATE_LAST
};

int main(int argc, char **argv) {
	struct addrinfo hints = {0};
	struct addrinfo *addr;
	int sockfd;
	FILE *sockf;
	char *host, *group, *messageid;

	if (argc == 2 && strchr(argv[1], '@')) {
		host = "news.gmane.org";
		group = strdup("gmane.linux.kernel");
		messageid = argv[1];
	} else if (argc == 3 && strchr(argv[2], '@')) {
		host = "news.gmane.org";

		group = argv[1];
		if (strncmp(group, "gmane.", sizeof("gmane.") - 1)) {
			group = malloc(strlen(argv[1]) + sizeof("gmane."));
			sprintf(group, "gmane.%s", argv[1]);
		}
		char *g = group;
		while ((g = strchr(g, '-'))) *g = '.';

		messageid = argv[2];
	} else if (argc == 4) {
		host = argv[1];
		group = strdup(argv[2]);
		messageid = argv[3];
	} else {
		fprintf(stderr, "Usage: %s host group message-id\n"
				"or:    %s message-id\n",
				*argv, *argv);
		return 1;
	}

	hints.ai_protocol = IPPROTO_TCP;

	if (getaddrinfo(host, "119", &hints, &addr)) {
		perror("getaddrinfo");
		return 1;
	}

	sockfd = socket(addr->ai_family, SOCK_STREAM, addr->ai_protocol);
	if (sockfd < 0) {
		perror("socket");
		goto err_free_addr;
	}
	sockf = fdopen(sockfd, "r");

	if (connect(sockfd, addr->ai_addr, addr->ai_addrlen)) {
		perror("connect");
		goto err_close_sock;
	}

	enum state state = STATE_CONNECTED;
	while (state != STATE_LAST) {
		char buff[512];
		int r = -1;
		if (fgets(buff, sizeof(buff), sockf) == NULL) {
			perror("read response");
			goto err_close_sock;
		}
		switch (state) {
		case STATE_CONNECTED:
			if (strncmp(buff, "200 ", 4)) {
				fprintf(stderr, "Invalid response: %s\n", buff);
				goto err_close_sock;
			}
			r = snprintf(buff, sizeof(buff), "group %s\r\n", group);
		break;
		case STATE_GROUP_JOINED:
			if (strncmp(buff, "211 ", 4)) {
				fprintf(stderr, "Invalid response: %s\n", buff);
				goto err_close_sock;
			}
			if (messageid[0] == '<') {
				r = snprintf(buff, sizeof(buff), "article %s\r\n", messageid);
			} else {
				r = snprintf(buff, sizeof(buff), "article <%s>\r\n", messageid);
			}
			break;
		case STATE_ARTICLE_RECV:
			if (strncmp(buff, "220 ", 4)) {
				fprintf(stderr, "Invalid response: %s\n", buff);
				goto err_close_sock;
			}
			while (fgets(buff, sizeof(buff), sockf) != NULL) {
				if (!strcmp(buff, "..\r\n")) puts(".");
				else if (!strcmp(buff, ".\r\n")) break;
				else fputs(buff, stdout);
			}
			r = snprintf(buff, sizeof(buff), "quit\r\n");
			break;
		case STATE_LAST:
			break;
		}
		state++;
		if (r > 0) write(sockfd, buff, r);
	}
	
	close(sockfd);
	freeaddrinfo(addr);
	free(group);
	return 0;
err_close_sock:
	close(sockfd);
err_free_addr:
	freeaddrinfo(addr);
	free(group);
	return 1;
}