/* nntputil.c -- misc nntp-related stuff Written by Arnt Gulbrandsen and copyright 1995 Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47 22646949. Modified by Cornelius Krasel and Randolf Skerka . Copyright of the modifications 1997. Modified by Kent Robotti . Copyright of the modifications 1998. Modified by Markus Enzenberger . Copyright of the modifications 1998. Modified by Cornelius Krasel . Copyright of the modifications 1998, 1999. Modified by Ralf Wildenhues . Copyright of the modifications 2002. Modified by Matthias Andree . 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 #include #include #include #include #ifndef __LCLINT__ #include #endif /* not __LCLINT__ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 "); 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 when asked "STAT ". 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 : * 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