"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/nntplib.c" (23 Dec 2022, 63116 Bytes) of package /linux/misc/tin-2.6.2.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "nntplib.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : nntplib.c
    4  *  Author    : S. Barber & I. Lea
    5  *  Created   : 1991-01-12
    6  *  Updated   : 2022-12-23
    7  *  Notes     : NNTP client routines taken from clientlib.c 1.5.11 (1991-02-10)
    8  *  Copyright : (c) Copyright 1991-99 by Stan Barber & Iain Lea
    9  *              Permission is hereby granted to copy, reproduce, redistribute
   10  *              or otherwise use this software  as long as: there is no
   11  *              monetary  profit  gained  specifically  from the use or
   12  *              reproduction or this software, it is not  sold, rented,
   13  *              traded or otherwise marketed, and this copyright notice
   14  *              is included prominently in any copy made.
   15  */
   16 
   17 
   18 #ifndef TIN_H
   19 #   include "tin.h"
   20 #endif /* !TIN_H */
   21 #ifndef TCURSES_H
   22 #   include "tcurses.h"
   23 #endif /* !TCURSES_H */
   24 #ifndef TNNTP_H
   25 #   include "tnntp.h"
   26 #endif /* !TNNTP_H */
   27 
   28 char *nntp_server = NULL;
   29 #ifdef NO_POSTING
   30     t_bool can_post = FALSE;
   31 #else
   32     t_bool can_post = TRUE;
   33 #endif /* NO_POSTING */
   34 
   35 #ifdef NNTP_ABLE
   36     /* Flag to show whether tin did reconnect in last get_server() */
   37     t_bool reconnected_in_last_get_server = FALSE;
   38     /* Flag used in LIST ACVTIVE loop */
   39     t_bool did_reconnect = FALSE;
   40 #endif /* NNTP_ABLE */
   41 
   42 #ifdef NNTP_ABLE
   43     /* Copy of last NNTP command sent, so we can retry it if needed */
   44     static char last_put[NNTP_STRLEN];
   45     static constext *xover_cmds = "XOVER";
   46     static constext *xhdr_cmds = "XHDR";
   47     /* Set so we don't reconnect just to QUIT */
   48     static t_bool quitting = FALSE;
   49 #endif /* NNTP_ABLE */
   50 
   51 /*
   52  * local prototypes
   53  */
   54 #ifdef NNTP_ABLE
   55     static int mode_reader(t_bool *sec);
   56     static int reconnect(int retry);
   57     static int server_init(char *machine, const char *cservice, unsigned short port, char *text, size_t mlen);
   58     static void close_server(t_bool send_no_quit);
   59     static void list_motd(void);
   60 #   ifdef INET6
   61         static int get_tcp6_socket(char *machine, unsigned short port);
   62 #   else
   63         static int get_tcp_socket(char *machine, char *service, unsigned short port);
   64 #   endif /* INET6 */
   65 #   ifdef DECNET
   66         static int get_dnet_socket(char *machine, char *service);
   67 #   endif /* DECNET */
   68 
   69 struct simplebuf {
   70     unsigned char buf[4096];
   71     unsigned lb; /* lower bound */
   72     unsigned ub; /* upper bound */
   73 };
   74 
   75 struct nntpbuf {
   76     struct simplebuf rd;
   77     struct simplebuf wr;
   78     int fd;
   79 #ifdef NNTPS_ABLE
   80     void *tls_ctx;
   81 #endif /* NNTPS_ABLE */
   82 };
   83 
   84 #ifdef NNTPS_ABLE
   85 #   define NNTPBUF_INITIALIZER { { {0}, 0, 0 }, { {0}, 0, 0 }, -1, NULL }
   86 #else
   87 #   define NNTPBUF_INITIALIZER { { {0}, 0, 0 }, { {0}, 0, 0 }, -1 }
   88 #endif /* NNTPS_ABLE */
   89 
   90 static struct nntpbuf nntp_buf = NNTPBUF_INITIALIZER;
   91 
   92 static int nntpbuf_refill(struct nntpbuf *buf);
   93 static int nntpbuf_flush(struct nntpbuf* buf);
   94 static int nntpbuf_puts(const char* data, struct nntpbuf* buf);
   95 static int nntpbuf_getc(struct nntpbuf *buf);
   96 static int nntpbuf_ungetc(int c, struct nntpbuf *buf);
   97 static char *nntpbuf_gets(char *s, int size, struct nntpbuf *buf);
   98 static void nntpbuf_close(struct nntpbuf *buf);
   99 static int nntpbuf_is_open(struct nntpbuf *buf);
  100 
  101 #endif /* NNTP_ABLE */
  102 
  103 
  104 /*
  105  * getserverbyfile(file)
  106  *
  107  * Get the name of a server from a named file.
  108  *  Handle white space and comments.
  109  *  Use NNTPSERVER environment variable if set.
  110  *
  111  *  Parameters: "file" is the name of the file to read.
  112  *
  113  *  Returns: Pointer to static data area containing the
  114  *           first non-ws/comment line in the file.
  115  *           NULL on error (or lack of entry in file).
  116  *
  117  *  Side effects: None.
  118  */
  119 char *
  120 getserverbyfile(
  121     const char *file)
  122 {
  123     static char buf[256];
  124 #ifdef NNTP_ABLE
  125     FILE *fp;
  126     char *cp;
  127 #   if !defined(HAVE_SETENV) && defined(HAVE_PUTENV)
  128     char tmpbuf[256];
  129     char *new_env;
  130     static char *old_env = NULL;
  131 #   endif /* !HAVE_SETENV && HAVE_PUTENV */
  132 #endif /* NNTP_ABLE */
  133 
  134     if (read_saved_news) {
  135         STRCPY(buf, "reading saved news");
  136         return buf;
  137     }
  138 
  139     if (!read_news_via_nntp) {
  140         STRCPY(buf, "local");   /* what if a server is named "local"? */
  141         return buf;
  142     }
  143 
  144 #ifdef NNTP_ABLE
  145     if (cmdline.args & CMDLINE_NNTPSERVER) {
  146         get_nntpserver(buf, sizeof(buf), cmdline.nntpserver);
  147 #   ifdef HAVE_SETENV
  148         setenv("NNTPSERVER", buf, 1);
  149 #   else
  150 #       ifdef HAVE_PUTENV
  151         snprintf(tmpbuf, sizeof(tmpbuf), "NNTPSERVER=%s", buf);
  152         new_env = my_strdup(tmpbuf);
  153         putenv(new_env);
  154         FreeIfNeeded(old_env);
  155         old_env = new_env; /* we are 'leaking' the last malloced mem at exit here */
  156 #       endif /* HAVE_PUTENV */
  157 #   endif /* HAVE_SETENV */
  158         return buf;
  159     }
  160 
  161     if ((cp = getenv("NNTPSERVER")) != NULL) {
  162         get_nntpserver(buf, sizeof(buf), cp);
  163         return buf;
  164     }
  165 
  166     if (file == NULL)
  167         return NULL;
  168 
  169     if ((fp = fopen(file, "r")) != NULL) {
  170 
  171         while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
  172             if (*buf == '\n' || *buf == '#')
  173                 continue;
  174 
  175             if ((cp = strrchr(buf, '\n')) != NULL)
  176                 *cp = '\0';
  177 
  178             (void) fclose(fp);
  179             return buf;
  180         }
  181         (void) fclose(fp);
  182     }
  183 
  184 #   ifdef NNTP_DEFAULT_SERVER
  185     if (*(NNTP_DEFAULT_SERVER))
  186         return strcpy(buf, NNTP_DEFAULT_SERVER);
  187 #   endif /* NNTP_DEFAULT_SERVER */
  188 
  189 #endif /* NNTP_ABLE */
  190     return NULL;    /* No entry */
  191 }
  192 
  193 
  194 /*
  195  * server_init  Get a connection to the remote server.
  196  *
  197  *  Parameters: "machine" is the machine to connect to.
  198  *          "service" is the service to connect to on the machine.
  199  *          "port" is the service port to connect to.
  200  *
  201  *  Returns:    server's initial response code
  202  *          or -errno
  203  *
  204  *  Side effects:   Connects to server.
  205  *          "nntp_rd_fp" and "nntp_wr_fp" are fp's
  206  *          for reading and writing to server.
  207  *          "text" is updated to contain the rest of response string from server
  208  */
  209 #ifdef NNTP_ABLE
  210 static int
  211 server_init(
  212     char *machine,
  213     const char *cservice,   /* usually a literal */
  214     unsigned short port,
  215     char *text,
  216     size_t mlen)
  217 {
  218 #   ifndef INET6
  219     char temp[256];
  220     char *service = strncpy(temp, cservice, sizeof(temp) - 1); /* ...calls non-const funcs */
  221 #   endif /* !INET6 */
  222     int sock_fd;
  223 
  224 #   ifdef DECNET
  225     char *cp;
  226 
  227     cp = strchr(machine, ':');
  228 
  229     if (cp && cp[1] == ':') {
  230         *cp = '\0';
  231         sock_fd = get_dnet_socket(machine, service);
  232     } else
  233         sock_fd = get_tcp_socket(machine, service, port);
  234 #   else
  235 #       ifdef INET6
  236     sock_fd = get_tcp6_socket(machine, port);
  237 #       else
  238     sock_fd = get_tcp_socket(machine, service, port);
  239 #       endif /* INET6 */
  240 #   endif /* DECNET */
  241 
  242     if (sock_fd < 0)
  243         return sock_fd;
  244 
  245 #   ifdef TLI /* Transport Level Interface */
  246     if (t_sync(sock_fd) < 0) {  /* Sync up new fd with TLI */
  247         t_error("server_init: t_sync()");
  248         close(sock_fd);
  249         return -EPROTO;
  250     }
  251 #   endif /* TLI */
  252 
  253 #ifdef NNTPS_ABLE
  254 
  255     if (use_nntps) {
  256         int result;
  257 
  258         result = tintls_open(machine, sock_fd, &nntp_buf.tls_ctx);
  259         if (result < 0) {
  260             return result;
  261         }
  262 
  263         result = tintls_handshake(nntp_buf.tls_ctx);
  264         if (result < 0) {
  265             return result;
  266         }
  267     }
  268 
  269 #endif /* NNTPS_ABLE */
  270 
  271     nntp_buf.fd = sock_fd;
  272 
  273     last_put[0] = '\0';     /* no retries in get_respcode */
  274     /*
  275      * Now get the server's signon message
  276      */
  277     return (get_respcode(text, mlen));
  278 }
  279 #endif /* NNTP_ABLE */
  280 
  281 
  282 /*
  283  * get_tcp_socket -- get us a socket connected to the specified server.
  284  *
  285  *  Parameters: "machine" is the machine the server is running on.
  286  *          "service" is the service to connect to on the server.
  287  *          "port" is the port to connect to on the server.
  288  *
  289  *  Returns:    Socket connected to the server if all if ok
  290  *          EPROTO      for internal socket errors
  291  *          EHOSTUNREACH    if specified NNTP port/server can't be located
  292  *          errno       any valid errno value on connection
  293  *
  294  *  Side effects:   Connects to server.
  295  *
  296  *  Errors:     Returned & printed via perror.
  297  */
  298 #if defined(NNTP_ABLE) && !defined(INET6)
  299 static int
  300 get_tcp_socket(
  301     char *machine,      /* remote host */
  302     char *service,      /* nttp/smtp etc. */
  303     unsigned short port)    /* tcp port number */
  304 {
  305     int s = -1;
  306     int save_errno = 0;
  307     struct sockaddr_in sock_in;
  308 #   ifdef TLI /* Transport Level Interface */
  309     char device[20];
  310     char *env_device;
  311     extern int t_errno;
  312     extern struct hostent *gethostbyname();
  313     struct hostent *hp;
  314     struct t_call *callptr;
  315 
  316     /*
  317      * Create a TCP transport endpoint.
  318      */
  319     if ((env_device = getenv("DEV_TCP")) != NULL) /* SCO uses DEV_TCP, most other OS use /dev/tcp */
  320         STRCPY(device, env_device);
  321     else
  322         strcpy(device, "/dev/tcp");
  323 
  324     if ((s = t_open(device, O_RDWR, (struct t_info *) 0)) < 0) {
  325         t_error(txt_error_topen);
  326         return -EPROTO;
  327     }
  328     if (t_bind(s, (struct t_bind *) 0, (struct t_bind *) 0) < 0) {
  329         t_error("t_bind");
  330         t_close(s);
  331         return -EPROTO;
  332     }
  333     memset((char *) &sock_in, '\0', sizeof(sock_in));
  334     sock_in.sin_family = AF_INET;
  335     sock_in.sin_port = htons(port);
  336 
  337     if (!isdigit((unsigned char) *machine)
  338 #       ifdef HAVE_INET_ATON
  339         || !inet_aton(machine, &sock_in)
  340 #       else
  341 #           ifdef HAVE_INET_ADDR
  342         || (long) (sock_in.sin_addr.s_addr = inet_addr(machine)) == INADDR_NONE
  343 #           endif /* HAVE_INET_ADDR */
  344 #       endif /* HAVE_INET_ATON */
  345     ) {
  346         if ((hp = gethostbyname(machine)) == NULL) {
  347             my_fprintf(stderr, _(txt_gethostbyname), "gethostbyname() ", machine);
  348             t_close(s);
  349             return -EHOSTUNREACH;
  350         }
  351         memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
  352     }
  353 
  354     /*
  355      * Allocate a t_call structure and initialize it.
  356      * Let t_alloc() initialize the addr structure of the t_call structure.
  357      */
  358     if ((callptr = (struct t_call *) t_alloc(s, T_CALL, T_ADDR)) == NULL) {
  359         t_error("t_alloc");
  360         t_close(s);
  361         return -EPROTO;
  362     }
  363 
  364     callptr->addr.maxlen = sizeof(sock_in);
  365     callptr->addr.len = sizeof(sock_in);
  366     callptr->addr.buf = (char *) &sock_in;
  367     callptr->opt.len = 0;           /* no options */
  368     callptr->udata.len = 0;         /* no user data with connect */
  369 
  370     /*
  371      * Connect to the server.
  372      */
  373     if (t_connect(s, callptr, (struct t_call *) 0) < 0) {
  374         save_errno = t_errno;
  375         if (save_errno == TLOOK)
  376             fprintf(stderr, "%s", _(txt_error_server_unavailable));
  377         else
  378             t_error("t_connect");
  379         t_free((char *) callptr, T_CALL);
  380         t_close(s);
  381         return -save_errno;
  382     }
  383 
  384     /*
  385      * Now replace the timod module with the tirdwr module so that
  386      * standard read() and write() system calls can be used on the
  387      * descriptor.
  388      */
  389 
  390     t_free((char *) callptr, T_CALL);
  391 
  392     if (ioctl(s, I_POP, NULL) < 0) {
  393         perror("I_POP(timod)");
  394         t_close(s);
  395         return -EPROTO;
  396     }
  397 
  398     if (ioctl(s, I_PUSH, "tirdwr") < 0) {
  399         perror("I_PUSH(tirdwr)");
  400         t_close(s);
  401         return -EPROTO;
  402     }
  403 
  404 #   else
  405 #       ifndef EXCELAN
  406     struct servent *sp;
  407     struct hostent *hp;
  408 #           ifdef h_addr
  409     int x = 0;
  410     char **cp;
  411 #           endif /* h_addr */
  412 #           ifdef HAVE_HOSTENT_H_ADDR_LIST
  413     static char *alist[2] = {0, 0};
  414 #           endif /* HAVE_HOSTENT_H_ADDR_LIST */
  415     static struct hostent def;
  416     static struct in_addr defaddr;
  417     static char namebuf[256];
  418 
  419 #           ifdef HAVE_GETSERVBYNAME
  420     if ((sp = (struct servent *) getservbyname(service, "tcp")) == NULL) {
  421         my_fprintf(stderr, _(txt_error_unknown_service), service);
  422         return -EHOSTUNREACH;
  423     }
  424 #           else
  425     sp = my_malloc(sizeof(struct servent));
  426     sp->s_port = htons(IPPORT_NNTP);
  427 #           endif /* HAVE_GETSERVBYNAME */
  428 
  429     /* If not a raw ip address, try nameserver */
  430     if (!isdigit((unsigned char) *machine)
  431 #           ifdef HAVE_INET_ATON
  432         || !inet_aton(machine, &defaddr)
  433 #           else
  434 #               ifdef HAVE_INET_ADDR
  435         || (long) (defaddr.s_addr = (long) inet_addr(machine)) == -1
  436 #               endif /* HAVE_INET_ADDR */
  437 #           endif /* HAVE_INET_ATON */
  438         )
  439     {
  440         hp = gethostbyname(machine);
  441     } else {
  442         /* Raw ip address, fake */
  443         STRCPY(namebuf, machine);
  444         def.h_name = (char *) namebuf;
  445 #           ifdef HAVE_HOSTENT_H_ADDR_LIST
  446         def.h_addr_list = alist;
  447         def.h_addr_list[0] = (char *) &defaddr;
  448 #           else
  449         def.h_addr = (char *) &defaddr;
  450 #           endif /* HAVE_HOSTENT_H_ADDR_LIST */
  451         def.h_length = sizeof(struct in_addr);
  452         def.h_addrtype = AF_INET;
  453         def.h_aliases = 0;
  454         hp = &def;
  455     }
  456 
  457     if (hp == NULL) {
  458         my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
  459         return -EHOSTUNREACH;
  460     }
  461 
  462     memset((char *) &sock_in, '\0', sizeof(sock_in));
  463     sock_in.sin_family = hp->h_addrtype;
  464     sock_in.sin_port = htons(port);
  465 /*  sock_in.sin_port = sp->s_port; */
  466 #       else
  467     memset((char *) &sock_in, '\0', sizeof(sock_in));
  468     sock_in.sin_family = AF_INET;
  469 #       endif /* !EXCELAN */
  470 
  471     /*
  472      * The following is kinda gross. The name server under 4.3
  473      * returns a list of addresses, each of which should be tried
  474      * in turn if the previous one fails. However, 4.2 hostent
  475      * structure doesn't have this list of addresses.
  476      * Under 4.3, h_addr is a #define to h_addr_list[0].
  477      * We use this to figure out whether to include the NS specific
  478      * code...
  479      */
  480 
  481 #       ifdef h_addr
  482     /*
  483      * Get a socket and initiate connection -- use multiple addresses
  484      */
  485     for (cp = hp->h_addr_list; cp && *cp; cp++) {
  486 #           if defined(__hpux) && defined(SVR4)
  487         unsigned long socksize, socksizelen;
  488 #           endif /* __hpux && SVR4 */
  489 
  490         if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
  491             perror("socket");
  492             return -errno;
  493         }
  494 
  495         memcpy((char *) &sock_in.sin_addr, *cp, hp->h_length);
  496 
  497 #           ifdef HAVE_INET_NTOA
  498         if (x < 0)
  499             my_fprintf(stderr, _(txt_trying), (char *) inet_ntoa(sock_in.sin_addr));
  500 #           endif /* HAVE_INET_NTOA */
  501 
  502 #           if defined(__hpux) && defined(SVR4) /* recommended by raj@cup.hp.com */
  503 #               define HPSOCKSIZE 0x8000
  504         getsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
  505         if (socksize < HPSOCKSIZE) {
  506             socksize = HPSOCKSIZE;
  507             setsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
  508         }
  509         socksize = 0;
  510         socksizelen = sizeof(socksize);
  511         getsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
  512         if (socksize < HPSOCKSIZE) {
  513             socksize = HPSOCKSIZE;
  514             setsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
  515         }
  516 #           endif /* __hpux && SVR4 */
  517 
  518         if ((x = connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in))) == 0)
  519             break;
  520 
  521         save_errno = errno;                                 /* Keep for later */
  522 #           ifdef HAVE_INET_NTOA
  523         my_fprintf(stderr, _(txt_connection_to), (char *) inet_ntoa(sock_in.sin_addr));
  524         perror("");
  525 #           endif /* HAVE_INET_NTOA */
  526         (void) s_close(s);
  527     }
  528 
  529     if (x < 0) {
  530         my_fprintf(stderr, "%s", _(txt_giving_up));
  531         return -save_errno;                 /* Return the last errno we got */
  532     }
  533 #       else
  534 
  535 #           ifdef EXCELAN
  536     if ((s = socket(SOCK_STREAM, (struct sockproto *) NULL, &sock_in, SO_KEEPALIVE)) < 0) {
  537         perror("socket");
  538         return -errno;
  539     }
  540 
  541     /* set up addr for the connect */
  542     memset((char *) &sock_in, '\0', sizeof(sock_in));
  543     sock_in.sin_family = AF_INET;
  544     sock_in.sin_port = htons(IPPORT_NNTP);
  545 
  546     if ((sock_in.sin_addr.s_addr = rhost(&machine)) == -1) {
  547         my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
  548         return -1;
  549     }
  550 
  551     /* And connect */
  552     if (connect(s, (struct sockaddr *) &sock_in) < 0) {
  553         save_errno = errno;
  554         perror("connect");
  555         (void) s_close(s);
  556         return -save_errno;
  557     }
  558 
  559 #           else
  560     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  561         perror("socket");
  562         return -errno;
  563     }
  564 
  565     /* And then connect */
  566 #               ifdef HAVE_HOSTENT_H_ADDR_LIST
  567     memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
  568 #               else
  569     memcpy((char *) &sock_in.sin_addr, hp->h_addr, hp->h_length);
  570 #               endif /* HAVE_HOSTENT_H_ADDR_LIST */
  571     if (connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in)) < 0) {
  572         save_errno = errno;
  573         perror("connect");
  574         (void) s_close(s);
  575         return -save_errno;
  576     }
  577 
  578 #           endif /* EXCELAN */
  579 #       endif /* h_addr */
  580 #   endif /* TLI */
  581     return s;
  582 }
  583 #endif /* NNTP_ABLE && !INET6 */
  584 
  585 
  586 #if defined(NNTP_ABLE) && defined(INET6)
  587 /*
  588  * get_tcp6_socket -- get us a socket connected to the server.
  589  *
  590  * Parameters:   "machine" is the machine the server is running on.
  591  *                "port" is the portnumber to connect to.
  592  *
  593  * Returns:      Socket connected to the news server if
  594  *               all is ok, else -1 on error.
  595  *
  596  * Side effects: Connects to server via IPv4 or IPv6.
  597  *
  598  * Errors:       Printed via my_fprintf.
  599  */
  600 static int
  601 get_tcp6_socket(
  602     char *machine,
  603     unsigned short port)
  604 {
  605     char mymachine[MAXHOSTNAMELEN + 1];
  606     char myport[12];
  607     int s = -1, err, ec = 0, es = 0;
  608     struct addrinfo hints, *res, *res0;
  609 
  610     snprintf(mymachine, sizeof(mymachine), "%s", machine);
  611     snprintf(myport, sizeof(myport), "%u", port);
  612 
  613 /* just in case */
  614 #   ifdef AF_UNSPEC
  615 #       define ADDRFAM  AF_UNSPEC
  616 #   else
  617 #       ifdef PF_UNSPEC
  618 #           define ADDRFAM  PF_UNSPEC
  619 #       else
  620 #           define ADDRFAM  AF_INET
  621 #       endif /* PF_UNSPEC */
  622 #   endif /* AF_UNSPEC */
  623     memset(&hints, 0, sizeof(hints));
  624 /*  hints.ai_flags = AI_CANONNAME; */
  625     hints.ai_family = (force_ipv4 ? AF_INET : (force_ipv6 ? AF_INET6 : ADDRFAM));
  626     hints.ai_socktype = SOCK_STREAM;
  627     res0 = (struct addrinfo *) 0;
  628     if ((err = getaddrinfo(mymachine, myport, &hints, &res0))) {
  629         my_fprintf(stderr, "\ngetaddrinfo: %s\n", gai_strerror(err));
  630         return -1;
  631     }
  632     err = -1;
  633     for (res = res0; res; res = res->ai_next) {
  634         if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
  635             es = errno;
  636             continue;
  637         }
  638         if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
  639             ec = errno;
  640             close(s);
  641         } else {
  642             es = ec = err = 0;
  643             break;
  644         }
  645     }
  646     if (res0 != NULL)
  647         freeaddrinfo(res0);
  648     if (err < 0) {
  649         my_fprintf(stderr, "%s", _(txt_error_socket_or_connect_problem));
  650         if (es)
  651             my_fprintf(stderr, "\tsocket(2): %s\n", strerror(es));
  652         if (ec)
  653             my_fprintf(stderr, "\tconnect(2): %s\n", strerror(ec));
  654         sleep(3);
  655         return -1;
  656     }
  657     return s;
  658 }
  659 #endif /* NNTP_ABLE && INET6 */
  660 
  661 
  662 #ifdef DECNET
  663 /*
  664  * get_dnet_socket -- get us a socket connected to the server.
  665  *
  666  *  Parameters: "machine" is the machine the server is running on.
  667  *          "service" is the name of the service to connect to.
  668  *
  669  *  Returns:    Socket connected to the news server if
  670  *          all is ok, else -1 on error.
  671  *
  672  *  Side effects:   Connects to server.
  673  *
  674  *  Errors:     Printed via nerror.
  675  */
  676 static int
  677 get_dnet_socket(
  678     char *machine,
  679     char *service)
  680 {
  681 #   ifdef NNTP_ABLE
  682     int s, area, node;
  683     struct sockaddr_dn sdn;
  684     struct nodeent *getnodebyname(), *np;
  685 
  686     memset((char *) &sdn, '\0', sizeof(sdn));
  687 
  688     switch (s = sscanf(machine, "%d%*[.]%d", &area, &node)) {
  689         case 1:
  690             node = area;
  691             area = 0;
  692             /* FALLTHROUGH */
  693         case 2:
  694             node += area * 1024;
  695             sdn.sdn_add.a_len = 2;
  696             sdn.sdn_family = AF_DECnet;
  697             sdn.sdn_add.a_addr[0] = node % 256;
  698             sdn.sdn_add.a_addr[1] = node / 256;
  699             break;
  700 
  701         default:
  702             if ((np = getnodebyname(machine)) == NULL) {
  703                 my_fprintf(stderr, _(txt_gethostbyname), "", machine);
  704                 return -1;
  705             } else {
  706                 memcpy((char *) sdn.sdn_add.a_addr, np->n_addr, np->n_length);
  707                 sdn.sdn_add.a_len = np->n_length;
  708                 sdn.sdn_family = np->n_addrtype;
  709             }
  710             break;
  711     }
  712     sdn.sdn_objnum = 0;
  713     sdn.sdn_flags = 0;
  714     sdn.sdn_objnamel = strlen("NNTP");
  715     memcpy(&sdn.sdn_objname[0], "NNTP", sdn.sdn_objnamel);
  716 
  717     if ((s = socket(AF_DECnet, SOCK_STREAM, 0)) < 0) {
  718         nerror("socket");
  719         return -1;
  720     }
  721 
  722     /* And then connect */
  723 
  724     if (connect(s, (struct sockaddr *) &sdn, sizeof(sdn)) < 0) {
  725         nerror("connect");
  726         s_close(s);
  727         return -1;
  728     }
  729 
  730     return s;
  731 #   else
  732     return -1;
  733 #   endif /* NNTP_ABLE */
  734 }
  735 #endif /* DECNET */
  736 
  737 
  738 /*----------------------------------------------------------------------
  739  * Ideally the code after this point should be the only interface to the
  740  * NNTP internals...
  741  */
  742 
  743 /*
  744  * u_put_server -- send data to the server. Do not flush output.
  745  */
  746 #ifdef NNTP_ABLE
  747 void
  748 u_put_server(
  749     const char *string)
  750 {
  751     nntpbuf_puts(string, &nntp_buf);
  752 #   ifdef DEBUG
  753     if (debug & DEBUG_NNTP) {
  754         if (strcmp(string, "\r\n"))
  755             debug_print_file("NNTP", ">>>%s%s", logtime(), string);
  756     }
  757 #   endif /* DEBUG */
  758 }
  759 
  760 
  761 /*
  762  * Send 'string' to the NNTP server, terminating it with CR and LF, as per
  763  * ARPA standard.
  764  *
  765  * Returns: Nothing.
  766  *
  767  *  Side effects: Talks to the server.
  768  *                Closes connection if things are not right.
  769  *
  770  * Note: This routine flushes the buffer each time it is called. For large
  771  *       transmissions (i.e., posting news) don't use it. Instead, do the
  772  *       fprintf's yourself, and then a final fflush.
  773  *       Only cache commands, don't cache data transmissions.
  774  */
  775 void
  776 put_server(
  777     const char *string)
  778 {
  779     if (*string && strlen(string)) {
  780         DEBUG_IO((stderr, "put_server(%s)\n", string));
  781         nntpbuf_puts(string, &nntp_buf);
  782         nntpbuf_puts("\r\n", &nntp_buf);
  783 #   ifdef DEBUG
  784         if (debug & DEBUG_NNTP)
  785             debug_print_file("NNTP", ">>>%s%s", logtime(), string);
  786 #   endif /* DEBUG */
  787         /*
  788          * remember the last command we wrote to be able to resend it after a
  789          * reconnect. reconnection is handled by get_server()
  790          *
  791          * don't cache "LIST [ACTIVE|COUNTS|NEWSGROUPS] something" as we
  792          * would need to resend all of them but we remember just the last
  793          * one. we cache "LIST cmd." instead, this will slow down things, but
  794          * that's ok on reconnect.
  795          */
  796         if (strcmp(last_put, string))
  797             STRCPY(last_put, string);
  798         if (!strncmp(string, "LIST ACTIVE ", 12))
  799             last_put[11] = '\0'; /* "LIST ACTIVE" */
  800         else if (!strncmp(string, "LIST COUNTS ", 12))
  801             last_put[11] = '\0'; /* "LIST COUNTS" */
  802         else if (!strncmp(string, "LIST NEWSGROUPS ", 16))
  803             last_put[15] = '\0'; /* "LIST NEWSGROUPS" */
  804     }
  805     (void) nntpbuf_flush(&nntp_buf);
  806 }
  807 
  808 
  809 /*
  810  * Reconnect to server after a timeout, reissue last command to
  811  * get us back into the pre-timeout state
  812  */
  813 static int
  814 reconnect(
  815     int retry)
  816 {
  817     char buf[NNTP_STRLEN];
  818     int save_signal_context = signal_context;
  819 
  820     /*
  821      * Tear down current connection
  822      * Close the NNTP connection with prejudice
  823      */
  824     nntpbuf_close(&nntp_buf);
  825 
  826     if (!tinrc.auto_reconnect)
  827         ring_bell();
  828 
  829     DEBUG_IO((stderr, _("\nServer timed out, trying reconnect # %d\n"), retry));
  830 
  831     /*
  832      * set signal_context temporary to cReconnect to avoid trouble when receiving
  833      * SIGWINCH while being in prompt_yn()
  834      */
  835     signal_context = cReconnect;
  836 
  837     /*
  838      * Exit tin if there are no more tries or if the user says no to reconnect.
  839      * The exit code stops tin from trying to disconnect again - the connection
  840      * is already dead
  841      */
  842     if (retry > NNTP_TRY_RECONNECT || (!tinrc.auto_reconnect && prompt_yn(_(txt_reconnect_to_news_server), TRUE) != 1)) {
  843         if (!strcmp("POST", last_put)) {
  844             unlink(backup_article_name(article_name));
  845             rename_file(article_name, dead_article);
  846             if (tinrc.keep_dead_articles)
  847                 append_file(dead_article, dead_articles);
  848         }
  849         if (retry > NNTP_TRY_RECONNECT) {
  850 #   ifdef DEBUG
  851             /* TODO: -> lang.c */
  852             if ((debug & DEBUG_NNTP) && verbose > 1)
  853                 debug_print_file("NNTP", "reconnect(%d) limit %d reached, giving up.", retry, NNTP_TRY_RECONNECT);
  854 #   endif /* DEBUG */
  855         }
  856         tin_done(NNTP_ERROR_EXIT, _("NNTP connection error. Exiting..."));      /* user said no to reconnect or no more retries */
  857     }
  858 
  859     /* reset signal_context */
  860     signal_context = save_signal_context;
  861 
  862     clear_message();
  863     strcpy(buf, last_put);          /* Keep copy here, it will be clobbered a lot otherwise */
  864 
  865     if (!nntp_open()) {
  866         /* Re-establish our current group and resend last command */
  867         if (curr_group != NULL) {
  868             DEBUG_IO((stderr, _("Rejoin current group\n")));
  869             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
  870             put_server(last_put);
  871             if (nntpbuf_gets(last_put, NNTP_STRLEN, &nntp_buf) == NULL)
  872                 *last_put = '\0';
  873 #   ifdef DEBUG
  874             if (debug & DEBUG_NNTP)
  875                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
  876 #   endif /* DEBUG */
  877             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
  878         }
  879         DEBUG_IO((stderr, _("Resend last command (%s)\n"), buf));
  880         put_server(buf);
  881         did_reconnect = TRUE;
  882         retry = NNTP_TRY_RECONNECT;
  883     }
  884 
  885     return retry;
  886 }
  887 
  888 
  889 int
  890 fgetc_server(
  891     FILE *stream)
  892 {
  893     int c = EOF;
  894 
  895     if (stream != FAKE_NNTP_FP) {
  896         DEBUG_IO((stderr, "fgetc_server: BAD fp\n"));
  897         return EOF;
  898     }
  899 
  900     c = nntpbuf_getc(&nntp_buf);
  901     DEBUG_IO((stderr, "fgetc_server: %c / %d\n", c, c));
  902 
  903     return c;
  904 }
  905 
  906 
  907 int
  908 ungetc_server(
  909     int c,
  910     FILE *stream)
  911 {
  912     if (stream != FAKE_NNTP_FP) {
  913         DEBUG_IO((stderr, "fgetc_server: BAD fp\n"));
  914         return EOF;
  915     }
  916 
  917     DEBUG_IO((stderr, "ungetc_server: %c / %d\n", c, c));
  918 
  919     return nntpbuf_ungetc(c, &nntp_buf);
  920 }
  921 
  922 
  923 /*
  924  * Read a line of data from the NNTP socket. If something gives, do reconnect
  925  *
  926  *  Parameters: "string" has the buffer space for the line received.
  927  *          "size" is maximum size of the buffer to read.
  928  *
  929  *  Returns:    NULL on end of stream, or a line of data.
  930  *              Basically, we try to act like fgets() on an NNTP stream.
  931  *
  932  *  Side effects:   Talks to server, changes contents of "string".
  933  *          Reopens connection when necessary and requested.
  934  *          Exits via tin_done() if fatal error occurs.
  935  */
  936 char *
  937 get_server(
  938     char *string,
  939     int size)
  940 {
  941     static int retry_cnt = 0;
  942 
  943     reconnected_in_last_get_server = FALSE;
  944     errno = 0;
  945 
  946     /*
  947      * NULL socket reads indicates socket has closed. Try a few times more
  948      *
  949      * Leave the s_gets() after a timeout for these cases:
  950      *   -some servers do not close the connection but simply do not send any
  951      *    response data
  952      *   -the network connection went down
  953      */
  954 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  955     alarm((unsigned) tinrc.nntp_read_timeout_secs);
  956 #   endif /* HAVE_ALARM && SIGALRM */
  957     while (!nntpbuf_is_open(&nntp_buf) || nntpbuf_gets(string, size, &nntp_buf) == NULL) {
  958         if (errno == EINTR) {
  959             errno = 0;
  960 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  961             alarm((unsigned) tinrc.nntp_read_timeout_secs);     /* Restart the timer */
  962 #   endif /* HAVE_ALARM && SIGALRM */
  963             continue;
  964         }
  965 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  966         alarm(0);
  967 #   endif /* HAVE_ALARM && SIGALRM */
  968         if (quitting)                       /* Don't bother to reconnect */
  969             tin_done(NNTP_ERROR_EXIT, NULL);        /* And don't try to disconnect again! */
  970 
  971 #   ifdef DEBUG
  972         if (errno != 0 && errno != EINTR)   /* Will only confuse end users */
  973             perror_message("get_server()");
  974 #   endif /* DEBUG */
  975 
  976         /*
  977          * Reconnect only if command was not "QUIT" anyway (in which case a
  978          * reconnection would be useless because the connection will be
  979          * closed immediately). Also prevents tin from asking to reconnect
  980          * when user is quitting tin if tinrc.auto_reconnect is false.
  981          */
  982         if (strcmp(last_put, "QUIT")) {
  983             /*
  984              * Typhoon v2.1.1.363 colses the connection right after an unknown
  985              * command, (i.e. CAPABILITIES) so we avoid to reissue it on a
  986              * reconnect if it was the last command.
  987              */
  988             if (!strcmp(last_put, "CAPABILITIES")) {
  989                 strcpy(last_put, "MODE READER");
  990                 nntp_caps.type = BROKEN;
  991             }
  992             retry_cnt = reconnect(++retry_cnt);     /* Will abort when out of tries */
  993             reconnected_in_last_get_server = TRUE;
  994         } else {
  995             /*
  996              * Use standard NNTP closing message and response code if user is
  997              * quitting tin and leave loop.
  998              */
  999             strncpy(string, _(txt_nntp_ok_goodbye), (size_t) (size - 3));
 1000             strcat(string, "\r\n");     /* tin_fgets() needs CRLF */
 1001             break;
 1002         }
 1003     }
 1004 #   if defined(HAVE_ALARM) && defined(SIGALRM)
 1005     alarm(0);
 1006 #   endif /* HAVE_ALARM && SIGALRM */
 1007     retry_cnt = 0;
 1008     return string;
 1009 }
 1010 
 1011 
 1012 /*
 1013  * Send "QUIT" command and close the connection to the server
 1014  *
 1015  * Side effects: Closes the connection to the server.
 1016  *                You can't use "put_server" or "get_server" after this
 1017  *                routine is called.
 1018  *
 1019  * TODO: remember servers response string and if it contains anything else
 1020  *       than just "." (i.e. transfer statistics) present it to the user?
 1021  *
 1022  */
 1023 static void
 1024 close_server(
 1025     t_bool send_no_quit)
 1026 {
 1027     if (!send_no_quit && nntpbuf_is_open(&nntp_buf)) {
 1028         if (!batch_mode || verbose) {
 1029             char *msg;
 1030 
 1031             msg = strunc(_(txt_disconnecting), cCOLS - 1);
 1032             my_fputs(msg, stdout);
 1033             my_fputc('\n', stdout);
 1034             free(msg);
 1035         }
 1036         nntp_command("QUIT", OK_GOODBYE, NULL, 0);
 1037         quitting = TRUE;                                        /* Don't reconnect just for this */
 1038     }
 1039     nntpbuf_close(&nntp_buf);
 1040 }
 1041 
 1042 
 1043 /*
 1044  * Try and use CAPABILITIES here. Get this list before issuing other NNTP
 1045  * commands because the correct methods may be mentioned in the list of
 1046  * extensions.
 1047  *
 1048  * Sets up: t_capabilities nntp_caps
 1049  */
 1050 int
 1051 check_extensions(
 1052     int rvl)
 1053 {
 1054     char *d;
 1055     char *ptr;
 1056     char buf[NNTP_STRLEN];
 1057     int i;
 1058     int ret = 0;
 1059 
 1060     buf[0] = '\0';
 1061 
 1062     /* rvl > 0 = manually send "CAPABILITIES" to avoid endless AUTH loop */
 1063     i = rvl ? rvl : new_nntp_command("CAPABILITIES", INF_CAPABILITIES, buf, sizeof(buf));
 1064     switch (i) {
 1065         case INF_CAPABILITIES:
 1066             /* clear capabilities */
 1067             nntp_caps.type = CAPABILITIES;
 1068             nntp_caps.version = 0;
 1069             nntp_caps.mode_reader = FALSE;
 1070             nntp_caps.reader = FALSE;
 1071             nntp_caps.post = FALSE;
 1072             nntp_caps.list_active = FALSE;
 1073             nntp_caps.list_active_times = FALSE;
 1074             nntp_caps.list_distrib_pats = FALSE;
 1075             nntp_caps.list_headers = FALSE;
 1076             FreeAndNull(nntp_caps.headers_range);
 1077             FreeAndNull(nntp_caps.headers_id);
 1078             nntp_caps.list_newsgroups = FALSE;
 1079             nntp_caps.list_overview_fmt = FALSE;
 1080             nntp_caps.list_motd = FALSE;
 1081             nntp_caps.list_subscriptions = FALSE;
 1082             nntp_caps.list_distributions = FALSE;
 1083             nntp_caps.list_moderators = FALSE;
 1084             nntp_caps.list_counts = FALSE;
 1085             nntp_caps.xpat = TRUE; /* toggles to false if fails, INN > 2.7.0 announces it */
 1086             nntp_caps.hdr = FALSE;
 1087             nntp_caps.hdr_cmd = NULL;
 1088             nntp_caps.over = FALSE;
 1089             nntp_caps.over_msgid = FALSE;
 1090             nntp_caps.over_cmd = NULL;
 1091             nntp_caps.newnews = FALSE;
 1092             FreeAndNull(nntp_caps.implementation);
 1093             nntp_caps.starttls = FALSE;
 1094             nntp_caps.authinfo_user = FALSE;
 1095             nntp_caps.authinfo_sasl = FALSE;
 1096             nntp_caps.authinfo_state = FALSE;
 1097             nntp_caps.sasl = SASL_NONE;
 1098             nntp_caps.compress = FALSE;
 1099             nntp_caps.compress_algorithm = COMPRESS_NONE;
 1100             /* nntp_caps.maxartnum will be init it nntp_open() */
 1101 #   if 0
 1102             nntp_caps.streaming = FALSE;
 1103             nntp_caps.ihave = FALSE;
 1104 #   endif /* 0 */
 1105 #   ifndef BROKEN_LISTGROUP
 1106             nntp_caps.broken_listgroup = FALSE;
 1107 #   else
 1108             nntp_caps.broken_listgroup = TRUE;
 1109 #   endif /* !BROKEN_LISTGROUP */
 1110 
 1111             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1112 #   ifdef DEBUG
 1113                 if (debug & DEBUG_NNTP)
 1114                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1115 #   endif /* DEBUG */
 1116                 /* look for version number(s) */
 1117                 if (!nntp_caps.version && nntp_caps.type == CAPABILITIES) {
 1118                     if (!strncasecmp(ptr, "VERSION", 7)) {
 1119                         d = ptr + 7;
 1120                         d = strpbrk(d, " \t");
 1121                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1122                             d++;
 1123                             nntp_caps.version = (unsigned int) atoi(d);
 1124                             d = strpbrk(d, " \t");
 1125                         }
 1126                     }
 1127                 }
 1128                 /* we currently only support CAPABILITIES VERSION 2 */
 1129                 if (nntp_caps.version == 2) {
 1130                     /*
 1131                      * check for LIST variants
 1132                      */
 1133                     if (!strncasecmp(ptr, "LIST", 4)) {
 1134                         d = ptr + 4;
 1135                         d = strpbrk(d, " \t");
 1136                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1137                             d++;
 1138                             if (!strncasecmp(d, "ACTIVE.TIMES", 12))
 1139                                 nntp_caps.list_active_times = TRUE;
 1140                             else if (!strncasecmp(d, "ACTIVE", 6))
 1141                                 nntp_caps.list_active = TRUE;
 1142                             else if (!strncasecmp(d, "DISTRIB.PATS", 12))
 1143                                 nntp_caps.list_distrib_pats = TRUE;
 1144                             else if (!strncasecmp(d, "DISTRIBUTIONS", 13)) /* RFC 6048 */
 1145                                 nntp_caps.list_distributions = TRUE;
 1146                             else if (!strncasecmp(d, "HEADERS", 7))
 1147                                 nntp_caps.list_headers = TRUE; /* HDR requires LIST HEADERS, but not vice versa */
 1148                             else if (!strncasecmp(d, "NEWSGROUPS", 10))
 1149                                 nntp_caps.list_newsgroups = TRUE;
 1150                             else if (!strncasecmp(d, "OVERVIEW.FMT", 12)) /* OVER requires OVERVIEW.FMT, but not vice versa */
 1151                                 nntp_caps.list_overview_fmt = TRUE;
 1152                             else if (!strncasecmp(d, "MOTD", 4)) /* RFC 6048 */
 1153                                 nntp_caps.list_motd = TRUE;
 1154                             else if (!strncasecmp(d, "SUBSCRIPTIONS", 13)) /* RFC 6048 */
 1155                                 nntp_caps.list_subscriptions = TRUE;
 1156                             else if (!strncasecmp(d, "MODERATORS", 10)) /* RFC 6048 */
 1157                                 nntp_caps.list_moderators = TRUE;
 1158                             else if (!strncasecmp(d, "COUNTS", 6)) /* RFC 6048 */
 1159                                 nntp_caps.list_counts = TRUE;
 1160                             d = strpbrk(d, " \t");
 1161                         }
 1162                     } else if (!strncasecmp(ptr, "IMPLEMENTATION", 14)) {
 1163                         FreeIfNeeded(nntp_caps.implementation);
 1164                         nntp_caps.implementation = my_strdup(ptr + 14);
 1165                         str_trim(nntp_caps.implementation);
 1166                     } else if (!strcasecmp(ptr, "MODE-READER")) {
 1167                         if (!nntp_caps.reader)
 1168                             nntp_caps.mode_reader = TRUE;
 1169                     } else if (!strcasecmp(ptr, "READER")) { /* if we saw READER, "LIST ACTIVE" and "LIST NEWSGROUPS" must be implemented */
 1170                         nntp_caps.reader = TRUE;
 1171                         nntp_caps.mode_reader = FALSE;
 1172                         nntp_caps.list_newsgroups = TRUE;
 1173                         nntp_caps.list_active = TRUE;
 1174                     } else if (!strcasecmp(ptr, "POST"))
 1175                         nntp_caps.post = TRUE;
 1176                     else if (!strcasecmp(ptr, "NEWNEWS"))
 1177                         nntp_caps.newnews = TRUE;
 1178                     else if (!strcasecmp(ptr, "XPAT")) /* extension, RFC 2980 */
 1179                         nntp_caps.xpat = TRUE;
 1180                     else if (!strcasecmp(ptr, "STARTTLS"))
 1181                         nntp_caps.starttls = TRUE;
 1182                     /*
 1183                      * NOTE: if we saw OVER, LIST OVERVIEW.FMT _must_ be implemented
 1184                      */
 1185                     else if (!strncasecmp(ptr, &xover_cmds[1], strlen(&xover_cmds[1]))) {
 1186                         nntp_caps.list_overview_fmt = nntp_caps.over = TRUE;
 1187                         nntp_caps.over_cmd = &xover_cmds[1];
 1188                         d = ptr + strlen(&xover_cmds[1]);
 1189                         d = strpbrk(d, " \t");
 1190                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1191                             d++;
 1192                             if (!strcasecmp(d, "MSGID"))
 1193                                 nntp_caps.over_msgid = TRUE;
 1194                             d = strpbrk(d, " \t");
 1195                         }
 1196                     }
 1197                     /*
 1198                      * NOTE: if we saw HDR, LIST HEADERS _must_ be implemented
 1199                      */
 1200                     else if (!strncasecmp(ptr, &xhdr_cmds[1], strlen(&xhdr_cmds[1]))) {
 1201                         nntp_caps.hdr_cmd = &xhdr_cmds[1];
 1202                         nntp_caps.list_headers = nntp_caps.hdr = TRUE;
 1203                         nntp_caps.headers_range = my_strdup("");
 1204                         nntp_caps.headers_id = my_strdup("");
 1205                     } else if (!strncasecmp(ptr, "AUTHINFO", 8)) {
 1206                         d = ptr + 8;
 1207                         d = strpbrk(d, " \t");
 1208                         if (d == NULL) /* AUTHINFO without args */
 1209                             nntp_caps.authinfo_state = TRUE;
 1210                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1211                             d++;
 1212                             if (!strncasecmp(d, "USER", 4))
 1213                                 nntp_caps.authinfo_user = TRUE;
 1214                             if (!strncasecmp(d, "SASL", 4))
 1215                                 nntp_caps.authinfo_sasl = TRUE;
 1216                             d = strpbrk(d, " \t");
 1217                         }
 1218                     } else if (!strncasecmp(ptr, "SASL", 4)) {
 1219                         d = ptr + 4;
 1220                         d = strpbrk(d, " \t");
 1221                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1222                             d++;
 1223                             if (!strncasecmp(d, "CRAM-MD5", 8)) { /* RFC 2195 */
 1224                                 nntp_caps.authinfo_sasl = TRUE;
 1225                                 nntp_caps.sasl |= SASL_CRAM_MD5;
 1226                             }
 1227                             if (!strncasecmp(d, "DIGEST-MD5", 10)) { /* RFC 2831 */
 1228                                 nntp_caps.authinfo_sasl = TRUE;
 1229                                 nntp_caps.sasl |= SASL_DIGEST_MD5;
 1230                             }
 1231                             if (!strncasecmp(d, "PLAIN", 5)) { /* RFC 4616 */
 1232                                 nntp_caps.authinfo_sasl = TRUE;
 1233                                 nntp_caps.sasl |= SASL_PLAIN;
 1234                             }
 1235                             if (!strncasecmp(d, "GSSAPI", 6)) { /* RFC 4752 */
 1236                                 nntp_caps.authinfo_sasl = TRUE;
 1237                                 nntp_caps.sasl |= SASL_GSSAPI;
 1238                             }
 1239                             if (!strncasecmp(d, "EXTERNAL", 8)) { /* RFC 4422 */
 1240                                 nntp_caps.authinfo_sasl = TRUE;
 1241                                 nntp_caps.sasl |= SASL_EXTERNAL;
 1242                             }
 1243                             /* inn also can do these */
 1244                             if (!strncasecmp(d, "OTP", 3)) { /* RFC 2444 */
 1245                                 nntp_caps.authinfo_sasl = TRUE;
 1246                                 nntp_caps.sasl |= SASL_OTP;
 1247                             }
 1248                             if (!strncasecmp(d, "NTLM", 4)) { /* Microsoft */
 1249                                 nntp_caps.authinfo_sasl = TRUE;
 1250                                 nntp_caps.sasl |= SASL_NTLM;
 1251                             }
 1252                             if (!strncasecmp(d, "LOGIN", 5)) { /* Microsoft */
 1253                                 nntp_caps.authinfo_sasl = TRUE;
 1254                                 nntp_caps.sasl |= SASL_LOGIN;
 1255                             }
 1256                         }
 1257                     } else if (!strncasecmp(ptr, "COMPRESS", 8)) { /* RFC 8054 */
 1258                         d = ptr + 8;
 1259                         d = strpbrk(d, " \t");
 1260                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1261                             d++;
 1262                             if (!strncasecmp(d, "DEFLATE", 7)) {
 1263                                 nntp_caps.compress = TRUE;
 1264                                 nntp_caps.compress_algorithm |= COMPRESS_DEFLATE;
 1265                             }
 1266                         }
 1267                     }
 1268 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 1269                     /*
 1270                      * MAXARTNUM - <tnqm14$35bas$1@news.trigofacile.com>
 1271                      *
 1272                      * if server responds with MAXARTNUM we (re)parse it
 1273                      * as we may have changed the state (auth) and it's
 1274                      * the servers job to not advertised MAXARTNUM again
 1275                      * after it had been used ...
 1276                      */
 1277                     else if (!strncasecmp(ptr, "MAXARTNUM", 9)) {
 1278                         d = ptr + 9;
 1279                         d = strpbrk(d, " \t");
 1280                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1281                             d++;
 1282                             nntp_caps.maxartnum = MIN(atoartnum(d), ARTNUM_MAX);
 1283                             d = strpbrk(d, " \t");
 1284                         }
 1285                     }
 1286 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 1287 #   if 0 /* we don't need these */
 1288                     else if (!strcasecmp(ptr, "IHAVE"))
 1289                         nntp_caps.ihave = TRUE;
 1290                     else if (!strcasecmp(ptr, "STREAMING"))
 1291                         nntp_caps.streaming = TRUE;
 1292 #   endif /* 0 */
 1293                 /* XZVER, XZHDR, ... */
 1294                 } else
 1295                     nntp_caps.type = NONE;
 1296             }
 1297             break;
 1298 
 1299         /*
 1300          * XanaNewz 2 Server Version 2.0.0.3 doesn't know CAPABILITIES
 1301          * but responds with 400 _without_ closing the connection. If
 1302          * you use tin on a XanaNewz 2 Server comment out the following
 1303          * case.
 1304          */
 1305 #   if 1
 1306         case ERR_GOODBYE:
 1307             ret = i;
 1308             error_message(2, "%s", buf);
 1309             break;
 1310 #   endif /* 1 */
 1311 
 1312         default:
 1313             break;
 1314     }
 1315 
 1316 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 1317 #       if 0 /* likely to be dropped from the draft */
 1318     if (nntp_caps.maxartnum == T_ARTNUM_CONST(0)) /* a value of 0 (zero) indicates that the server can handle article numbers of any size */
 1319         nntp_caps.maxartnum = ARTNUM_MAX;
 1320 #       endif /* 0 */
 1321     if (nntp_caps.maxartnum <= T_ARTNUM_CONST(2147483647)) /* RFC 3977 "Article numbers MUST lie between 1 and 2,147,483,647, inclusive." */
 1322         nntp_caps.maxartnum = T_ARTNUM_CONST(0);
 1323 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 1324 
 1325     return ret;
 1326 }
 1327 
 1328 
 1329 /*
 1330  * Switch INN into NNRP mode with 'mode reader'
 1331  */
 1332 static int
 1333 mode_reader(
 1334     t_bool *sec)
 1335 {
 1336     int ret = 0;
 1337 
 1338     if (!nntp_caps.reader) {
 1339         char line[NNTP_STRLEN];
 1340 #   ifdef DEBUG
 1341         if ((debug & DEBUG_NNTP) && verbose > 1)
 1342             debug_print_file("NNTP", "mode_reader(MODE READER)");
 1343 #   endif /* DEBUG */
 1344         DEBUG_IO((stderr, "nntp_command(MODE READER)\n"));
 1345         put_server("MODE READER");
 1346 
 1347         /*
 1348          * According to RFC 3977 (5.3), MODE READER may only return the
 1349          * following response codes:
 1350          *
 1351          *   200 (OK_CANPOST)     Hello, you can post
 1352          *   201 (OK_NOPOST)      Hello, you can't post
 1353          *   502 (ERR_ACCESS)     Service unavailable
 1354          *
 1355          * However, there are servers out there (e.g. Delegate 9.8.x) that do
 1356          * not implement this command and therefore return ERR_COMMAND (500).
 1357          * Unfortunately there are some new servers out there (i.e. INN 2.4.0
 1358          * (20020220 prerelease) which do return ERR_COMMAND if they are feed
 1359          * only servers.
 1360          */
 1361 
 1362         switch ((ret = get_respcode(line, sizeof(line)))) {
 1363             case OK_CANPOST:
 1364                 can_post = TRUE && !force_no_post;
 1365                 *sec = TRUE;
 1366                 ret = 0;
 1367                 break;
 1368 
 1369             case OK_NOPOST:
 1370                 can_post = FALSE;
 1371                 *sec = TRUE;
 1372                 ret = 0;
 1373                 break;
 1374 
 1375             case ERR_GOODBYE:
 1376             case ERR_ACCESS:
 1377                 error_message(2, "%s", line);
 1378                 return ret;
 1379 
 1380             case ERR_COMMAND:
 1381 #   if 1
 1382                 ret = 0;
 1383                 break;
 1384 #   endif /* 1 */
 1385 
 1386             default:
 1387                 break;
 1388         }
 1389     }
 1390     return ret;
 1391 }
 1392 #endif /* NNTP_ABLE */
 1393 
 1394 
 1395 /*
 1396  * Open a connection to the NNTP server. Authenticate if necessary or
 1397  * desired, and test if the server supports XOVER.
 1398  * Returns: 0   success
 1399  *        > 0   NNTP error response code
 1400  *        < 0   -errno from system call or similar error
 1401  */
 1402 int
 1403 nntp_open(
 1404     void)
 1405 {
 1406 #ifdef NNTP_ABLE
 1407     char *linep;
 1408     char line[NNTP_STRLEN] = { '\0' };
 1409     int ret;
 1410     t_bool sec = FALSE;
 1411     /* It appears that is_reconnect guards code that should be run only once */
 1412     static t_bool is_reconnect = FALSE;
 1413 
 1414     if (!read_news_via_nntp)
 1415         return 0;
 1416 
 1417 #   ifdef DEBUG
 1418     if ((debug & DEBUG_NNTP) && verbose > 1)
 1419         debug_print_file("NNTP", "nntp_open() BEGIN");
 1420 #   endif /* DEBUG */
 1421 
 1422     if (nntp_server == NULL) {
 1423         error_message(2, _(txt_cannot_get_nntp_server_name));
 1424         error_message(2, _(txt_server_name_in_file_env_var), NNTP_SERVER_FILE);
 1425         return -EHOSTUNREACH;
 1426     }
 1427 
 1428     if (!batch_mode || verbose) {
 1429         if (nntp_tcp_port != IPPORT_NNTP)
 1430             wait_message(0, _(txt_connecting_port), nntp_server, nntp_tcp_port);
 1431         else
 1432             wait_message(0, _(txt_connecting), nntp_server);
 1433     }
 1434 
 1435     if ((!batch_mode || verbose) && use_nntps)
 1436         my_fputc('\n', stdout);
 1437 
 1438 #   ifdef DEBUG
 1439     if ((debug & DEBUG_NNTP) && verbose > 1)
 1440         debug_print_file("NNTP", "nntp_open() %s:%d", nntp_server, nntp_tcp_port);
 1441 #   endif /* DEBUG */
 1442 
 1443     ret = server_init(nntp_server, NNTP_TCP_NAME, nntp_tcp_port, line, sizeof(line));
 1444     DEBUG_IO((stderr, "server_init returns %d,%s\n", ret, line));
 1445 
 1446     if ((!batch_mode || verbose) && ret >= 0 && !use_nntps)
 1447         my_fputc('\n', stdout);
 1448 
 1449 #   ifdef DEBUG
 1450     if ((debug & DEBUG_NNTP) && verbose > 1)
 1451         debug_print_file("NNTP", "nntp_open() %s", line);
 1452 #   endif /* DEBUG */
 1453 
 1454     switch (ret) {
 1455         /*
 1456          * ret < 0 : some error from system call
 1457          * ret > 0 : NNTP response code
 1458          *
 1459          * According to the ietf-nntp mailinglist:
 1460          *   200 you may (try to) do anything
 1461          *   201 you may not POST
 1462          *   All unrecognised 200 series codes should be assumed as success.
 1463          *   All unrecognised 300 series codes should be assumed as notice to continue.
 1464          *   All unrecognised 400 series codes should be assumed as temporary error.
 1465          *   All unrecognised 500 series codes should be assumed as error.
 1466          */
 1467 
 1468         case OK_CANPOST:
 1469             can_post = TRUE && !force_no_post;
 1470             break;
 1471 
 1472         case OK_NOPOST:
 1473             can_post = FALSE;
 1474             break;
 1475 
 1476         default:
 1477             if (ret >= 200 && ret <= 299) {
 1478                 can_post = TRUE && !force_no_post;
 1479                 break;
 1480             }
 1481             if (ret < 0)
 1482                 error_message(2, _(txt_failed_to_connect_to_server), nntp_server);
 1483             else
 1484                 error_message(2, "%s", line);
 1485 
 1486             return ret;
 1487     }
 1488     if (!is_reconnect && *line) {
 1489         /* remove leading whitespace and save server's initial response */
 1490         linep = line;
 1491         while (isspace((int) *linep))
 1492             linep++;
 1493 
 1494         STRCPY(bug_nntpserver1, linep);
 1495     }
 1496 
 1497 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 1498     nntp_caps.maxartnum = T_ARTNUM_CONST(-1);
 1499 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 1500 
 1501     /*
 1502      * Find out which NNTP extensions are available
 1503      * - Typhoon v2.1.1.363 closes the connection after an unknown command
 1504      *   (i.e. CAPABILITIES) but as we are not allowed to cache CAPABILITIES
 1505      *   we reissue the command on reconnect. To prevent a loop we catch this
 1506      *   case.
 1507      *
 1508      * TODO: The authentication method required may be mentioned in the list
 1509      *       of extensions. (For details about authentication methods, see
 1510      *       RFC 4643).
 1511      */
 1512     if (nntp_caps.type != BROKEN)
 1513         check_extensions(0);
 1514 
 1515     /*
 1516      * If the user wants us to authenticate on connection startup, do it now.
 1517      * Some news servers return "201 no posting" first, but after successful
 1518      * authentication you get a "200 posting allowed". To find out if we are
 1519      * allowed to post after authentication issue a "MODE READER" again and
 1520      * interpret the response code.
 1521      */
 1522 
 1523     if (nntp_caps.type == CAPABILITIES && !nntp_caps.reader) {
 1524         if (nntp_caps.mode_reader) {
 1525             char buf[NNTP_STRLEN];
 1526 
 1527 #   ifdef DEBUG
 1528             if ((debug & DEBUG_NNTP) && verbose > 1)
 1529                 debug_print_file("NNTP", "nntp_open(MODE READER)");
 1530 #   endif /* DEBUG */
 1531             put_server("MODE READER");
 1532             switch (get_only_respcode(buf, sizeof(buf))) {
 1533                 /* just honor critical errors */
 1534                 case ERR_GOODBYE:
 1535                 case ERR_ACCESS:
 1536                     error_message(2, "%s", buf);
 1537                     return -1;
 1538 
 1539                 default:
 1540                     break;
 1541             }
 1542             check_extensions(0);
 1543         }
 1544     }
 1545 
 1546     if (force_auth_on_conn_open) {
 1547 #   ifdef DEBUG
 1548         if ((debug & DEBUG_NNTP) && verbose > 1)
 1549             debug_print_file("NNTP", "nntp_open(authenticate(force_auth_on_conn_open))");
 1550 #   endif /* DEBUG */
 1551 
 1552         if (!authenticate(nntp_server, userid, FALSE))  /* 3rd parameter is FALSE as we need to get prompted for username password here */
 1553             return -1;
 1554     }
 1555 
 1556     if ((nntp_caps.type == CAPABILITIES && nntp_caps.mode_reader) || nntp_caps.type != CAPABILITIES) {
 1557         if ((ret = mode_reader(&sec))) {
 1558             if (nntp_caps.type == CAPABILITIES)
 1559                 can_post = nntp_caps.post && !force_no_post;
 1560 
 1561             return ret;
 1562         }
 1563         if (nntp_caps.type == CAPABILITIES)
 1564             check_extensions(0);
 1565     }
 1566 
 1567     if (nntp_caps.type == CAPABILITIES) {
 1568         if (!nntp_caps.reader) {
 1569 #   ifdef DEBUG
 1570             if ((debug & DEBUG_NNTP) && verbose > 1)
 1571                 debug_print_file("NNTP", "CAPABILITIES did not announce READER");
 1572 #   endif /* DEBUG */
 1573             error_message(2, _("CAPABILITIES did not announce READER")); /* TODO: -> lang.c */
 1574             return -1; /* give up */
 1575         }
 1576         can_post = nntp_caps.post && !force_no_post;
 1577     }
 1578 
 1579     if (!is_reconnect && *line) {
 1580 #   if 0
 1581     /*
 1582      * gives wrong results if RFC 3977 server requests auth after
 1583      * CAPABILITIES is parsed (with no posting allowed) and after auth
 1584      * posting is allowed. as we will inform the user later on when he
 1585      * actually tries to post it should do no harm to skip this message
 1586      */
 1587         /* Inform user if he cannot post */
 1588         if (!can_post && !batch_mode)
 1589             wait_message(0, "%s\n", _(txt_cannot_post));
 1590 #   endif /* 0 */
 1591 
 1592         /* Remove leading white space and save server's second response */
 1593         linep = line;
 1594         while (isspace((int) *linep))
 1595             linep++;
 1596 
 1597         STRCPY(bug_nntpserver2, linep);
 1598 
 1599         /*
 1600          * Show user last server response line, do some nice formatting if
 1601          * response is longer than a screen wide.
 1602          *
 1603          * TODO: This only breaks the line once, but the response could be
 1604          * longer than two lines ...
 1605          */
 1606         if (!batch_mode || verbose) {
 1607             char *chr1, *chr2;
 1608             int j;
 1609 
 1610             j = atoi(get_val("COLUMNS", "80"));
 1611             chr1 = my_strdup((sec ? bug_nntpserver2 : bug_nntpserver1));
 1612 
 1613             if (j > MIN_COLUMNS_ON_TERMINAL && ((int) strlen(chr1)) >= j) {
 1614                 chr2 = chr1 + strlen(chr1) - 1;
 1615                 while (chr2 - chr1 >= j)
 1616                     chr2--;
 1617                 while (chr2 > chr1 && *chr2 != ' ')
 1618                     chr2--;
 1619                 if (chr2 != chr1)
 1620                     *chr2 = '\n';
 1621             }
 1622 
 1623             wait_message(0, "%s\n", chr1);
 1624             free(chr1);
 1625         }
 1626     }
 1627 
 1628     /*
 1629      * If CAPABILITIES failed, check if NNTP supports XOVER or OVER command
 1630      * We have to check that we _don't_ get an ERR_COMMAND
 1631      *
 1632      * TODO: this should be done when the command is first used
 1633      */
 1634     if (nntp_caps.type != CAPABILITIES) {
 1635         int i, j = 0;
 1636 
 1637         for (i = 0; i < 2 && j >= 0; i++) {
 1638             j = new_nntp_command(&xover_cmds[i], ERR_NCING, line, sizeof(line));
 1639             switch (j) {
 1640                 case ERR_COMMAND:
 1641                     break;
 1642 
 1643                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1644                     nntp_caps.over_cmd = &xover_cmds[i];
 1645 #   ifdef DEBUG
 1646                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1647                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xover_cmds[i]);
 1648 #   endif /* DEBUG */
 1649                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1650                         ;
 1651                     j = -1;
 1652                     break;
 1653 
 1654                 default:
 1655                     nntp_caps.over_cmd = &xover_cmds[i];
 1656                     j = -1;
 1657                     break;
 1658             }
 1659         }
 1660         for (i = 0, j = 0; i < 2 && j >= 0; i++) {
 1661             j = new_nntp_command(&xhdr_cmds[i], ERR_CMDSYN, line, sizeof(line));
 1662             switch (j) {
 1663                 case ERR_COMMAND:
 1664                     break;
 1665 
 1666                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1667                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1668 #   ifdef DEBUG
 1669                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1670                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xhdr_cmds[i]);
 1671 #   endif /* DEBUG */
 1672                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1673                         ;
 1674                     j = -1;
 1675                     break;
 1676 
 1677                 default:    /* usually ERR_CMDSYN (args missing), Typhoon/Twister sends ERR_NCING */
 1678                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1679                     j = -1;
 1680                     break;
 1681             }
 1682         }
 1683         /* no XPAT probing here, we do when it's needed */
 1684         nntp_caps.xpat = TRUE;
 1685 #   if 0
 1686         switch (new_nntp_command("XPAT Newsgroups <0> *", ERR_NOART, line, sizeof(line))) {
 1687             case ERR_NOART:
 1688                 nntp_caps.xpat = TRUE;
 1689                 break;
 1690 
 1691             default:
 1692                 break;
 1693         }
 1694 #   endif /* 0 */
 1695     } else {
 1696         if (!nntp_caps.over_cmd) {
 1697             /*
 1698              * CAPABILITIES didn't mention OVER or XOVER, try XOVER
 1699              */
 1700             switch (new_nntp_command(xover_cmds, ERR_NCING, line, sizeof(line))) {
 1701                 case ERR_COMMAND:
 1702                     break;
 1703 
 1704                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1705                     nntp_caps.over_cmd = xover_cmds;
 1706 #   ifdef DEBUG
 1707                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1708                         debug_print_file("NNTP", "nntp_open() %s skipping data", xover_cmds);
 1709 #   endif /* DEBUG */
 1710                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1711                         ;
 1712                     break;
 1713 
 1714                 default:
 1715                     nntp_caps.over_cmd = xover_cmds;
 1716                     break;
 1717             }
 1718         }
 1719         if (!nntp_caps.hdr_cmd) {
 1720             /*
 1721              * CAPABILITIES didn't mention HDR or XHDR, try XHDR
 1722              */
 1723             switch (new_nntp_command(xhdr_cmds, ERR_NCING, line, sizeof(line))) {
 1724                 case ERR_COMMAND:
 1725                     break;
 1726 
 1727                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1728                     nntp_caps.hdr_cmd = xhdr_cmds;
 1729 #   ifdef DEBUG
 1730                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1731                         debug_print_file("NNTP", "nntp_open() %s skipping data", xhdr_cmds);
 1732 #   endif /* DEBUG */
 1733                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1734                         ;
 1735                     break;
 1736 
 1737                 default:    /* ERR_NCING or ERR_CMDSYN */
 1738                     nntp_caps.hdr_cmd = xhdr_cmds;
 1739                     break;
 1740             }
 1741         }
 1742     }
 1743 
 1744     if (!nntp_caps.over_cmd) {
 1745         if (!is_reconnect && !batch_mode) {
 1746             wait_message(2, _(txt_no_xover_support));
 1747 
 1748             if (tinrc.cache_overview_files)
 1749                 wait_message(2, _(txt_caching_on));
 1750             else
 1751                 wait_message(2, _(txt_caching_off));
 1752         }
 1753     }
 1754 #   if 0
 1755     else {
 1756         /*
 1757          * TODO: issue warning if old index files found?
 1758          *        in index_newsdir?
 1759          */
 1760     }
 1761 #   endif /* 0 */
 1762 
 1763 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 1764     set_maxartnum(is_reconnect);
 1765 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 1766 
 1767     if (!is_reconnect && !batch_mode && show_description && check_for_new_newsgroups) {
 1768         /*
 1769          * TODO:
 1770          * - add a tinrc var to turn LIST MOTD on/off?
 1771          *   (currently done automatically for -d, -q and -Q)
 1772          */
 1773         if (nntp_caps.list_motd)
 1774             list_motd();
 1775     }
 1776 
 1777     is_reconnect = TRUE;
 1778 #endif /* NNTP_ABLE */
 1779 
 1780     DEBUG_IO((stderr, "nntp_open okay\n"));
 1781     return 0;
 1782 }
 1783 
 1784 
 1785 /*
 1786  * 'Public' function to shutdown the NNTP connection
 1787  */
 1788 void
 1789 nntp_close(
 1790     t_bool send_no_quit)
 1791 {
 1792 #ifdef NNTP_ABLE
 1793     if (read_news_via_nntp && !read_saved_news) {
 1794 #   ifdef DEBUG
 1795         if ((debug & DEBUG_NNTP) && verbose > 1)
 1796             debug_print_file("NNTP", "nntp_close(%s) END", bool_unparse(send_no_quit));
 1797 #   endif /* DEBUG */
 1798         close_server(send_no_quit);
 1799     }
 1800 #endif /* NNTP_ABLE */
 1801 }
 1802 
 1803 
 1804 #ifdef NNTP_ABLE
 1805 /*
 1806  * Get a response code from the server.
 1807  * Returns:
 1808  *  +ve NNTP return code
 1809  *  -1  on an error or user abort. We don't differentiate.
 1810  * If 'message' is not NULL, then any trailing text after the response
 1811  * code is copied into it.
 1812  * Does not perform authentication if required; use get_respcode()
 1813  * instead.
 1814  */
 1815 int
 1816 get_only_respcode(
 1817     char *message,
 1818     size_t mlen)
 1819 {
 1820     int respcode;
 1821     char *end, *ptr;
 1822 
 1823     ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1824 
 1825     if (tin_errno || ptr == NULL) {
 1826 #   ifdef DEBUG
 1827         if ((debug & DEBUG_NNTP) && verbose > 1)
 1828             debug_print_file("NNTP", "Error: tin_error<>0 or ptr==NULL in get_only_respcode()");
 1829 #   endif /* DEBUG */
 1830         return -1;
 1831     }
 1832 
 1833 #   ifdef DEBUG
 1834     if (debug & DEBUG_NNTP)
 1835         debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1836 #   endif /* DEBUG */
 1837     respcode = (int) strtol(ptr, &end, 10);
 1838     if (end == ptr) /* no leading numbers in response */
 1839         respcode = -1;
 1840     DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1841 
 1842     /*
 1843      * we also reconnect on ERR_FAULT if last_put was ARTICLE or LIST or POST
 1844      * as inn (2.2.3) sends ERR_FAULT on timeout
 1845      *
 1846      * what about other LIST cmds? (ACTIVE|COUNTS|OVERVIEW.FMT|...)
 1847      */
 1848     if (last_put[0] != '\0' && ((respcode == ERR_FAULT && (!strncmp(last_put, "ARTICLE", 7) || !strcmp(last_put, "POST") || !strcmp(last_put, "LIST"))) || respcode == ERR_GOODBYE || respcode == OK_GOODBYE) && strcmp(last_put, "QUIT")) {
 1849         /*
 1850          * Maybe server timed out.
 1851          * If so, retrying will force a reconnect.
 1852          */
 1853 #   ifdef DEBUG
 1854         if ((debug & DEBUG_NNTP) && verbose > 1)
 1855             debug_print_file("NNTP", "get_only_respcode() timeout");
 1856 #   endif /* DEBUG */
 1857         put_server(last_put);
 1858         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1859 
 1860         if (tin_errno || ptr == NULL) {
 1861 #   ifdef DEBUG
 1862             if ((debug & DEBUG_NNTP) && verbose > 1)
 1863                 debug_print_file("NNTP", "Error: tin_errno<>0 or ptr==NULL in get_only_respcode(retry)");
 1864 #   endif /* DEBUG */
 1865             return -1;
 1866         }
 1867 
 1868 #   ifdef DEBUG
 1869         if (debug & DEBUG_NNTP)
 1870             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1871 #   endif /* DEBUG */
 1872         respcode = (int) strtol(ptr, &end, 10);
 1873         if (end == ptr) /* no leading numbers in response */
 1874             respcode = -1;
 1875         DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1876     }
 1877     if (message != NULL && mlen > 1 && *end != '\0')        /* Pass out the rest of the text */
 1878         my_strncpy(message, ++end, mlen - 1);
 1879 
 1880     return respcode;
 1881 }
 1882 
 1883 
 1884 /*
 1885  * Get a response code from the server.
 1886  * Returns:
 1887  *  +ve NNTP return code
 1888  *  -1  on an error
 1889  * If 'message' is not NULL, then any trailing text after the response
 1890  *  code is copied into it.
 1891  * Performs authentication if required and repeats the last command if
 1892  * necessary after a timeout.
 1893  *
 1894  * TODO: make this handle 401 and 483 (RFC 3977) return codes.
 1895  *       as 401 requires the examination of the returned text besides the
 1896  *       return value, we have to "fix" all nntp_command(..., NULL, 0) and
 1897  *       get_only_respcode(NULL, 0) calls to do this properly.
 1898  */
 1899 int
 1900 get_respcode(
 1901     char *message,
 1902     size_t mlen)
 1903 {
 1904     int respcode;
 1905     char savebuf[NNTP_STRLEN];
 1906     char *ptr, *end;
 1907 
 1908     respcode = get_only_respcode(message, mlen);
 1909     if ((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) {
 1910         /*
 1911          * Server requires authentication.
 1912          */
 1913 #   ifdef DEBUG
 1914         if ((debug & DEBUG_NNTP) && verbose > 1)
 1915             debug_print_file("NNTP", "get_respcode() authentication");
 1916 #   endif /* DEBUG */
 1917         STRCPY(savebuf, last_put);
 1918 
 1919         if (!authenticate(nntp_server, userid, FALSE))
 1920             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
 1921 
 1922         if (nntp_caps.type == CAPABILITIES)
 1923             can_post = nntp_caps.post && !force_no_post;
 1924         else {
 1925             put_server("MODE READER");
 1926             if (get_only_respcode(message, mlen) == OK_CANPOST)
 1927                 can_post = TRUE && !force_no_post;
 1928         }
 1929         if (curr_group != NULL) {
 1930             DEBUG_IO((stderr, _("Rejoin current group\n")));
 1931             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
 1932             put_server(last_put);
 1933             if (nntpbuf_gets(last_put, NNTP_STRLEN, &nntp_buf) == NULL)
 1934                 *last_put = '\0';
 1935 #   ifdef DEBUG
 1936             if (debug & DEBUG_NNTP)
 1937                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
 1938 #   endif /* DEBUG */
 1939             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
 1940         }
 1941         STRCPY(last_put, savebuf);
 1942 
 1943         put_server(last_put);
 1944         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1945 
 1946         if (tin_errno) {
 1947 #   ifdef DEBUG
 1948             if ((debug & DEBUG_NNTP) && verbose > 1)
 1949                 debug_print_file("NNTP", "Error: tin_errno <> 0");
 1950 #   endif /* DEBUG */
 1951             return -1;
 1952         }
 1953 
 1954 #   ifdef DEBUG
 1955         if (debug & DEBUG_NNTP)
 1956             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1957 #   endif /* DEBUG */
 1958         if (ptr == NULL)
 1959             return -1;
 1960 
 1961         respcode = (int) strtol(ptr, &end, 10);
 1962         if (end == ptr) /* no leading numbers in response */
 1963             return -1;
 1964 
 1965         if (message != NULL && mlen > 1)                /* Pass out the rest of the text */
 1966             strncpy(message, end, mlen - 1);
 1967     }
 1968     return respcode;
 1969 }
 1970 
 1971 
 1972 /*
 1973  * Do an NNTP command. Send command to server, and read the reply.
 1974  * If the reply code matches success, then return an open file stream
 1975  * Return NULL if we did not see the response we wanted.
 1976  * If message is not NULL, then the trailing text of the reply string is
 1977  * copied into it for the caller to process.
 1978  */
 1979 FILE *
 1980 nntp_command(
 1981     const char *command,
 1982     int success,
 1983     char *message,
 1984     size_t mlen)
 1985 {
 1986 DEBUG_IO((stderr, "nntp_command(%s)\n", command));
 1987 #   ifdef DEBUG
 1988     if ((debug & DEBUG_NNTP) && verbose > 1)
 1989         debug_print_file("NNTP", "nntp_command(%s)", command);
 1990 #   endif /* DEBUG */
 1991     put_server(command);
 1992 
 1993     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 1994         if (get_respcode(message, mlen) != success) {
 1995 #   ifdef DEBUG
 1996             if ((debug & DEBUG_NNTP) && verbose > 1)
 1997                 debug_print_file("NNTP", "nntp_command(%s) NOT_OK", command);
 1998 #   endif /* DEBUG */
 1999             /* error_message(2, "%s", message); */
 2000             return (FILE *) 0;
 2001         }
 2002     }
 2003 #   ifdef DEBUG
 2004     if ((debug & DEBUG_NNTP) && verbose > 1)
 2005         debug_print_file("NNTP", "nntp_command(%s) OK", command);
 2006 #   endif /* DEBUG */
 2007     return FAKE_NNTP_FP;
 2008 }
 2009 
 2010 
 2011 /*
 2012  * same as above, but with a slightly more useful return code.
 2013  * TODO: use it instead of nntp_command in the rest of the code
 2014  *       (wherever it is more useful).
 2015  */
 2016 int
 2017 new_nntp_command(
 2018     const char *command,
 2019     int success,
 2020     char *message,
 2021     size_t mlen)
 2022 {
 2023     int respcode = 0;
 2024 
 2025 DEBUG_IO((stderr, "new_nntp_command(%s)\n", command));
 2026 #   ifdef DEBUG
 2027     if ((debug & DEBUG_NNTP) && verbose > 1)
 2028         debug_print_file("NNTP", "new_nntp_command(%s)", command);
 2029 #   endif /* DEBUG */
 2030     put_server(command);
 2031 
 2032     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 2033         if ((respcode = get_respcode(message, mlen)) != success) {
 2034 #   ifdef DEBUG
 2035             if ((debug & DEBUG_NNTP) && verbose > 1)
 2036                 debug_print_file("NNTP", "new_nntp_command(%s) NOT_OK - Expected: %d, got: %d", command, success, respcode);
 2037 #   endif /* DEBUG */
 2038             return respcode;
 2039         }
 2040     }
 2041 #   ifdef DEBUG
 2042     if ((debug & DEBUG_NNTP) && verbose > 1)
 2043         debug_print_file("NNTP", "new_nntp_command(%s) OK", command);
 2044 #   endif /* DEBUG */
 2045     return respcode;
 2046 }
 2047 
 2048 
 2049 static void
 2050 list_motd(
 2051     void)
 2052 {
 2053     char *ptr;
 2054     char *p;
 2055     char buf[NNTP_STRLEN];
 2056     int i;
 2057     size_t len;
 2058     unsigned int l = 0;
 2059 
 2060     buf[0] = '\0';
 2061     i = new_nntp_command("LIST MOTD", OK_MOTD, buf, sizeof(buf));
 2062 
 2063     switch (i) {
 2064         case OK_MOTD:
 2065 #   ifdef HAVE_COLOR
 2066             fcol(tinrc.col_message);
 2067 #   endif /* HAVE_COLOR */
 2068             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 2069 #   ifdef DEBUG
 2070                 if (debug & DEBUG_NNTP)
 2071                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 2072 #   endif /* DEBUG */
 2073                 /*
 2074                  * RFC 6048 2.5.2 "The information MUST be in UTF-8"
 2075                  *
 2076                  * TODO: - store a hash value of the entire motd in the server-rc
 2077                  *         and only if it differs from the old value display the
 2078                  *         motd?
 2079                  *       - use some sort of pager?
 2080                  *       - -> lang.c
 2081                  */
 2082                 p = my_strdup(ptr);
 2083                 len = strlen(p);
 2084                 process_charsets(&p, &len, "UTF-8", tinrc.mm_local_charset, FALSE);
 2085                 my_printf("%s%s\n", _("MOTD: "), p);
 2086                 free(p);
 2087                 l++;
 2088             }
 2089 #   ifdef HAVE_COLOR
 2090             fcol(tinrc.col_normal);
 2091 #   endif /* HAVE_COLOR */
 2092             if (l) {
 2093                 my_flush();
 2094                 sleep((l >> 1) | 0x01);
 2095             }
 2096             break;
 2097 
 2098         default:    /* common response codes are 500, 501, 503 */
 2099             break;
 2100     }
 2101 }
 2102 
 2103 #define SZ(a) sizeof((a))
 2104 
 2105 /*
 2106  * write data from write buffer to NNTP connection when requested
 2107  */
 2108 static int
 2109 nntpbuf_flush(
 2110     struct nntpbuf* buf)
 2111 {
 2112     if (!buf)
 2113         return EOF;
 2114 
 2115     while (buf->wr.ub > buf->wr.lb) {
 2116         ssize_t bytes_written;
 2117 
 2118 #ifdef NNTPS_ABLE
 2119         if (buf->tls_ctx)
 2120             bytes_written = tintls_write(buf->tls_ctx, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
 2121         else
 2122             bytes_written = write(buf->fd, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
 2123 #else
 2124         bytes_written = write(buf->fd, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
 2125 #endif /* NNTPS_ABLE */
 2126 
 2127         if (bytes_written < 0)
 2128             return EOF;
 2129 
 2130         buf->wr.lb += bytes_written;
 2131     }
 2132 
 2133     buf->wr.ub = buf->wr.lb = 0;
 2134 
 2135     return 0;
 2136 }
 2137 
 2138 
 2139 /*
 2140  * fputs(3) replacement using the NNTP connection
 2141  */
 2142 static int
 2143 nntpbuf_puts(
 2144     const char* data,
 2145     struct nntpbuf* buf)
 2146 {
 2147     int bytes_written = 0;
 2148     unsigned len, l;
 2149 
 2150     if (!buf || SZ(buf->wr.buf) == 0 || buf->wr.lb > buf->wr.ub)
 2151         return EOF;
 2152 
 2153     if (!data)
 2154         return 0;
 2155 
 2156     len = strlen(data);
 2157     while (len) {
 2158         if (buf->wr.ub == SZ(buf->wr.buf)) {
 2159             int retval = nntpbuf_flush(buf);
 2160 
 2161             if (retval != 0)
 2162                 return retval;
 2163         }
 2164 
 2165         l = len;
 2166         if (l > SZ(buf->wr.buf) - buf->wr.ub)
 2167             l = SZ(buf->wr.buf) - buf->wr.ub;
 2168 
 2169         memcpy(buf->wr.buf + buf->wr.ub, data, l);
 2170         buf->wr.ub += l;
 2171         data += l;
 2172         bytes_written += l;
 2173         len -= l;
 2174     }
 2175 
 2176     return bytes_written;
 2177 }
 2178 
 2179 
 2180 /*
 2181  * internal helper to refill the read buffer using the NNTP connection
 2182  */
 2183 static int
 2184 nntpbuf_refill(
 2185     struct nntpbuf *buf)
 2186 {
 2187     unsigned free_b;
 2188 
 2189     if (buf->rd.ub == buf->rd.lb)
 2190         buf->rd.ub = buf->rd.lb = 0;
 2191 
 2192     free_b = SZ(buf->rd.buf) - buf->rd.ub;
 2193     if (free_b) {
 2194         ssize_t bytes_read;
 2195 
 2196 #ifdef NNTPS_ABLE
 2197         if (buf->tls_ctx)
 2198             bytes_read = tintls_read(buf->tls_ctx, buf->rd.buf + buf->rd.ub, free_b);
 2199         else
 2200             bytes_read = read(buf->fd, buf->rd.buf + buf->rd.ub, free_b);
 2201 #else
 2202         bytes_read = read(buf->fd, buf->rd.buf + buf->rd.ub, free_b);
 2203 #endif /* NNTPS_ABLE */
 2204 
 2205         if (bytes_read > 0)
 2206             buf->rd.ub += bytes_read;
 2207 
 2208         return (int) bytes_read;
 2209     }
 2210     return 0;
 2211 }
 2212 
 2213 
 2214 /*
 2215  * fgetc(3) replacement using the NNTP connection
 2216  */
 2217 static int
 2218 nntpbuf_getc(
 2219     struct nntpbuf *buf)
 2220 {
 2221     int c = EOF;
 2222 
 2223     if (buf->rd.ub - buf->rd.lb == 0) {
 2224         int retval = nntpbuf_refill(buf);
 2225 
 2226         if (retval <= 0)
 2227             return retval;
 2228     }
 2229 
 2230     c = buf->rd.buf[buf->rd.lb];
 2231     buf->rd.lb += 1;
 2232 
 2233     return c;
 2234 }
 2235 
 2236 
 2237 /*
 2238  * ungetc(3) replacement using the NNTP connection
 2239  */
 2240 static int
 2241 nntpbuf_ungetc(
 2242     int c,
 2243     struct nntpbuf *buf)
 2244 {
 2245     if (buf->rd.lb == 0) {
 2246         if (buf->rd.ub == SZ(buf->rd.buf)) {
 2247             errno = ENOSPC;
 2248             return EOF;
 2249         }
 2250         memmove(buf->rd.buf+1, buf->rd.buf, buf->rd.ub);
 2251         buf->rd.lb += 1;
 2252     }
 2253 
 2254     buf->rd.lb -= 1;
 2255     buf->rd.buf[buf->rd.lb] = (unsigned char)c;
 2256 
 2257     return c;
 2258 }
 2259 
 2260 
 2261 /*
 2262  * fgets(3) replacement using the NNTP connection
 2263  */
 2264 static char *
 2265 nntpbuf_gets(
 2266     char *s,
 2267     int size,
 2268     struct nntpbuf *buf)
 2269 {
 2270     int write_at = 0;
 2271 
 2272     if (s == NULL || size == 0)
 2273         return s;
 2274 
 2275     s[size - 1] = '\0';
 2276     size -= 1;
 2277 
 2278     while (size) {
 2279         if (buf->rd.ub - buf->rd.lb == 0) {
 2280             int retval = nntpbuf_refill(buf);
 2281 
 2282             if (retval <= 0)
 2283                 return NULL;
 2284         }
 2285 
 2286         while (size && (buf->rd.ub - buf->rd.lb) > 0) {
 2287             s[write_at++] = buf->rd.buf[buf->rd.lb++];
 2288             size -= 1;
 2289 
 2290             if (s[write_at - 1] == '\n' && size) {
 2291                 s[write_at] = '\0';
 2292                 goto out;
 2293             }
 2294         }
 2295     }
 2296 
 2297 out:
 2298     return s;
 2299 }
 2300 
 2301 
 2302 static void
 2303 nntpbuf_close(
 2304     struct nntpbuf *buf)
 2305 {
 2306     if (!buf)
 2307         return;
 2308 
 2309 #ifdef NNTPS_ABLE
 2310     if (buf->tls_ctx) {
 2311         int result = tintls_close(buf->tls_ctx);
 2312 
 2313         if (result != 0) {
 2314             /* warn? */
 2315         }
 2316     }
 2317     buf->tls_ctx = NULL;
 2318 #endif /* NNTPS_ABLE */
 2319 
 2320     if (buf->fd >= 0)
 2321         close(buf->fd);
 2322 
 2323     buf->fd = -1;
 2324 
 2325     buf->rd.lb = buf->rd.ub = 0;
 2326     buf->wr.lb = buf->wr.ub = 0;
 2327 }
 2328 
 2329 
 2330 static int
 2331 nntpbuf_is_open(
 2332     struct nntpbuf *buf)
 2333 {
 2334     return buf->fd != -1;
 2335 }
 2336 
 2337 #undef SZ
 2338 
 2339 int
 2340 nntp_conninfo(
 2341     FILE *stream)
 2342 {
 2343     int retval = 0;
 2344 
 2345     fprintf(stream, "\nConnection details:\n");
 2346     fprintf(stream, "-------------------\n");
 2347     fprintf(stream, "NNTPSERVER    : %s\n", nntp_server);
 2348     fprintf(stream, "NNTPPORT      : %d\n", nntp_tcp_port);
 2349     if (nntp_caps.type == CAPABILITIES) {
 2350         if (*nntp_caps.implementation)
 2351             fprintf(stream, "IMPLEMENTATION: %s\n", nntp_caps.implementation);
 2352 #if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 2353         if (nntp_caps.maxartnum)
 2354             fprintf(stream, "MAXARTNUM     : %"T_ARTNUM_PFMT"\n", nntp_caps.maxartnum);
 2355 #endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 2356     }
 2357 
 2358 #ifdef NNTPS_ABLE
 2359     if (nntp_buf.tls_ctx)
 2360         retval = tintls_conninfo(nntp_buf.tls_ctx, stream);
 2361 
 2362 #endif /* NNTPS_ABLE */
 2363 
 2364     return retval;
 2365 }
 2366 
 2367 
 2368 #   if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
 2369 /*
 2370  * MAXARTNUM - <tnqm14$35bas$1@news.trigofacile.com>
 2371  */
 2372 void
 2373 set_maxartnum(
 2374     t_bool reconnect)
 2375 {
 2376     char cmd[NNTP_STRLEN];
 2377     char line[NNTP_STRLEN] = { '\0' };
 2378     static int cnt = 0;
 2379 
 2380     if (nntp_caps.maxartnum < T_ARTNUM_CONST(2147483647) || (!reconnect && cnt))
 2381         return;
 2382     else
 2383         cnt++;
 2384 
 2385     snprintf(cmd, sizeof(cmd), "MAXARTNUM %"T_ARTNUM_PFMT, nntp_caps.maxartnum);
 2386 
 2387     switch (new_nntp_command(cmd, OK_EXTENSIONS, line, sizeof(line))) {
 2388         case OK_EXTENSIONS:
 2389             nntp_caps.mode_reader = FALSE;
 2390             nntp_caps.reader = TRUE;
 2391             nntp_caps.list_newsgroups = TRUE;
 2392             nntp_caps.list_active = TRUE;
 2393             break;
 2394 
 2395         default:
 2396             nntp_caps.maxartnum = T_ARTNUM_CONST(0);
 2397             break;
 2398     }
 2399 }
 2400 #   endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
 2401 #endif /* NNTP_ABLE */