/*
nntputil.c -- misc nntp-related stuff
Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
22646949.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
and Randolf Skerka <Randolf.Skerka@gmx.de>.
Copyright of the modifications 1997.
Modified by Kent Robotti <robotti@erols.com>. Copyright of the
modifications 1998.
Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
Copyright of the modifications 1998.
Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
Copyright of the modifications 1998, 1999.
Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>.
Copyright of the modifications 2002.
Modified by Matthias Andree <matthias.andree@gmx.de>.
Copyright of the modifications 2000 - 2003.
See file COPYING for restrictions on the use of this software.
*/
#include "system.h"
#include "leafnode.h"
#include "mysigact.h"
#include <fcntl.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifndef __LCLINT__
#include <arpa/inet.h>
#endif /* not __LCLINT__ */
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include "ln_log.h"
char last_command[SIZE_lineout + 1];
char lineout[SIZE_lineout + 1];
/*@relnull@*//*@dependent@*/ FILE *nntpin = NULL;
/*@relnull@*//*@dependent@*/ FILE *nntpout = NULL;
int stat_is_evil = 0;
int date_is_evil = 0;
static int xnntpreply(const struct server *, int);
static void authsucc(const struct server *current_server) {
if (verbose)
printf("%s: authenticated as %s\n", current_server->name, current_server->username);
syslog(LOG_INFO, "%s: authenticated as %s", current_server->name, current_server->username);
}
/*
05/26/97 - T. Sweeney - Send a string out, keeping a copy in reserve.
*/
void
putaline(void)
{
if (debug >= 1) {
char y, *x = lineout + strcspn(lineout, "\r\n");
y = *x; *x = '\0';
syslog(LOG_DEBUG, ">%s", lineout);
*x = y;
}
strcpy(last_command, lineout); /* RATS: ignore */
fputs(lineout, nntpout);
fflush(nntpout);
}
/*
* Authenticate ourselves at a remote server.
* Returns TRUE if authentication succeeds, FALSE if it does not.
* Error will have been logged in case of a FALSE return,
* no log output if TRUE returned.
*/
int
authenticate(const struct server *current_server)
{
int reply;
if (!current_server) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"authenticate: internal error: current_server is NULL, aborting");
abort();
}
if (!current_server->username) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: username needed for authentication",
current_server->name);
return FALSE;
}
fprintf(nntpout, "AUTHINFO USER %s\r\n", current_server->username);
fflush(nntpout);
if (debugmode)
syslog(LOG_DEBUG, ">AUTHINFO USER %s", current_server->username);
reply = xnntpreply(current_server, 0);
if (reply == 281) {
authsucc(current_server);
return TRUE;
} else if (reply != 381) {
ln_log(LNLOG_SERR, LNLOG_CSERVER, "error: %s: AUTHINFO USER rejected: %03d",
current_server->name, reply);
return FALSE;
}
if (!current_server->password) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: password needed for authentication",
current_server->name);
return FALSE;
}
/* DO NOT LOG THIS: */
fprintf(nntpout, "AUTHINFO PASS %s\r\n", current_server->password);
fflush(nntpout);
if (debugmode)
syslog(LOG_DEBUG, ">AUTHINFO PASS <password not shown>");
reply = xnntpreply(current_server, 0);
if (reply != 281) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: AUTHINFO PASS failed: %03d", reply);
return FALSE;
}
authsucc(current_server);
return TRUE;
}
static size_t lllen = 0;
static
/*@null@*/
/*@owned@*/
char *llstr = NULL;
void freelastreply(void)
{
if (!llstr) return;
free(llstr);
llstr = NULL;
}
/*@dependent@*//*@null@*/ char *
lastreply(void)
{
return llstr;
}
/**
* decode an NNTP reply number
* reads a line from the server and returns an integer
*
* 498 is used to mean "protocol error", like smail,
* and includes timeout and "server disconnect" (EOF)
* conditions
*
* the text returned is stored in lllen/llstr
* for later retrieval by lastreply()
*
* from Tim Sweeney: retry in case of AUTHINFO failure.
*/
static int
xnntpreply(const struct server *current_server,
/** set this to true to enable authentication after 480 reply */
int may_auth)
{
char *response;
int r = 0;
int c;
do {
response = mgetaline(nntpin);
if (!response) {
if (llstr)
free(llstr);
llstr = NULL;
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: NNTP server went away (server disconnect or timeout)");
return 498;
}
if (debug == 1) syslog(LOG_DEBUG, "<%s", response);
/* cache line */
if (strlen(response) > lllen || !llstr) {
if (llstr)
free(llstr);
lllen = strlen(response);
llstr = critmalloc(lllen + 1, "nntpreply");
}
strcpy(llstr, response); /* RATS: ignore */
if (strlen(response) > 2
&& isdigit((unsigned char)response[0])
&& isdigit((unsigned char)response[1])
&& isdigit((unsigned char)response[2])
&& ((response[3] == ' ')
|| (response[3] == '\0')
|| (response[3] == '-'))) {
int rl;
rl = atoi(response);
if (r > 0 && r != rl) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: multiline reply with variant error code (%d vs. %d), last line: %s", r, rl, response);
r = 498; /* protocol error */
} else
r = rl;
c = (response[3] == '-');
} else {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: syntax error in reply \"%s\"", response);
c = 0;
r = 498; /* protocol error */
}
} while(c);
if (r == 480 && may_auth) { /* need to authenticate */
char *x = critstrdup(last_command, "xnntpreply");
x[strcspn(last_command, "\r\n")] = '\0';
if (debugmode)
syslog(LOG_DEBUG, "%s: requested authentication for command \"%s\"",
current_server->name, x);
if (verbose)
printf("%s: requested authentication for command \"%s\"\n",
current_server->name, x);
free(x);
if (authenticate(current_server)) {
strcpy(lineout, last_command);
putaline();
r = xnntpreply(current_server, 0);
}
}
return r;
}
int nntpreply(const struct server *s) {
return xnntpreply(s, 1);
}
struct versions {
const char *name;
int is_evil;
};
/*
* NewsCache 0.99.17 and previous versions always
* reply with 223 0 <MID> when asked "STAT <MID>". This
* is a violation of RFC 977 and breaks posting.
*/
static struct versions stat_versions[] = {
{ "NewsCache 0.99.2 ", 1 },
{ "NewsCache 0.99.2", 0 },
{ "NewsCache 0.99.19", 0 },
{ "NewsCache 0.99.18", 0 },
{ "NewsCache 1.0.", 1 },
{ "NewsCache 1", 0 },
{ "NewsCache", 1 },
/* reported to be necessary by
* Robert Marshall <robert@chezmarshall.freeserve.co.uk>:
* nc news.cache.ntlworld.com 119
* 200 ntl NNTP news cache. posting ok (feedback to nntptrial-feedback@ntli.net)
* quit
* 205 NNTP Service closing connection - goodbye!
*/
{ "NNTP news cache", 1 },
};
static const int stat_count = sizeof(stat_versions)/sizeof(stat_versions[0]);
static struct versions date_versions[] = {
{ "NewsCache 0.99.22p", 0 },
{ "NewsCache 0.99.2 ", 1 },
{ "NewsCache 0.99.20", 1 },
{ "NewsCache 0.99.21", 1 },
{ "NewsCache 0.99.22", 1 },
{ "NewsCache 0.99.2", 0 },
{ "NewsCache 1.1.10 ", 1 },
{ "NewsCache 1.1.11 ", 1 },
{ "NewsCache 1.1.1 ", 1 },
{ "NewsCache 1.1.0", 1},
{ "NewsCache 1", 0},
{ "NewsCache", 1 }
};
static const int date_count = sizeof(date_versions)/sizeof(date_versions[0]);
static int check_linlist(const char *s, const struct versions *list, int count) {
int i;
for (i = 0; i < count; i++) {
if (strstr(s, list[i].name)) {
return list[i].is_evil;
}
}
return 0;
}
#define incopy(a) (*((struct in_addr *)a))
/*
* connect to upstream nntp server
*
* returns 200 for posting allowed, 201 for read-only;
* if connection failed, return 0
*/
int
nntpconnect(const struct server *upstream)
{
static /*@observer@*/ struct servent *sp;
struct servent sp_def;
#ifdef HAVE_IPV6
struct addrinfo hints, *ai;
struct addrinfo *volatile aii;
char buf[INET6_ADDRSTRLEN+1];
#else
struct sockaddr_in s_in;
struct hostent *hp;
char buf[16];
volatile int i;
#endif
int sock, reply, e, ds;
char *line;
if (upstream->port == 0) {
sp = getservbyname("nntp", "tcp");
if (sp == NULL) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: unable to find service name nntp/tcp");
return FALSE;
}
} else {
sp = &sp_def;
sp->s_port = htons(upstream->port);
}
sprintf(buf, "%hu", ntohs(sp->s_port));
/* Fetch the ip addresses of the given host. */
#ifdef HAVE_IPV6
memset((void *)&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_CANONNAME|AI_ADDRCONFIG;
e = getaddrinfo(upstream->name, buf, &hints, &ai);
if (e == 0) {
for (aii = ai; aii; aii = aii->ai_next) {
sock = socket(aii->ai_family, SOCK_STREAM, 0);
if (sock < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"error: cannot create inet/stream socket: %m");
break;
}
switch(aii->ai_family) {
case AF_INET6:
inet_ntop(aii->ai_family, &((const struct sockaddr_in6 *)aii->ai_addr)->sin6_addr, buf, sizeof(buf));
break;
case AF_INET:
inet_ntop(aii->ai_family, &((const struct sockaddr_in *)aii->ai_addr)->sin_addr, buf, sizeof(buf));
break;
default:
strcpy(buf, "UNKNOWN");
}
#else
hp = gethostbyname(upstream->name);
if (hp) {
/* Try to make connection to each of the addresses in turn. */
for (i = 0; (int *)(hp->h_addr_list)[i]; i++) {
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"error: cannot create inet/stream socket: %m");
break;
}
strcpy(buf, inet_ntoa(s_in.sin_addr));
#endif
if (sigsetjmp(timeout,1) != 0) {
ln_log(LNLOG_SWARNING, LNLOG_CTOP,
"warning: %s: connection to %s timed out",
upstream->name, buf);
(void)close(sock);
continue;
}
(void)mysigact(SIGALRM, SA_RESETHAND, timer, 0);
(void)alarm((unsigned)upstream->timeout);
#ifdef HAVE_IPV6
e = connect(sock, aii->ai_addr, aii->ai_addrlen);
if (e) {
ln_log(LNLOG_SWARNING, LNLOG_CTOP,
"warning: %s: connection to %s failed: %m",
upstream->name, buf);
}
#else
memset((void *)&s_in, 0, sizeof(s_in));
s_in.sin_family = hp->h_addrtype;
s_in.sin_port = sp->s_port;
s_in.sin_addr = incopy(hp->h_addr_list[i]);
e = connect(sock, (struct sockaddr *)&s_in, sizeof(s_in));
if (e)
ln_log(LNLOG_SWARNING, LNLOG_CTOP,
"warning: %s: connection to %s failed: %m",
upstream->name, inet_ntoa(s_in.sin_addr));
#endif
(void)alarm(0U);
(void)mysigact(SIGALRM, 0, SIG_DFL, 0);
if (e)
continue;
nntpout = fdopen(sock, "w");
if (nntpout == NULL) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"error: %s: fdopen(%d, \"w\") failed: %m",
upstream->name, sock);
break;
}
if ((ds = dup(sock)) < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"error: %s: dup(%d) failed returning %d: %m",
upstream->name, sock, ds);
break;
}
nntpin = fdopen(ds, "r");
if (nntpin == NULL) {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"error: %s: fdopen(%d, \"r\") failed: %m",
upstream->name, sock);
break;
}
reply = nntpreply(upstream);
if (reply == 200 || reply == 201) {
syslog(LOG_INFO, "%s: connected to %s:%hd, reply: %d",
upstream->name, buf, ntohs(sp->s_port), reply);
line = lastreply();
if (line == NULL) {
ln_log(LNLOG_SWARNING, LNLOG_CTOP,
"warning: %s: server disconnect or timeout before sending the greeting",
upstream->name);
nntpdisconnect();
continue;
}
if (strstr(line, "NNTPcache server V2.3")) {
/* NNTPcache 2.3.3 is still in widespread use, but it
* has Y2k bugs which have only been fixed in a beta
* version as of 2001-12-24. This 2.3 version is
* unsuitable for any use since 2000-01-01. */
static const char msg[] =
"error: %s: Server greeting \"%s\" hints to "
"NNTPcache v2.3.x. "
"This server has severe (Year 2000) bugs which make it "
"unsuitable for use with leafnode. "
"Ask the news server administrator to update to "
"NNTPcache v3.0.x or newer.";
ln_log(LNLOG_SERR, LNLOG_CTOP, msg, upstream->name, line);
nntpquit();
continue;
}
stat_is_evil = check_linlist(lastreply(), stat_versions,
stat_count);
date_is_evil = check_linlist(lastreply(), date_versions,
date_count);
if (stat_is_evil) {
syslog(LOG_WARNING, "warning: server \"%s\" greeting "
"\"%s\" hints to "
"an outdated version with broken "
"STAT command handling. Please ask the upstream "
"maintainer to update. "
"Emulating STAT with HEAD at the expense of bandwidth.",
upstream->name, line);
}
#ifdef HAVE_IPV6
if (ai)
freeaddrinfo(ai);
#endif
return reply;
} else { /* reply not 200 and not 201 */
char *ll = lastreply();
ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: received bogus greeting (%d): %s",
upstream->name, reply, ll ? ll : "(nil)");
nntpquit();
}
} /* end of IP-addresses for loop */
#ifdef HAVE_IPV6
if (!aii)
#else
if (!(int *)(hp->h_addr_list)[i])
#endif
ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
"%s: address list exhausted without establishing connection.",
upstream->name);
#ifdef HAVE_IPV6
if (ai)
freeaddrinfo(ai);
#endif
} else {
/* gethostbyname or getaddrinfo returned error */
const char *er;
#ifdef HAVE_IPV6
er = gai_strerror(e);
#else
switch(h_errno) {
case HOST_NOT_FOUND:
er = "No such host.";
break;
case NO_DATA:
er = "Name exists in DNS, but has no associated address (\"A\"-type DNS resource record).";
break;
case NO_RECOVERY:
er = "Unexpected permanent server failure.";
break;
case TRY_AGAIN:
er = "Temporary DNS error, please try again later.";
break;
default:
er = "Unknown h_errno value.";
break;
}
#endif
ln_log(LNLOG_SWARNING, LNLOG_CTOP,
"warning: %s: cannot resolve host name: %s", upstream->name, er);
}
return FALSE;
} /* end of connect function */
/*
* disconnect from upstream server
*/
void
nntpdisconnect(void)
{
if (nntpin) {
fclose(nntpin);
nntpin = NULL;
}
if (nntpout) {
fclose(nntpout);
nntpout = NULL;
}
}
void
nntpquit(void)
{
xsnprintf(lineout, SIZE_lineout, "QUIT\r\n"); /* say it, then just exit :) */
putaline();
nntpdisconnect();
}
#ifdef MAIN
int verbose=0;
int debug=0;
int main(int argc, char **argv) {
int i = 1;
while (i < argc) {
int stat_is_evil;
int date_is_evil;
stat_is_evil = check_linlist(argv[i], stat_versions,
stat_count);
date_is_evil = check_linlist(argv[i], date_versions,
date_count);
printf("%s: stat_evil=%d, date_evil=%d\n",
argv[i], stat_is_evil, date_is_evil);
i++;
}
return 0;
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1