Logo Search packages:      
Sourcecode: leafnode version File versions

nntputil.c

/*
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@web.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

Generated by  Doxygen 1.6.0   Back to index