/* simplified nntp client */ #include <X11/Xlib.h> #include "www.h" #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <string.h> #include <signal.h> #define CTRLSIZE 8192 #define BUFSIZE 8192 #define THRESHOLD 1024 #define NNTP_PORT 119 /* IANA allocated port */ /* the NNTP commands as given by HELP command 100 This server accepts the following commands: ARTICLE BODY GROUP HEAD LAST LIST NEXT POST QUIT STAT NEWGROUPS HELP IHAVE NEWNEWS SLAVE Additionally, the following extentions are supported: XHDR Retrieve a single header line from a range of articles. XTHREAD Retrieve trn thread file for the current group. XUSER Log a clients username to nntp logfile. XINDEX Retrieve a tin group index file. XMOTD Retrieve the NNTP/news motd. XOVER Return news overview data */ extern char *gateway; extern int debug; extern int Authorize; extern Doc NewDoc; char *XoverView; /* XOVER list for current news group */ char *CurrentGroup; /* the current news group */ static struct sockaddr_in server; /* address info */ static struct sockaddr_in dataserver; /* address info */ static struct hostent *hp; /* other host info */ int GatewayPort = 2785; /* keep track of current values for expanding abbreviated references */ int GetNewsResponse(int socket, char *request, char *buffer, int len) { char *p; int status; if (request) { if (debug) fprintf(stderr, "sending: %s", request); status = XPSend(socket, request, strlen(request), 1); if (status == -1) { Warn("Couldn't send request: %s", request); close(socket); /* close the socket */ return -1; } } status = XPRecv(socket, buffer, len); if (status == -1) { Warn("Couldn't get response to %s", request); close(socket); /* close the socket */ return -1; } buffer[status] = '\0'; if (debug) fprintf(stderr, "%s", TextLine(buffer)); return *buffer - '0'; } /* read the overview data for the current group, closes socket and issues warning message on problems */ char *GetXoverData(int skt, char *group) { int size, len, count, eol; char *p, *q, *buffer; /* purge buffer as needed */ if (XoverView) { free(XoverView); free(group); } size = 4096; len = 0; buffer = malloc(size); if (buffer == NULL) { close(skt); /* close the socket */ ShowAbortButton(0); Warn("Couldn't alloc buffer sized %d", size); return NULL; } sprintf(buffer, "GROUP %s\r\n", group); count = XPSend(skt, buffer, strlen(buffer), 1); if (count == -1) { close(skt); ShowAbortButton(0); Warn("Can't list group: %s", group); free(buffer); return NULL; } count = XPRecv(skt, buffer, CTRLSIZE-1); if (count == -1) { close(skt); ShowAbortButton(0); Warn("Can't list group: %s", group); free(buffer); return NULL; } buffer[count] = '\0'; if (*buffer != '2') { close(skt); ShowAbortButton(0); Warn(buffer); free(buffer); return NULL; } Announce(buffer); len = XPSend(skt, "XOVER\r\n", 7, 1); if (len == -1) { close(skt); /* close the socket */ ShowAbortButton(0); Warn("Couldn't send XOVER command"); free(buffer); return NULL; } len = XPRecv(skt, buffer, size-1); if (len < 1) { close(skt); /* close the socket */ ShowAbortButton(0); Warn("Couldn't get response to XOVER command"); free(buffer); return NULL; } buffer[len] = '\0'; if (*buffer != '2') { close(skt); /* close the socket */ ShowAbortButton(0); Warn(buffer); free(buffer); return NULL; } /* search for pattern: "\n." */ p = buffer; eol = 0; for(;;) { /* do we need to read some more ? */ if (*p == '\0') { /* do we need to expand buffer? */ if (size - len < 513) /* need to grow buffer */ { count = p - buffer; size *= 2; /* attempt to double size */ q = realloc(buffer, size); if (q == NULL) { buffer[len] = '\0'; close(skt); /* close the socket */ ShowAbortButton(0); Warn("Couldn't realloc buffer size to %d", size); NewDoc.length = len; NewDoc.buffer = buffer; return buffer; } buffer = q; p = buffer + count; } /* now read some more data */ count = XPRecv(skt, buffer+len, size-len-1); if (count < 1) { close(skt); /* close the socket */ ShowAbortButton(0); Warn("Couldn't get data for XOVER command"); free(buffer); return NULL; } len += count; buffer[len] = '\0'; } if (eol && *p == '.') break; eol = (*p++ == '\n' ? 1 : 0); } XoverView = buffer; CurrentGroup = strdup(group); return buffer; } char *GetNewsDocument(char *host, char *path) { int s, c, count, n, vg, len, first, last, line; long size; char *p, *q, *t, *r, *from, *date, *subject, *groups, *messageId, *refs, *buffer, recvbuf[CTRLSIZE]; signal(SIGPIPE, SIG_IGN); /* don't barf on stream errors */ /* if UDI was news://host/ref, then trim leading '/' from path */ if (*path == '/') ++path; NewDoc.type = HTMLDOCUMENT; NewDoc.length = 0; /* create a socket */ s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (s == -1) { ShowAbortButton(0); Warn("Can't create socket for %s", host); return NULL; } /* request a connection */ Announce("Connecting to %s", host); vg = 0; if (!Connect(s, host, NNTP_PORT, &vg)) { if (!Authorize) ShowAbortButton(0); return NULL; } size = BUFSIZE; len = 0; buffer = malloc(size); if (buffer == 0) { close(s); ShowAbortButton(0); Warn("Can't allocate read buffer for nntp"); free(buffer); return NULL; } n = GetNewsResponse(s, NULL, buffer, size); /* get response to connecting */ if (n == -1) { ShowAbortButton(0); Warn("Can't connect to news server %s", host); free(buffer); return NULL; } if (n != 2) { close(s); ShowAbortButton(0); Announce(""); NewDoc.length = strlen(buffer); NewDoc.buffer = buffer; return buffer; } Announce(buffer); /* show signon */ /* if path has form 3456@host or <3456@host> it denotes an article */ p = strchr(path, '@'); if (p) { NewDoc.type = HTMLDOCUMENT; if (*path == '<') sprintf(recvbuf, "ARTICLE %s\r\n", path); else sprintf(recvbuf, "ARTICLE <%s>\r\n", path); n = XPSend(s, recvbuf, strlen(recvbuf), 1); if (n == -1) { close(s); ShowAbortButton(0); Warn("Couldn't retrieve article <%s>", path); free(buffer); return NULL; } count = XPRecv(s, recvbuf, CTRLSIZE-1); if (count == -1) { close(s); ShowAbortButton(0); Warn("can't retrieve article <%s>", path); free(buffer); return NULL; } recvbuf[count] = '\0'; if (*recvbuf != '2') { close(s); sprintf(buffer, "<PRE>\n"); q = buffer+6; for (p = recvbuf; *p;) { if (*p == '<') { *q++ = '&'; *q++ = 'l'; *q++ = 't'; *q++ = ';'; } else if (*p == '>') { *q++ = '&'; *q++ = 'g'; *q++ = 't'; *q++ = ';'; } else if (*p == '&') { *q++ = '&'; *q++ = 'a'; *q++ = 'm'; *q++ = 'p'; *q++ = ';'; } else *q++ = *p; ++p; } NewDoc.length = strlen(buffer); NewDoc.buffer = buffer; return buffer; } from = subject = date = refs = groups = ""; p = recvbuf; for (;;) { if (p[0] == '\r' || p[0] == '\n') break; for (q = p; *q && *q != '\n'; ++q) { if (*q == '\r') *q = '\0'; } *q++ = '\0'; if (strncmp(p, "From: ", 6) == 0) from = p+6; else if (strncmp(p, "Subject: ", 9) == 0) subject = p+9; else if (strncmp(p, "Newsgroups: ", 12) == 0) groups = p+12; else if (strncmp(p, "Date: ", 6) == 0) { date = p+6; r = strchr(date, ':'); /* if (r) *(r-3) = '\0'; r = p+6; if (*r == ' ') ++r; if (*r > '9') while (!('0' <= *r && *r <= '9')) ++r; date = r; if (date[1] == ' ') { --date; *date = ' '; } r = strrchr(date, ' '); if (r) { ++r; r[0] = r[2]; r[1] = r[3]; r[2] = '\0'; } */ } else if (strncmp(p, "Message-ID: ", 12) == 0) { messageId = p+12; if (*messageId == '<') ++messageId; r = strchr(messageId, '>'); if (r) *r = '\0'; } else if (strncmp(p, "References: ", 12) == 0) refs = p+12; p = q; } sprintf(buffer, "<TITLE>%s</TITLE>\n<H1>%s</H1>\n<PRE>\n", subject, subject); len = strlen(buffer); sprintf(buffer+len, "Date: %s\nFrom: %s\nMessageId: <%s>\n", date, from, messageId); len += strlen(buffer+len); /* translate news groups into buttons */ n = 0; /* reference number */ for (;;) { while (*groups == ' ') ++groups; r = groups; while (*r && *r != ',') ++r; /* set q to next group */ if (*r == ',') /* another group follows */ { *r = '\0'; q = r+1; } else /* *r == '\0' and no more groups */ q = r; if (++n == 1) { sprintf(buffer+len, "Newsgroups: "); len += 12; } else if (n%3 == 1) { sprintf(buffer+len, "\n "); len += 13; } sprintf(buffer+len, "<A HREF=\"news:%s\">%s</A> ", groups, groups); len += strlen(buffer+len); if (*q == '\0') break; groups = q; } if (n > 0) buffer[len++] = '\n'; /* translate references into buttons */ n = 0; /* reference number */ for (;;) { t = strchr(refs, '<'); q = strchr(refs, '>'); r = strchr(refs, '@'); if ( !(p && q && r) ) break; refs = q+1; ++t; *q = '\0'; if (++n == 1) { sprintf(buffer+len, "References: "); len += 12; } else if (n%3 == 1) { sprintf(buffer+len, "\n "); len += 13; } sprintf(buffer+len, "<A HREF=\"news:%s\">%s</A> ", t, r+1); len += strlen(buffer+len); } if (n > 0) buffer[len++] = '\n'; buffer[len++] = '\n'; for(;;) { /* come here at beginning of line */ if (p[0] == '.') { if (p[1] == '\r' || p[1] == '\n') break; if (p[1] == '.') ++p; } /* set q to end of line or end of data */ for (q = p; *q != '\n'; ++q) { if (*q == '\0') /* need to read some more */ { /* first shift first part of line to start of buffer */ n = q - p; memcpy(recvbuf, p, n); /* and read the next part of the line */ count = XPRecv(s, recvbuf+n, CTRLSIZE-n-1); if (count < 1) { buffer[len] = '\0'; NewDoc.length = len; NewDoc.buffer = buffer; close(s); ShowAbortButton(0); Announce(NewDoc.url); return buffer; } recvbuf[n+count] = '\0'; p = recvbuf; q = p + n - 1; /* -1 compensates for q++ in loop */ } } if (size - len < THRESHOLD) /* need to grow buffer */ { size *= 2; /* attempt to double size */ r = realloc(buffer, size); if (r == NULL) { buffer[len] = '\0'; close(s); /* close the socket */ ShowAbortButton(0); Warn("Couldn't realloc buffer to size %ld", size); NewDoc.length = len; NewDoc.buffer = buffer; return buffer; } buffer = r; } n = q - p; if (p[n-1] == '\r') --n; while (n > 0) { #if 0 /* this code replaces message ids in message body with hypertext links */ if (*p == '<') { t = ""; r = p+1; while (*r && *r != '>') { if (*r == '@') t = r; ++r; } if (*r == '>' && *t == '@') { *r++ = '\0'; sprintf(buffer+len, "<A HREF=\"news:%s\">%s</A> ", ++p, ++t); len += strlen(buffer+len); p = r; n -= 1+r-p; continue; } } #endif buffer[len++] = *p++; --n; } buffer[len++] = '\n'; p = ++q; } close(s); buffer[len] = '\0'; NewDoc.length = len; NewDoc.buffer = buffer; ShowAbortButton(0); Announce(NewDoc.url); return buffer; } /* if the path has the form "alt.*" it denotes a list of group names */ n = strlen(path); if (path[n-1] == '*') { if (*path == '*') strcpy(recvbuf, "LIST\r\n"); else { if (path[n - 2] == '.') --n; c = path[n]; path[n-1] = '\0'; /* use NEWGROUPS for Jan 1st 1970 with path as distribution */ sprintf(recvbuf, "NEWGROUPS 700101 0000 <%s>\r\n", path); path[n] = c; } count = XPSend(s, recvbuf, strlen(recvbuf), 1); if (count == -1) { close(s); ShowAbortButton(0); Warn("lost connection with news server %s", host); free(buffer); return NULL; } sprintf(buffer, "<TITLE>News Groups</TITLE>\n<H1>News Groups</H1>\nNote the number of of news articles in each group is an upper bound\n"); count = XPRecv(s, recvbuf, CTRLSIZE -1); if (count > 0 && *recvbuf == '2') { strcat(buffer, "<UL>\n"); len = strlen(buffer); recvbuf[count] = '\0'; line = 0; p = recvbuf; for (;;) { if (p[0] == '.' && (p[1] == '\r' || p[1] == '\n')) break; if (p[0] == '\r' || p[1] == '\n') break; /* ensure we have the entire line in the recvbuf */ r = p; /* p -> start of line */ for(;;) { c = *r; if (c == '\0') /* need to read some more */ { /* copy existing part to start of buffer */ if (p > recvbuf) strcpy(recvbuf, p); n = r - p; /* the number of chars in this line we currently hold */ p = recvbuf; r = recvbuf+n; count = XPRecv(s, recvbuf+n, CTRLSIZE - 1 - n); if (count < 1) break; recvbuf[n+count] = '\0'; --r; } ++r; if (c == '\n') break; } /* r -> start of next line */ if (line++ == 0) /* skip first line with NNTP status msg */ { p = r; continue; } if (size - len < THRESHOLD) /* need to grow buffer */ { size *= 2; /* attempt to double size */ q = realloc(buffer, size); if (q == NULL) { buffer[len] = '\0'; close(s); /* close the socket */ NewDoc.length = len; NewDoc.buffer = buffer; ShowAbortButton(0); Warn("Couldn't realloc buffer to size %d", size); return buffer; } buffer = q; } for (q = p; *q != ' '; ++q); *q++ = '\0'; sscanf(q, "%d %d", &last, &first); sprintf(buffer+len, " <LI><A HREF=\"news:%s\">%s</A> %d\n", p, p, last-first+1); len += strlen(buffer+len); /*+= strlen(buffer+len);*/ p = r; } strcat(buffer+len, "</UL>\n"); len += 6; } else { if (count > 0) strcat(buffer, recvbuf); else strcat(buffer, "couldn't list groups"); len = strlen(buffer); } close(s); NewDoc.type = HTMLDOCUMENT; buffer[len] = '\0'; NewDoc.length = len; NewDoc.buffer = buffer; ShowAbortButton(0); Announce(NewDoc.url); return buffer; } /* other wise get list of articles in specified group */ p = GetXoverData(s, path); if (!p) return NULL; /* error message handled by GetXover() */ sprintf(buffer, "<TITLE>news:%s</TITLE>\n<H1>%s</H1>\n<PRE>\n", path, path); len = strlen(buffer); /* skip to first line of data in overview */ p = strchr(p, '\n'); ++p; line = 0; NewDoc.type = HTMLDOCUMENT; for (;;) { if (p[0] == '.' && (p[1] == '\r' || p[1] == '\n')) break; subject = strchr(p, '\t'); ++subject; from = strchr(subject, '\t'); *from++ = '\0'; date = strchr(from, '\t'); *date++ = '\0'; /* from like "dsr@hplb.hpl.com (Dave Raggett)" */ q = strchr(from, '('); if (q) { from = q+1; q = strchr(from, ')'); if (q) *q = '\0'; } else /* from like "Dave Raggett <dsr@hplb.hpl.hp.com>" */ { q = strchr(from, '<'); if (q && q > from+1) *(q-1) = '\0'; } messageId = strchr(date, '\t'); *messageId++ = '\0'; q = strchr(messageId, '\t'); *q++ = '\0'; /* and move p to start of next line */ p = strchr(q, '\n'); ++p; /* strip < brackets > from messageId */ if (*messageId == '<') { ++messageId; if ((q = strchr(messageId, '>'))) *q++ = '\0'; } /* strip of time of day */ r = strchr(date, ':'); if (r) *(r-3) = '\0'; r = date; /* adjust year */ if (*r == ' ') ++r; if (*r > '9') while (!('0' <= *r && *r <= '9')) ++r; date = r; if (date[1] == ' ') { --date; *date = ' '; } r = strrchr(date, ' '); if (r) { ++r; r[0] = r[2]; r[1] = r[3]; r[2] = '\0'; } if (size - len < THRESHOLD) /* need to grow buffer */ { size *= 2; /* attempt to double size */ q = realloc(buffer, size); if (q == NULL) { buffer[len] = '\0'; close(s); /* close the socket */ ShowAbortButton(0); Announce("Couldn't realloc buffer size to %d", size); NewDoc.length = len; NewDoc.buffer = buffer; return buffer; } buffer = q; } sprintf(buffer+len, "%-10.10s<A HREF=\"news:%s\"> %-42.42s</A> %s\n", date, messageId, subject, from); len += strlen(buffer+len); ++line; } sprintf(buffer+len, "</PRE>\n"); len += 7; send (s, "QUIT\r\n", 6, 0); close(s); NewDoc.length = len; NewDoc.buffer = buffer; ShowAbortButton(0); Announce(NewDoc.url); return buffer; }