leafnode  1.12.0
About: Leafnode is a store & forward NNTP proxy for small (dialup) sites.
  Fossies Dox: leafnode-1.12.0.tar.xz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

nntputil.c
Go to the documentation of this file.
1/*
2nntputil.c -- misc nntp-related stuff
3
4Written by Arnt Gulbrandsen <agulbra@troll.no> and copyright 1995
5Troll Tech AS, Postboks 6133 Etterstad, 0602 Oslo, Norway, fax +47
622646949.
7Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>
8and Randolf Skerka <Randolf.Skerka@gmx.de>.
9Copyright of the modifications 1997.
10Modified by Kent Robotti <robotti@erols.com>. Copyright of the
11modifications 1998.
12Modified by Markus Enzenberger <enz@cip.physik.uni-muenchen.de>.
13Copyright of the modifications 1998.
14Modified by Cornelius Krasel <krasel@wpxx02.toxi.uni-wuerzburg.de>.
15Copyright of the modifications 1998, 1999.
16Modified by Ralf Wildenhues <ralf.wildenhues@gmx.de>.
17Copyright of the modifications 2002.
18Modified by Matthias Andree <matthias.andree@gmx.de>.
19Copyright of the modifications 2000 - 2013.
20
21See file COPYING for restrictions on the use of this software.
22*/
23
24#include "leafnode.h"
25#include "system.h"
26#include "mysigact.h"
27
28#include <fcntl.h>
29#include <sys/uio.h>
30#include <sys/param.h>
31#include <sys/socket.h>
32#include <netinet/in.h>
33#ifndef __LCLINT__
34#include <arpa/inet.h>
35#endif /* not __LCLINT__ */
36#include <ctype.h>
37#include <errno.h>
38#include <limits.h>
39#include <stdlib.h>
40#include <netdb.h>
41#include <setjmp.h>
42#include <signal.h>
43#include <stdio.h>
44#include <string.h>
45#include <syslog.h>
46#include <sys/stat.h>
47#include <sys/types.h>
48#include <unistd.h>
49#include <dirent.h>
50
51#include "ln_log.h"
52
55
56/*@relnull@*//*@dependent@*/ FILE *nntpin = NULL;
57/*@relnull@*//*@dependent@*/ FILE *nntpout = NULL;
58
61
62static int xnntpreply(const struct server *, int);
63
64static void authsucc(const struct server *current_server) {
65 if (verbose) {
66 printf("%s: authenticated as %s\n", current_server->name, current_server->username);
67 syslog(LOG_INFO, "%s: authenticated as %s", current_server->name, current_server->username);
68 }
69}
70
71/*
7205/26/97 - T. Sweeney - Send a string out, keeping a copy in reserve.
73*/
74void
76{
77 if (debug >= 1) {
78 char y, *x = lineout + strcspn(lineout, "\r\n");
79
80 y = *x; *x = '\0';
81 syslog(LOG_DEBUG, ">%s", lineout);
82 *x = y;
83 }
84 strcpy(last_command, lineout); /* RATS: ignore */
85 fputs(lineout, nntpout);
86 fflush(nntpout);
87}
88
89/*
90 * Authenticate ourselves at a remote server.
91 * Returns TRUE if authentication succeeds, FALSE if it does not.
92 * Error will have been logged in case of a FALSE return,
93 * no log output if TRUE returned.
94 */
95int
96authenticate(const struct server *current_server)
97{
98 int reply;
99
100 if (!current_server) {
102 "authenticate: internal error: current_server is NULL, aborting");
103 abort();
104 }
105
106 if (!current_server->username) {
107 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: username needed for authentication",
108 current_server->name);
109 return FALSE;
110 }
111
112 fprintf(nntpout, "AUTHINFO USER %s\r\n", current_server->username);
113 fflush(nntpout);
114 if (debugmode)
115 syslog(LOG_DEBUG, ">AUTHINFO USER %s", current_server->username);
116
117 reply = xnntpreply(current_server, 0);
118 if (reply == 281) {
119 authsucc(current_server);
120 return TRUE;
121 } else if (reply != 381) {
122 ln_log(LNLOG_SERR, LNLOG_CSERVER, "error: %s: AUTHINFO USER rejected: %03d",
123 current_server->name, reply);
124 return FALSE;
125 }
126
127 if (!current_server->password) {
128 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: password needed for authentication",
129 current_server->name);
130 return FALSE;
131 }
132 /* DO NOT LOG THIS: */
133 fprintf(nntpout, "AUTHINFO PASS %s\r\n", current_server->password);
134 fflush(nntpout);
135 if (debugmode)
136 syslog(LOG_DEBUG, ">AUTHINFO PASS <password not shown>");
137
138 reply = xnntpreply(current_server, 0);
139
140 if (reply != 281) {
141 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: AUTHINFO PASS failed: %03d", reply);
142 return FALSE;
143 }
144 authsucc(current_server);
145 return TRUE;
146}
147
148static size_t lllen = 0;
149static
150 /*@null@*/
151 /*@owned@*/
152char *llstr = NULL;
153
155{
156 if (!llstr) return;
157 free(llstr);
158 llstr = NULL;
159}
160
161/*@dependent@*//*@null@*/ char *
163{
164 return llstr;
165}
166
167/**
168 * decode an NNTP reply number
169 * reads a line from the server and returns an integer
170 *
171 * 498 is used to mean "protocol error", like smail,
172 * and includes timeout and "server disconnect" (EOF)
173 * conditions
174 *
175 * the text returned is stored in lllen/llstr
176 * for later retrieval by lastreply()
177 *
178 * from Tim Sweeney: retry in case of AUTHINFO failure.
179 */
180static int
181xnntpreply(const struct server *current_server,
182 /** set this to true to enable authentication after 480 reply */
183 int may_auth)
184{
185 char *response;
186 int r = 0;
187 int c;
188
189 do {
190 response = mgetaline(nntpin);
191 if (!response) {
192 if (llstr)
193 free(llstr);
194 llstr = NULL;
195 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: NNTP server went away (server disconnect or timeout)");
196 return 498;
197 }
198 if (debug == 1) syslog(LOG_DEBUG, "<%s", response);
199
200 /* cache line */
201 if (strlen(response) > lllen || !llstr) {
202 if (llstr)
203 free(llstr);
204 lllen = strlen(response);
205 llstr = critmalloc(lllen + 1, "nntpreply");
206 }
207 strcpy(llstr, response); /* RATS: ignore */
208 if (strlen(response) > 2
209 && isdigit((unsigned char)response[0])
210 && isdigit((unsigned char)response[1])
211 && isdigit((unsigned char)response[2])
212 && ((response[3] == ' ')
213 || (response[3] == '\0')
214 || (response[3] == '-'))) {
215 int rl;
216 rl = atoi(response);
217 if (r > 0 && r != rl) {
218 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: multiline reply with variant error code (%d vs. %d), last line: %s", r, rl, response);
219 r = 498; /* protocol error */
220 } else
221 r = rl;
222 c = (response[3] == '-');
223 } else {
224 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: syntax error in reply \"%s\"", response);
225 c = 0;
226 r = 498; /* protocol error */
227 }
228 } while(c);
229
230 if (r == 480 && may_auth) { /* need to authenticate */
231 char *x = critstrdup(last_command, "xnntpreply");
232 x[strcspn(last_command, "\r\n")] = '\0';
233 if (debugmode)
234 syslog(LOG_DEBUG, "%s: requested authentication for command \"%s\"",
235 current_server->name, x);
236 if (verbose)
237 printf("%s: requested authentication for command \"%s\"\n",
238 current_server->name, x);
239 free(x);
240 if (authenticate(current_server)) {
241 strcpy(lineout, last_command);
242 putaline();
243 r = xnntpreply(current_server, 0);
244 }
245 }
246 return r;
247}
248
249int nntpreply(const struct server *s) {
250 return xnntpreply(s, 1);
251}
252
253struct versions {
254 const char *name;
256};
257
258/*
259 * NewsCache 0.99.17 and previous versions always
260 * reply with 223 0 <MID> when asked "STAT <MID>". This
261 * is a violation of RFC 977 and breaks posting.
262 */
263static struct versions stat_versions[] = {
264 { "NewsCache 0.99.2 ", 1 },
265 { "NewsCache 0.99.2", 0 },
266 { "NewsCache 0.99.19", 0 },
267 { "NewsCache 0.99.18", 0 },
268 { "NewsCache 1.0.", 1 },
269 { "NewsCache 1", 0 },
270 { "NewsCache", 1 },
271 /* reported to be necessary by
272 * Robert Marshall <robert@chezmarshall.freeserve.co.uk>:
273 * nc news.cache.ntlworld.com 119
274 * 200 ntl NNTP news cache. posting ok (feedback to nntptrial-feedback@ntli.net)
275 * quit
276 * 205 NNTP Service closing connection - goodbye!
277 */
278 { "NNTP news cache", 1 },
279};
280static const int stat_count = sizeof(stat_versions)/sizeof(stat_versions[0]);
281
282static struct versions date_versions[] = {
283 { "NewsCache 0.99.22p", 0 },
284 { "NewsCache 0.99.2 ", 1 },
285 { "NewsCache 0.99.20", 1 },
286 { "NewsCache 0.99.21", 1 },
287 { "NewsCache 0.99.22", 1 },
288 { "NewsCache 0.99.2", 0 },
289 { "NewsCache 1.1.10 ", 1 },
290 { "NewsCache 1.1.11 ", 1 },
291 { "NewsCache 1.1.1 ", 1 },
292 { "NewsCache 1.1.0", 1},
293 { "NewsCache 1", 0},
294 { "NewsCache", 1 }
295};
296static const int date_count = sizeof(date_versions)/sizeof(date_versions[0]);
297
298static int check_linlist(const char *s, const struct versions *list, int count) {
299 int i;
300
301 for (i = 0; i < count; i++) {
302 if (strstr(s, list[i].name)) {
303 return list[i].is_evil;
304 }
305 }
306 return 0;
307}
308
309#define incopy(a) (*((struct in_addr *)a))
310
311/*
312 * connect to upstream nntp server
313 *
314 * returns 200 for posting allowed, 201 for read-only;
315 * if connection failed, return 0
316 */
317int
318nntpconnect(const struct server *upstream)
319{
320 static /*@observer@*/ struct servent *sp;
321 struct servent sp_def;
322#ifdef HAVE_IPV6
323 struct addrinfo hints, *ai;
324 struct addrinfo *volatile aii;
325 char buf[INET6_ADDRSTRLEN+1];
326#else
327 struct sockaddr_in s_in;
328 struct hostent *hp;
329 char buf[16];
330 volatile int i;
331#endif
332 int sock, reply, e, ds;
333 char *line;
334
335 if (upstream->port == 0) {
336 sp = getservbyname("nntp", "tcp");
337 if (sp == NULL) {
338 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: unable to find service name nntp/tcp");
339 return FALSE;
340 }
341 } else {
342 sp = &sp_def;
343 sp->s_port = htons(upstream->port);
344 }
345
346 sprintf(buf, "%u", (unsigned int)ntohs(sp->s_port));
347
348 /* Fetch the ip addresses of the given host. */
349#ifdef HAVE_IPV6
350 memset((void *)&hints, 0, sizeof(hints));
351 hints.ai_family = AF_UNSPEC;
352 hints.ai_socktype = SOCK_STREAM;
353 hints.ai_protocol = IPPROTO_TCP;
354 hints.ai_flags = AI_CANONNAME|AI_ADDRCONFIG;
355
356 e = getaddrinfo(upstream->name, buf, &hints, &ai);
357 if (e == 0) {
358 for (aii = ai; aii; aii = aii->ai_next) {
359 sock = socket(aii->ai_family, SOCK_STREAM, 0);
360 if (sock < 0) {
362 "error: cannot create inet/stream socket: %m");
363 break;
364 }
365 switch(aii->ai_family) {
366 case AF_INET6:
367 inet_ntop(aii->ai_family, &((const struct sockaddr_in6 *)aii->ai_addr)->sin6_addr, buf, sizeof(buf));
368 break;
369 case AF_INET:
370 inet_ntop(aii->ai_family, &((const struct sockaddr_in *)aii->ai_addr)->sin_addr, buf, sizeof(buf));
371 break;
372 default:
373 strcpy(buf, "UNKNOWN");
374 }
375#else
376 hp = gethostbyname(upstream->name);
377 if (hp) {
378 /* Try to make connection to each of the addresses in turn. */
379 for (i = 0; (int *)(hp->h_addr_list)[i]; i++) {
380 sock = socket(AF_INET, SOCK_STREAM, 0);
381 if (sock < 0) {
383 "error: cannot create inet/stream socket: %m");
384 break;
385 }
386 memset((void *)&s_in, 0, sizeof(s_in));
387 s_in.sin_family = hp->h_addrtype;
388 s_in.sin_port = sp->s_port;
389 s_in.sin_addr = incopy(hp->h_addr_list[i]);
390
391 strcpy(buf, inet_ntoa(s_in.sin_addr));
392#endif
393
394 if (sigsetjmp(timeout,1) != 0) {
396 "warning: %s: connection to %s timed out",
397 upstream->name, buf);
398 (void)close(sock);
399 continue;
400 }
401
402 (void)mysigact(SIGALRM, SA_RESETHAND, timer, 0);
403 (void)alarm((unsigned)upstream->timeout);
404
405#ifdef HAVE_IPV6
406 e = connect(sock, aii->ai_addr, aii->ai_addrlen);
407 if (e) {
409 "warning: %s: connection to %s failed: %m",
410 upstream->name, buf);
411 }
412#else
413 e = connect(sock, (struct sockaddr *)&s_in, sizeof(s_in));
414 if (e)
416 "warning: %s: connection to %s failed: %m",
417 upstream->name, inet_ntoa(s_in.sin_addr));
418#endif
419 (void)alarm(0U);
420 (void)mysigact(SIGALRM, 0, SIG_DFL, 0);
421 if (e)
422 continue;
423
424 nntpout = fdopen(sock, "w");
425 if (nntpout == NULL) {
427 "error: %s: fdopen(%d, \"w\") failed: %m",
428 upstream->name, sock);
429 break;
430 }
431
432 if ((ds = dup(sock)) < 0) {
434 "error: %s: dup(%d) failed returning %d: %m",
435 upstream->name, sock, ds);
436 break;
437 }
438
439 nntpin = fdopen(ds, "r");
440 if (nntpin == NULL) {
442 "error: %s: fdopen(%d, \"r\") failed: %m",
443 upstream->name, sock);
444 break;
445 }
446
447 reply = nntpreply(upstream);
448 if (reply == 200 || reply == 201) {
449 ln_log(LNLOG_SINFO, LNLOG_CSERVER, "%s: connected to %s:%u, reply: %d",
450 upstream->name, buf, (unsigned int)ntohs(sp->s_port), reply);
451 line = lastreply();
452 if (line == NULL) {
454 "warning: %s: server disconnect or timeout before sending the greeting",
455 upstream->name);
457 continue;
458 }
459
460 if (strstr(line, "NNTPcache server V2.3")) {
461 /* NNTPcache 2.3.3 is still in widespread use, but it
462 * has Y2k bugs which have only been fixed in a beta
463 * version as of 2001-12-24. This 2.3 version is
464 * unsuitable for any use since 2000-01-01. */
465 static const char msg[] =
466 "error: %s: Server greeting \"%s\" hints to "
467 "NNTPcache v2.3.x. "
468 "This server has severe (Year 2000) bugs which make it "
469 "unsuitable for use with leafnode. "
470 "Ask the news server administrator to update to "
471 "NNTPcache v3.0.x or newer.";
472 ln_log(LNLOG_SERR, LNLOG_CTOP, msg, upstream->name, line);
473 nntpquit();
474 continue;
475 }
477 stat_count);
479 date_count);
480 if (stat_is_evil) {
481 syslog(LOG_WARNING, "warning: server \"%s\" greeting "
482 "\"%s\" hints to "
483 "an outdated version with broken "
484 "STAT command handling. Please ask the upstream "
485 "maintainer to update. "
486 "Emulating STAT with HEAD at the expense of bandwidth.",
487 upstream->name, line);
488 }
489#ifdef HAVE_IPV6
490 if (ai)
491 freeaddrinfo(ai);
492#endif
493 return reply;
494 } else { /* reply not 200 and not 201 */
495 char *ll = lastreply();
496 ln_log(LNLOG_SERR, LNLOG_CTOP, "error: %s: received bogus greeting (%d): %s",
497 upstream->name, reply, ll ? ll : "(nil)");
498 nntpquit();
499 }
500 } /* end of IP-addresses for loop */
501#ifdef HAVE_IPV6
502 if (!aii)
503#else
504 if (!(int *)(hp->h_addr_list)[i])
505#endif
507 "%s: address list exhausted without establishing connection.",
508 upstream->name);
509#ifdef HAVE_IPV6
510 if (ai)
511 freeaddrinfo(ai);
512#endif
513 } else {
514 /* gethostbyname or getaddrinfo returned error */
515 const char *er;
516#ifdef HAVE_IPV6
517 er = gai_strerror(e);
518#else
519 switch(h_errno) {
520 case HOST_NOT_FOUND:
521 er = "No such host.";
522 break;
523 case NO_DATA:
524 er = "Name exists in DNS, but has no associated address (\"A\"-type DNS resource record).";
525 break;
526 case NO_RECOVERY:
527 er = "Unexpected permanent server failure.";
528 break;
529 case TRY_AGAIN:
530 er = "Temporary DNS error, please try again later.";
531 break;
532 default:
533 er = "Unknown h_errno value.";
534 break;
535 }
536#endif
538 "warning: %s: cannot resolve host name: %s", upstream->name, er);
539 }
540 return FALSE;
541} /* end of connect function */
542
543/*
544 * disconnect from upstream server
545 */
546void
548{
549 if (nntpin) {
550 fclose(nntpin);
551 nntpin = NULL;
552 }
553 if (nntpout) {
554 fclose(nntpout);
555 nntpout = NULL;
556 }
557}
558
559void
561{
562 xsnprintf(lineout, SIZE_lineout, "QUIT\r\n"); /* say it, then just exit :) */
563 putaline();
565}
566
567#ifdef MAIN
568int verbose=0;
569int debug=0;
570
571int main(int argc, char **argv) {
572 int i = 1;
573 while (i < argc) {
574 int stat_is_evil;
575 int date_is_evil;
576
578 stat_count);
580 date_count);
581
582 printf("%s: stat_evil=%d, date_evil=%d\n",
583 argv[i], stat_is_evil, date_is_evil);
584
585 i++;
586 }
587 return 0;
588}
589#endif
int main(void)
Definition: amiroot.c:12
int verbose
Definition: applyfilter.c:31
int debug
Definition: applyfilter.c:30
int debugmode
Definition: configutil.c:67
char * critstrdup(const char *source, const char *message)
Definition: critmem.c:92
char * critmalloc(size_t size, const char *message)
Definition: critmem.c:61
char * mgetaline(FILE *f)
Definition: mgetaline.c:51
sigjmp_buf timeout
Definition: mgetaline.c:39
void timer(int sig)
Definition: mgetaline.c:41
#define TRUE
Definition: leafnode.h:29
#define FALSE
Definition: leafnode.h:32
#define SIZE_lineout
Definition: leafnode.h:279
int xsnprintf(char *str, size_t n, const char *format,...)
Definition: miscutil.c:798
void ln_log(int sev, int ctx, const char *format,...)
Definition: ln_log.c:103
#define LNLOG_CSERVER
Definition: ln_log.h:23
#define LNLOG_SERR
Definition: ln_log.h:13
#define LNLOG_SWARNING
Definition: ln_log.h:14
#define LNLOG_SINFO
Definition: ln_log.h:16
#define LNLOG_CTOP
Definition: ln_log.h:22
#define LNLOG_SNOTICE
Definition: ln_log.h:15
const char * name
Definition: miscutil.c:126
int mysigact(int sig, int flags, void(*func)(int), int blockthis)
Definition: mysigact.c:9
static void list(struct newsgroup *g, const int what, const char *pattern)
Definition: nntpd.c:800
void nntpdisconnect(void)
Definition: nntputil.c:547
int stat_is_evil
Definition: nntputil.c:59
static int check_linlist(const char *s, const struct versions *list, int count)
Definition: nntputil.c:298
void freelastreply(void)
Definition: nntputil.c:154
FILE * nntpin
Definition: nntputil.c:56
int nntpreply(const struct server *s)
Definition: nntputil.c:249
void putaline(void)
Definition: nntputil.c:75
static struct versions stat_versions[]
Definition: nntputil.c:263
static size_t lllen
Definition: nntputil.c:148
int nntpconnect(const struct server *upstream)
Definition: nntputil.c:318
static void authsucc(const struct server *current_server)
Definition: nntputil.c:64
static char * llstr
Definition: nntputil.c:152
void nntpquit(void)
Definition: nntputil.c:560
static const int date_count
Definition: nntputil.c:296
int date_is_evil
Definition: nntputil.c:60
FILE * nntpout
Definition: nntputil.c:57
char last_command[1024+1]
Definition: nntputil.c:53
char lineout[1024+1]
Definition: nntputil.c:54
static struct versions date_versions[]
Definition: nntputil.c:282
int authenticate(const struct server *current_server)
Definition: nntputil.c:96
static const int stat_count
Definition: nntputil.c:280
static int xnntpreply(const struct server *, int)
Definition: nntputil.c:181
#define incopy(a)
Definition: nntputil.c:309
char * lastreply(void)
Definition: nntputil.c:162
char * password
Definition: leafnode.h:222
char * username
Definition: leafnode.h:221
char * name
Definition: leafnode.h:220
unsigned int port
Definition: leafnode.h:224
int timeout
Definition: leafnode.h:226
const char * name
Definition: nntputil.c:254
int is_evil
Definition: nntputil.c:255