"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/nntplib.c" (22 Dec 2021, 55748 Bytes) of package /linux/misc/tin-2.6.1.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 last Fossies "Diffs" side-by-side code changes report: 2.4.5_vs_2.6.0.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : nntplib.c
    4  *  Author    : S. Barber & I. Lea
    5  *  Created   : 1991-01-12
    6  *  Updated   : 2021-02-27
    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 static TCP *nntp_rd_fp = NULL;
   43 
   44 #ifdef NNTP_ABLE
   45     static TCP *nntp_wr_fp = NULL;
   46     /* Copy of last NNTP command sent, so we can retry it if needed */
   47     static char last_put[NNTP_STRLEN];
   48     static constext *xover_cmds = "XOVER";
   49     static constext *xhdr_cmds = "XHDR";
   50     /* Set so we don't reconnect just to QUIT */
   51     static t_bool quitting = FALSE;
   52 #endif /* NNTP_ABLE */
   53 
   54 /*
   55  * local prototypes
   56  */
   57 #ifdef NNTP_ABLE
   58     static int mode_reader(t_bool *sec);
   59     static int reconnect(int retry);
   60     static int server_init(char *machine, const char *cservice, unsigned short port, char *text, size_t mlen);
   61     static void close_server(t_bool send_no_quit);
   62     static void list_motd(void);
   63 #   ifdef INET6
   64         static int get_tcp6_socket(char *machine, unsigned short port);
   65 #   else
   66         static int get_tcp_socket(char *machine, char *service, unsigned short port);
   67 #   endif /* INET6 */
   68 #   ifdef DECNET
   69         static int get_dnet_socket(char *machine, char *service);
   70 #   endif /* DECNET */
   71 #endif /* NNTP_ABLE */
   72 
   73 
   74 /*
   75  * Return the actual fd in use for the nntp read-side socket
   76  * This is a leak of internal state and should go away if possible
   77  */
   78 FILE *
   79 get_nntp_fp(
   80     FILE *fp)
   81 {
   82     return (fp == FAKE_NNTP_FP ? nntp_rd_fp : fp);
   83 }
   84 
   85 
   86 #if 0 /* unused */
   87 FILE *
   88 get_nntp_wr_fp(
   89     FILE *fp)
   90 {
   91     return (fp == FAKE_NNTP_FP ? nntp_wr_fp : fp);
   92 }
   93 #endif /* 0 */
   94 
   95 /*
   96  * getserverbyfile(file)
   97  *
   98  * Get the name of a server from a named file.
   99  *  Handle white space and comments.
  100  *  Use NNTPSERVER environment variable if set.
  101  *
  102  *  Parameters: "file" is the name of the file to read.
  103  *
  104  *  Returns: Pointer to static data area containing the
  105  *           first non-ws/comment line in the file.
  106  *           NULL on error (or lack of entry in file).
  107  *
  108  *  Side effects: None.
  109  */
  110 char *
  111 getserverbyfile(
  112     const char *file)
  113 {
  114     static char buf[256];
  115 #ifdef NNTP_ABLE
  116     FILE *fp;
  117     char *cp;
  118 #   if !defined(HAVE_SETENV) && defined(HAVE_PUTENV)
  119     char tmpbuf[256];
  120     char *new_env;
  121     static char *old_env = NULL;
  122 #   endif /* !HAVE_SETENV && HAVE_PUTENV */
  123 #endif /* NNTP_ABLE */
  124 
  125     if (read_saved_news) {
  126         STRCPY(buf, "reading saved news");
  127         return buf;
  128     }
  129 
  130     if (!read_news_via_nntp) {
  131         STRCPY(buf, "local");   /* what if a server is named "local"? */
  132         return buf;
  133     }
  134 
  135 #ifdef NNTP_ABLE
  136     if (cmdline.args & CMDLINE_NNTPSERVER) {
  137         get_nntpserver(buf, sizeof(buf), cmdline.nntpserver);
  138 #   ifdef HAVE_SETENV
  139         setenv("NNTPSERVER", buf, 1);
  140 #   else
  141 #       ifdef HAVE_PUTENV
  142         snprintf(tmpbuf, sizeof(tmpbuf), "NNTPSERVER=%s", buf);
  143         new_env = my_strdup(tmpbuf);
  144         putenv(new_env);
  145         FreeIfNeeded(old_env);
  146         old_env = new_env; /* we are 'leaking' the last malloced mem at exit here */
  147 #       endif /* HAVE_PUTENV */
  148 #   endif /* HAVE_SETENV */
  149         return buf;
  150     }
  151 
  152     if ((cp = getenv("NNTPSERVER")) != NULL) {
  153         get_nntpserver(buf, sizeof(buf), cp);
  154         return buf;
  155     }
  156 
  157     if (file == NULL)
  158         return NULL;
  159 
  160     if ((fp = fopen(file, "r")) != NULL) {
  161 
  162         while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
  163             if (*buf == '\n' || *buf == '#')
  164                 continue;
  165 
  166             if ((cp = strrchr(buf, '\n')) != NULL)
  167                 *cp = '\0';
  168 
  169             (void) fclose(fp);
  170             return buf;
  171         }
  172         (void) fclose(fp);
  173     }
  174 
  175 #   ifdef NNTP_DEFAULT_SERVER
  176     if (*(NNTP_DEFAULT_SERVER))
  177         return strcpy(buf, NNTP_DEFAULT_SERVER);
  178 #   endif /* NNTP_DEFAULT_SERVER */
  179 
  180 #endif /* NNTP_ABLE */
  181     return NULL;    /* No entry */
  182 }
  183 
  184 
  185 /*
  186  * server_init  Get a connection to the remote server.
  187  *
  188  *  Parameters: "machine" is the machine to connect to.
  189  *          "service" is the service to connect to on the machine.
  190  *          "port" is the service port to connect to.
  191  *
  192  *  Returns:    server's initial response code
  193  *          or -errno
  194  *
  195  *  Side effects:   Connects to server.
  196  *          "nntp_rd_fp" and "nntp_wr_fp" are fp's
  197  *          for reading and writing to server.
  198  *          "text" is updated to contain the rest of response string from server
  199  */
  200 #ifdef NNTP_ABLE
  201 static int
  202 server_init(
  203     char *machine,
  204     const char *cservice,   /* usually a literal */
  205     unsigned short port,
  206     char *text,
  207     size_t mlen)
  208 {
  209 #   ifndef INET6
  210     char temp[256];
  211     char *service = strncpy(temp, cservice, sizeof(temp) - 1); /* ...calls non-const funcs */
  212 #   endif /* !INET6 */
  213     int sockt_rd, sockt_wr;
  214 
  215 #   ifdef DECNET
  216     char *cp;
  217 
  218     cp = strchr(machine, ':');
  219 
  220     if (cp && cp[1] == ':') {
  221         *cp = '\0';
  222         sockt_rd = get_dnet_socket(machine, service);
  223     } else
  224         sockt_rd = get_tcp_socket(machine, service, port);
  225 #   else
  226 #       ifdef INET6
  227     sockt_rd = get_tcp6_socket(machine, port);
  228 #       else
  229     sockt_rd = get_tcp_socket(machine, service, port);
  230 #       endif /* INET6 */
  231 #   endif /* DECNET */
  232 
  233     if (sockt_rd < 0)
  234         return sockt_rd;
  235 
  236     /*
  237      * Now we'll make file pointers (i.e., buffered I/O) out of
  238      * the socket file descriptor. Note that we can't just
  239      * open a fp for reading and writing -- we have to open
  240      * up two separate fp's, one for reading, one for writing.
  241      */
  242 
  243     if ((nntp_rd_fp = (TCP *) s_fdopen(sockt_rd, "r")) == NULL) {
  244         perror("server_init: fdopen() #1");
  245         s_close(sockt_rd);
  246         return -errno;
  247     }
  248 
  249     if ((sockt_wr = s_dup(sockt_rd)) < 0) {
  250         perror("server_init: dup()");
  251         s_fclose(nntp_rd_fp);
  252         nntp_rd_fp = NULL;
  253         return -errno;
  254     }
  255 
  256 #   ifdef TLI /* Transport Level Interface */
  257     if (t_sync(sockt_rd) < 0) { /* Sync up new fd with TLI */
  258         t_error("server_init: t_sync()");
  259         s_fclose(nntp_rd_fp);
  260         nntp_rd_fp = NULL;
  261         return -EPROTO;
  262     }
  263 #   else
  264     if ((nntp_wr_fp = (TCP *) s_fdopen(sockt_wr, "w")) == NULL) {
  265         perror("server_init: fdopen() #2");
  266         s_close(sockt_wr);
  267         s_fclose(nntp_rd_fp);
  268         nntp_rd_fp = NULL;
  269         return -errno;
  270     }
  271 #   endif /* TLI */
  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             s_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     s_puts(string, nntp_wr_fp);
  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         s_puts(string, nntp_wr_fp);
  782         s_puts("\r\n", nntp_wr_fp);
  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) s_flush(nntp_wr_fp);
  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     if (nntp_wr_fp)
  825         s_fclose(nntp_wr_fp);
  826     if (nntp_rd_fp)
  827         s_fclose(nntp_rd_fp);
  828     nntp_rd_fp = nntp_wr_fp = NULL;
  829 
  830     if (!tinrc.auto_reconnect)
  831         ring_bell();
  832 
  833     DEBUG_IO((stderr, _("\nServer timed out, trying reconnect # %d\n"), retry));
  834 
  835     /*
  836      * set signal_context temporary to cReconnect to avoid trouble when receiving
  837      * SIGWINCH while being in prompt_yn()
  838      */
  839     signal_context = cReconnect;
  840 
  841     /*
  842      * Exit tin if there are no more tries or if the user says no to reconnect.
  843      * The exit code stops tin from trying to disconnect again - the connection
  844      * is already dead
  845      */
  846     if (retry > NNTP_TRY_RECONNECT || (!tinrc.auto_reconnect && prompt_yn(_(txt_reconnect_to_news_server), TRUE) != 1)) {
  847         if (!strcmp("POST", last_put)) {
  848             unlink(backup_article_name(article_name));
  849             rename_file(article_name, dead_article);
  850             if (tinrc.keep_dead_articles)
  851                 append_file(dead_article, dead_articles);
  852         }
  853         if (retry > NNTP_TRY_RECONNECT) {
  854 #   ifdef DEBUG
  855             /* TODO: -> lang.c */
  856             if ((debug & DEBUG_NNTP) && verbose > 1)
  857                 debug_print_file("NNTP", "reconnect(%d) limit %d reached, giving up.", retry, NNTP_TRY_RECONNECT);
  858 #   endif /* DEBUG */
  859         }
  860         tin_done(NNTP_ERROR_EXIT, _("NNTP connection error. Exiting..."));      /* user said no to reconnect or no more retries */
  861     }
  862 
  863     /* reset signal_context */
  864     signal_context = save_signal_context;
  865 
  866     clear_message();
  867     strcpy(buf, last_put);          /* Keep copy here, it will be clobbered a lot otherwise */
  868 
  869     if (!nntp_open()) {
  870         /* Re-establish our current group and resend last command */
  871         if (curr_group != NULL) {
  872             DEBUG_IO((stderr, _("Rejoin current group\n")));
  873             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
  874             put_server(last_put);
  875             if (s_gets(last_put, NNTP_STRLEN, nntp_rd_fp) == NULL)
  876                 *last_put = '\0';
  877 #   ifdef DEBUG
  878             if (debug & DEBUG_NNTP)
  879                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
  880 #   endif /* DEBUG */
  881             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
  882         }
  883         DEBUG_IO((stderr, _("Resend last command (%s)\n"), buf));
  884         put_server(buf);
  885         did_reconnect = TRUE;
  886         retry = NNTP_TRY_RECONNECT;
  887     }
  888 
  889     return retry;
  890 }
  891 
  892 
  893 /*
  894  * Read a line of data from the NNTP socket. If something gives, do reconnect
  895  *
  896  *  Parameters: "string" has the buffer space for the line received.
  897  *          "size" is maximum size of the buffer to read.
  898  *
  899  *  Returns:    NULL on end of stream, or a line of data.
  900  *              Basically, we try to act like fgets() on an NNTP stream.
  901  *
  902  *  Side effects:   Talks to server, changes contents of "string".
  903  *          Reopens connection when necessary and requested.
  904  *          Exits via tin_done() if fatal error occurs.
  905  */
  906 char *
  907 get_server(
  908     char *string,
  909     int size)
  910 {
  911     static int retry_cnt = 0;
  912 
  913     reconnected_in_last_get_server = FALSE;
  914     errno = 0;
  915 
  916     /*
  917      * NULL socket reads indicates socket has closed. Try a few times more
  918      *
  919      * Leave the s_gets() after a timeout for these cases:
  920      *   -some servers do not close the connection but simply do not send any
  921      *    response data
  922      *   -the network connection went down
  923      */
  924 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  925     alarm((unsigned) tinrc.nntp_read_timeout_secs);
  926 #   endif /* HAVE_ALARM && SIGALRM */
  927     while (nntp_rd_fp == NULL || s_gets(string, size, nntp_rd_fp) == NULL) {
  928         if (errno == EINTR) {
  929             errno = 0;
  930 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  931             alarm((unsigned) tinrc.nntp_read_timeout_secs);     /* Restart the timer */
  932 #   endif /* HAVE_ALARM && SIGALRM */
  933             continue;
  934         }
  935 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  936         alarm(0);
  937 #   endif /* HAVE_ALARM && SIGALRM */
  938         if (quitting)                       /* Don't bother to reconnect */
  939             tin_done(NNTP_ERROR_EXIT, NULL);        /* And don't try to disconnect again! */
  940 
  941 #   ifdef DEBUG
  942         if (errno != 0 && errno != EINTR)   /* Will only confuse end users */
  943             perror_message("get_server()");
  944 #   endif /* DEBUG */
  945 
  946         /*
  947          * Reconnect only if command was not "QUIT" anyway (in which case a
  948          * reconnection would be useless because the connection will be
  949          * closed immediately). Also prevents tin from asking to reconnect
  950          * when user is quitting tin if tinrc.auto_reconnect is false.
  951          */
  952         if (strcmp(last_put, "QUIT")) {
  953             /*
  954              * Typhoon v2.1.1.363 colses the connection right after an unknown
  955              * command, (i.e. CAPABILITIES) so we avoid the reissue it on a
  956              * reconnect if it was the last command.
  957              */
  958             if (!strcmp(last_put, "CAPABILITIES")) {
  959                 strcpy(last_put, "MODE READER");
  960                 nntp_caps.type = BROKEN;
  961             }
  962             retry_cnt = reconnect(++retry_cnt);     /* Will abort when out of tries */
  963             reconnected_in_last_get_server = TRUE;
  964         } else {
  965             /*
  966              * Use standard NNTP closing message and response code if user is
  967              * quitting tin and leave loop.
  968              */
  969             strncpy(string, _(txt_nntp_ok_goodbye), (size_t) (size - 3));
  970             strcat(string, "\r\n");     /* tin_fgets() needs CRLF */
  971             break;
  972         }
  973     }
  974 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  975     alarm(0);
  976 #   endif /* HAVE_ALARM && SIGALRM */
  977     retry_cnt = 0;
  978     return string;
  979 }
  980 
  981 
  982 /*
  983  * Send "QUIT" command and close the connection to the server
  984  *
  985  * Side effects: Closes the connection to the server.
  986  *                You can't use "put_server" or "get_server" after this
  987  *                routine is called.
  988  *
  989  * TODO: remember servers response string and if it contains anything else
  990  *       than just "." (i.e. transfer statistics) present it to the user?
  991  *
  992  */
  993 static void
  994 close_server(
  995     t_bool send_no_quit)
  996 {
  997     if (!send_no_quit && nntp_wr_fp && nntp_rd_fp) {
  998 
  999         if (!batch_mode || verbose) {
 1000             char *msg;
 1001 
 1002             msg = strunc(_(txt_disconnecting), cCOLS - 1);
 1003             my_fputs(msg, stdout);
 1004             my_fputc('\n', stdout);
 1005             free(msg);
 1006         }
 1007         nntp_command("QUIT", OK_GOODBYE, NULL, 0);
 1008         quitting = TRUE;                                        /* Don't reconnect just for this */
 1009     }
 1010     if (nntp_wr_fp)
 1011         (void) s_fclose(nntp_wr_fp);
 1012     if (nntp_rd_fp)
 1013         (void) s_fclose(nntp_rd_fp);
 1014     s_end();
 1015     nntp_wr_fp = nntp_rd_fp = NULL;
 1016 }
 1017 
 1018 
 1019 /*
 1020  * Try and use CAPABILITIES here. Get this list before issuing other NNTP
 1021  * commands because the correct methods may be mentioned in the list of
 1022  * extensions.
 1023  *
 1024  * Sets up: t_capabilities nntp_caps
 1025  */
 1026 int
 1027 check_extensions(
 1028     int rvl)
 1029 {
 1030     char *d;
 1031     char *ptr;
 1032     char buf[NNTP_STRLEN];
 1033     int i;
 1034     int ret = 0;
 1035 
 1036     buf[0] = '\0';
 1037 
 1038     /* rvl > 0 = manually send "CAPABILITIES" to avoid endless AUTH loop */
 1039     i = rvl ? rvl : new_nntp_command("CAPABILITIES", INF_CAPABILITIES, buf, sizeof(buf));
 1040     switch (i) {
 1041         case INF_CAPABILITIES:
 1042             /* clear capabilities */
 1043             nntp_caps.type = CAPABILITIES;
 1044             nntp_caps.version = 0;
 1045             nntp_caps.mode_reader = FALSE;
 1046             nntp_caps.reader = FALSE;
 1047             nntp_caps.post = FALSE;
 1048             nntp_caps.list_active = FALSE;
 1049             nntp_caps.list_active_times = FALSE;
 1050             nntp_caps.list_distrib_pats = FALSE;
 1051             nntp_caps.list_headers = FALSE;
 1052             FreeAndNull(nntp_caps.headers_range);
 1053             FreeAndNull(nntp_caps.headers_id);
 1054             nntp_caps.list_newsgroups = FALSE;
 1055             nntp_caps.list_overview_fmt = FALSE;
 1056             nntp_caps.list_motd = FALSE;
 1057             nntp_caps.list_subscriptions = FALSE;
 1058             nntp_caps.list_distributions = FALSE;
 1059             nntp_caps.list_moderators = FALSE;
 1060             nntp_caps.list_counts = FALSE;
 1061             nntp_caps.xpat = TRUE; /* toggles to false if fails, INN > 2.7.0 announces it */
 1062             nntp_caps.hdr = FALSE;
 1063             nntp_caps.hdr_cmd = NULL;
 1064             nntp_caps.over = FALSE;
 1065             nntp_caps.over_msgid = FALSE;
 1066             nntp_caps.over_cmd = NULL;
 1067             nntp_caps.newnews = FALSE;
 1068             FreeAndNull(nntp_caps.implementation);
 1069             nntp_caps.starttls = FALSE;
 1070             nntp_caps.authinfo_user = FALSE;
 1071             nntp_caps.authinfo_sasl = FALSE;
 1072             nntp_caps.authinfo_state = FALSE;
 1073             nntp_caps.sasl = SASL_NONE;
 1074             nntp_caps.compress = FALSE;
 1075             nntp_caps.compress_algorithm = COMPRESS_NONE;
 1076 #   if 0
 1077             nntp_caps.streaming = FALSE;
 1078             nntp_caps.ihave = FALSE;
 1079 #   endif /* 0 */
 1080 #   ifndef BROKEN_LISTGROUP
 1081             nntp_caps.broken_listgroup = FALSE;
 1082 #   else
 1083             nntp_caps.broken_listgroup = TRUE;
 1084 #   endif /* !BROKEN_LISTGROUP */
 1085 
 1086             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1087 #   ifdef DEBUG
 1088                 if (debug & DEBUG_NNTP)
 1089                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1090 #   endif /* DEBUG */
 1091                 /* look for version number(s) */
 1092                 if (!nntp_caps.version && nntp_caps.type == CAPABILITIES) {
 1093                     if (!strncasecmp(ptr, "VERSION", 7)) {
 1094                         d = ptr + 7;
 1095                         d = strpbrk(d, " \t");
 1096                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1097                             d++;
 1098                             nntp_caps.version = (unsigned int) atoi(d);
 1099                             d = strpbrk(d, " \t");
 1100                         }
 1101                     }
 1102                 }
 1103                 /* we currently only support CAPABILITIES VERSION 2 */
 1104                 if (nntp_caps.version == 2) {
 1105                     /*
 1106                      * check for LIST variants
 1107                      */
 1108                     if (!strncasecmp(ptr, "LIST", 4)) {
 1109                         d = ptr + 4;
 1110                         d = strpbrk(d, " \t");
 1111                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1112                             d++;
 1113                             if (!strncasecmp(d, "ACTIVE.TIMES", 12))
 1114                                 nntp_caps.list_active_times = TRUE;
 1115                             else if (!strncasecmp(d, "ACTIVE", 6))
 1116                                 nntp_caps.list_active = TRUE;
 1117                             else if (!strncasecmp(d, "DISTRIB.PATS", 12))
 1118                                 nntp_caps.list_distrib_pats = TRUE;
 1119                             else if (!strncasecmp(d, "DISTRIBUTIONS", 13)) /* RFC 6048 */
 1120                                 nntp_caps.list_distributions = TRUE;
 1121                             else if (!strncasecmp(d, "HEADERS", 7))
 1122                                 nntp_caps.list_headers = TRUE; /* HDR requires LIST HEADERS, but not vice versa */
 1123                             else if (!strncasecmp(d, "NEWSGROUPS", 10))
 1124                                 nntp_caps.list_newsgroups = TRUE;
 1125                             else if (!strncasecmp(d, "OVERVIEW.FMT", 12)) /* OVER requires OVERVIEW.FMT, but not vice versa */
 1126                                 nntp_caps.list_overview_fmt = TRUE;
 1127                             else if (!strncasecmp(d, "MOTD", 4)) /* RFC 6048 */
 1128                                 nntp_caps.list_motd = TRUE;
 1129                             else if (!strncasecmp(d, "SUBSCRIPTIONS", 13)) /* RFC 6048 */
 1130                                 nntp_caps.list_subscriptions = TRUE;
 1131                             else if (!strncasecmp(d, "MODERATORS", 10)) /* RFC 6048 */
 1132                                 nntp_caps.list_moderators = TRUE;
 1133                             else if (!strncasecmp(d, "COUNTS", 6)) /* RFC 6048 */
 1134                                 nntp_caps.list_counts = TRUE;
 1135                             d = strpbrk(d, " \t");
 1136                         }
 1137                     } else if (!strncasecmp(ptr, "IMPLEMENTATION", 14)) {
 1138                         FreeIfNeeded(nntp_caps.implementation);
 1139                         nntp_caps.implementation = my_strdup(ptr + 14);
 1140                         str_trim(nntp_caps.implementation);
 1141                     } else if (!strcasecmp(ptr, "MODE-READER")) {
 1142                         if (!nntp_caps.reader)
 1143                             nntp_caps.mode_reader = TRUE;
 1144                     } else if (!strcasecmp(ptr, "READER")) { /* if we saw READER, "LIST ACTIVE" and "LIST NEWSGROUPS" must be implemented */
 1145                         nntp_caps.reader = TRUE;
 1146                         nntp_caps.mode_reader = FALSE;
 1147                         nntp_caps.list_newsgroups = TRUE;
 1148                         nntp_caps.list_active = TRUE;
 1149                     } else if (!strcasecmp(ptr, "POST"))
 1150                         nntp_caps.post = TRUE;
 1151                     else if (!strcasecmp(ptr, "NEWNEWS"))
 1152                         nntp_caps.newnews = TRUE;
 1153                     else if (!strcasecmp(ptr, "XPAT")) /* extension, RFC 2980 */
 1154                         nntp_caps.xpat = TRUE;
 1155                     else if (!strcasecmp(ptr, "STARTTLS"))
 1156                         nntp_caps.starttls = TRUE;
 1157                     /*
 1158                      * NOTE: if we saw OVER, LIST OVERVIEW.FMT _must_ be implemented
 1159                      */
 1160                     else if (!strncasecmp(ptr, &xover_cmds[1], strlen(&xover_cmds[1]))) {
 1161                         nntp_caps.list_overview_fmt = nntp_caps.over = TRUE;
 1162                         nntp_caps.over_cmd = &xover_cmds[1];
 1163                         d = ptr + strlen(&xover_cmds[1]);
 1164                         d = strpbrk(d, " \t");
 1165                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1166                             d++;
 1167                             if (!strcasecmp(d, "MSGID"))
 1168                                 nntp_caps.over_msgid = TRUE;
 1169                             d = strpbrk(d, " \t");
 1170                         }
 1171                     }
 1172                     /*
 1173                      * NOTE: if we saw HDR, LIST HEADERS _must_ be implemented
 1174                      */
 1175                     else if (!strncasecmp(ptr, &xhdr_cmds[1], strlen(&xhdr_cmds[1]))) {
 1176                         nntp_caps.hdr_cmd = &xhdr_cmds[1];
 1177                         nntp_caps.list_headers = nntp_caps.hdr = TRUE;
 1178                         nntp_caps.headers_range = my_strdup("");
 1179                         nntp_caps.headers_id = my_strdup("");
 1180                     } else if (!strncasecmp(ptr, "AUTHINFO", 8)) {
 1181                         d = ptr + 8;
 1182                         d = strpbrk(d, " \t");
 1183                         if (d == NULL) /* AUTHINFO without args */
 1184                             nntp_caps.authinfo_state = TRUE;
 1185                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1186                             d++;
 1187                             if (!strncasecmp(d, "USER", 4))
 1188                                 nntp_caps.authinfo_user = TRUE;
 1189                             if (!strncasecmp(d, "SASL", 4))
 1190                                 nntp_caps.authinfo_sasl = TRUE;
 1191                             d = strpbrk(d, " \t");
 1192                         }
 1193                     } else if (!strncasecmp(ptr, "SASL", 4)) {
 1194                         d = ptr + 4;
 1195                         d = strpbrk(d, " \t");
 1196                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1197                             d++;
 1198                             if (!strncasecmp(d, "CRAM-MD5", 8)) { /* RFC 2195 */
 1199                                 nntp_caps.authinfo_sasl = TRUE;
 1200                                 nntp_caps.sasl |= SASL_CRAM_MD5;
 1201                             }
 1202                             if (!strncasecmp(d, "DIGEST-MD5", 10)) { /* RFC 2831 */
 1203                                 nntp_caps.authinfo_sasl = TRUE;
 1204                                 nntp_caps.sasl |= SASL_DIGEST_MD5;
 1205                             }
 1206                             if (!strncasecmp(d, "PLAIN", 5)) { /* RFC 4616 */
 1207                                 nntp_caps.authinfo_sasl = TRUE;
 1208                                 nntp_caps.sasl |= SASL_PLAIN;
 1209                             }
 1210                             if (!strncasecmp(d, "GSSAPI", 6)) { /* RFC 4752 */
 1211                                 nntp_caps.authinfo_sasl = TRUE;
 1212                                 nntp_caps.sasl |= SASL_GSSAPI;
 1213                             }
 1214                             if (!strncasecmp(d, "EXTERNAL", 8)) { /* RFC 4422 */
 1215                                 nntp_caps.authinfo_sasl = TRUE;
 1216                                 nntp_caps.sasl |= SASL_EXTERNAL;
 1217                             }
 1218                             /* inn also can do these */
 1219                             if (!strncasecmp(d, "OTP", 3)) { /* RFC 2444 */
 1220                                 nntp_caps.authinfo_sasl = TRUE;
 1221                                 nntp_caps.sasl |= SASL_OTP;
 1222                             }
 1223                             if (!strncasecmp(d, "NTLM", 4)) { /* Microsoft */
 1224                                 nntp_caps.authinfo_sasl = TRUE;
 1225                                 nntp_caps.sasl |= SASL_NTLM;
 1226                             }
 1227                             if (!strncasecmp(d, "LOGIN", 5)) { /* Microsoft */
 1228                                 nntp_caps.authinfo_sasl = TRUE;
 1229                                 nntp_caps.sasl |= SASL_LOGIN;
 1230                             }
 1231                         }
 1232                     } else if (!strncasecmp(ptr, "COMPRESS", 8)) { /* RFC 8054 */
 1233                         d = ptr + 8;
 1234                         d = strpbrk(d, " \t");
 1235                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1236                             d++;
 1237                             if (!strncasecmp(d, "DEFLATE", 7)) {
 1238                                 nntp_caps.compress = TRUE;
 1239                                 nntp_caps.compress_algorithm |= COMPRESS_DEFLATE;
 1240                             }
 1241                         }
 1242                     }
 1243 #       if 0 /* we don't need these */
 1244                     else if (!strcasecmp(ptr, "IHAVE"))
 1245                         nntp_caps.ihave = TRUE;
 1246                     else if (!strcasecmp(ptr, "STREAMING"))
 1247                         nntp_caps.streaming = TRUE;
 1248 #       endif /* 0 */
 1249                 /* XZVER, XZHDR, ... */
 1250                 } else
 1251                     nntp_caps.type = NONE;
 1252             }
 1253             break;
 1254 
 1255         /*
 1256          * XanaNewz 2 Server Version 2.0.0.3 doesn't know CAPABILITIES
 1257          * but responds with 400 _without_ closing the connection. If
 1258          * you use tin on a XanaNewz 2 Server comment out the following
 1259          * case.
 1260          */
 1261 #       if 1
 1262         case ERR_GOODBYE:
 1263             ret = i;
 1264             error_message(2, "%s", buf);
 1265             break;
 1266 #       endif /* 1 */
 1267 
 1268         default:
 1269             break;
 1270     }
 1271 
 1272     return ret;
 1273 }
 1274 
 1275 
 1276 /*
 1277  * Switch INN into NNRP mode with 'mode reader'
 1278  */
 1279 static int
 1280 mode_reader(
 1281     t_bool *sec)
 1282 {
 1283     int ret = 0;
 1284 
 1285     if (!nntp_caps.reader) {
 1286         char line[NNTP_STRLEN];
 1287 #   ifdef DEBUG
 1288         if ((debug & DEBUG_NNTP) && verbose > 1)
 1289             debug_print_file("NNTP", "mode_reader(MODE READER)");
 1290 #   endif /* DEBUG */
 1291         DEBUG_IO((stderr, "nntp_command(MODE READER)\n"));
 1292         put_server("MODE READER");
 1293 
 1294         /*
 1295          * According to RFC 3977 (5.3), MODE READER may only return the
 1296          * following response codes:
 1297          *
 1298          *   200 (OK_CANPOST)     Hello, you can post
 1299          *   201 (OK_NOPOST)      Hello, you can't post
 1300          *   502 (ERR_ACCESS)     Service unavailable
 1301          *
 1302          * However, there are servers out there (e.g. Delegate 9.8.x) that do
 1303          * not implement this command and therefore return ERR_COMMAND (500).
 1304          * Unfortunately there are some new servers out there (i.e. INN 2.4.0
 1305          * (20020220 prerelease) which do return ERR_COMMAND if they are feed
 1306          * only servers.
 1307          */
 1308 
 1309         switch ((ret = get_respcode(line, sizeof(line)))) {
 1310             case OK_CANPOST:
 1311                 can_post = TRUE && !force_no_post;
 1312                 *sec = TRUE;
 1313                 ret = 0;
 1314                 break;
 1315 
 1316             case OK_NOPOST:
 1317                 can_post = FALSE;
 1318                 *sec = TRUE;
 1319                 ret = 0;
 1320                 break;
 1321 
 1322             case ERR_GOODBYE:
 1323             case ERR_ACCESS:
 1324                 error_message(2, "%s", line);
 1325                 return ret;
 1326 
 1327             case ERR_COMMAND:
 1328 #   if 1
 1329                 ret = 0;
 1330                 break;
 1331 #   endif /* 1 */
 1332 
 1333             default:
 1334                 break;
 1335         }
 1336     }
 1337     return ret;
 1338 }
 1339 #endif /* NNTP_ABLE */
 1340 
 1341 
 1342 /*
 1343  * Open a connection to the NNTP server. Authenticate if necessary or
 1344  * desired, and test if the server supports XOVER.
 1345  * Returns: 0   success
 1346  *        > 0   NNTP error response code
 1347  *        < 0   -errno from system call or similar error
 1348  */
 1349 int
 1350 nntp_open(
 1351     void)
 1352 {
 1353 #ifdef NNTP_ABLE
 1354     char *linep;
 1355     char line[NNTP_STRLEN]= { '\0' };
 1356     int ret;
 1357     t_bool sec = FALSE;
 1358     /* It appears that is_reconnect guards code that should be run only once */
 1359     static t_bool is_reconnect = FALSE;
 1360 
 1361     if (!read_news_via_nntp)
 1362         return 0;
 1363 
 1364 #   ifdef DEBUG
 1365     if ((debug & DEBUG_NNTP) && verbose > 1)
 1366         debug_print_file("NNTP", "nntp_open() BEGIN");
 1367 #   endif /* DEBUG */
 1368 
 1369     if (nntp_server == NULL) {
 1370         error_message(2, _(txt_cannot_get_nntp_server_name));
 1371         error_message(2, _(txt_server_name_in_file_env_var), NNTP_SERVER_FILE);
 1372         return -EHOSTUNREACH;
 1373     }
 1374 
 1375     if (!batch_mode || verbose) {
 1376         if (nntp_tcp_port != IPPORT_NNTP)
 1377             wait_message(0, _(txt_connecting_port), nntp_server, nntp_tcp_port);
 1378         else
 1379             wait_message(0, _(txt_connecting), nntp_server);
 1380     }
 1381 
 1382 #   ifdef DEBUG
 1383     if ((debug & DEBUG_NNTP) && verbose > 1)
 1384         debug_print_file("NNTP", "nntp_open() %s:%d", nntp_server, nntp_tcp_port);
 1385 #   endif /* DEBUG */
 1386 
 1387     ret = server_init(nntp_server, NNTP_TCP_NAME, nntp_tcp_port, line, sizeof(line));
 1388     DEBUG_IO((stderr, "server_init returns %d,%s\n", ret, line));
 1389 
 1390     if ((!batch_mode || verbose) && ret >= 0)
 1391         my_fputc('\n', stdout);
 1392 
 1393 #   ifdef DEBUG
 1394     if ((debug & DEBUG_NNTP) && verbose > 1)
 1395         debug_print_file("NNTP", "nntp_open() %s", line);
 1396 #   endif /* DEBUG */
 1397 
 1398     switch (ret) {
 1399         /*
 1400          * ret < 0 : some error from system call
 1401          * ret > 0 : NNTP response code
 1402          *
 1403          * According to the ietf-nntp mailinglist:
 1404          *   200 you may (try to) do anything
 1405          *   201 you may not POST
 1406          *   All unrecognised 200 series codes should be assumed as success.
 1407          *   All unrecognised 300 series codes should be assumed as notice to continue.
 1408          *   All unrecognised 400 series codes should be assumed as temporary error.
 1409          *   All unrecognised 500 series codes should be assumed as error.
 1410          */
 1411 
 1412         case OK_CANPOST:
 1413             can_post = TRUE && !force_no_post;
 1414             break;
 1415 
 1416         case OK_NOPOST:
 1417             can_post = FALSE;
 1418             break;
 1419 
 1420         default:
 1421             if (ret >= 200 && ret <= 299) {
 1422                 can_post = TRUE && !force_no_post;
 1423                 break;
 1424             }
 1425             if (ret < 0)
 1426                 error_message(2, _(txt_failed_to_connect_to_server), nntp_server);
 1427             else
 1428                 error_message(2, "%s", line);
 1429 
 1430             return ret;
 1431     }
 1432     if (!is_reconnect && *line) {
 1433         /* remove leading whitespace and save server's initial response */
 1434         linep = line;
 1435         while (isspace((int) *linep))
 1436             linep++;
 1437 
 1438         STRCPY(bug_nntpserver1, linep);
 1439     }
 1440 
 1441     /*
 1442      * Find out which NNTP extensions are available
 1443      * - Typhoon v2.1.1.363 closes the connection after an unknown command
 1444      *   (i.e. CAPABILITIES) but as we are not allowed to cache CAPABILITIES
 1445      *   we reissue the command on reconnect. To prevent a loop we catch this
 1446      *   case.
 1447      *
 1448      * TODO: The authentication method required may be mentioned in the list
 1449      *       of extensions. (For details about authentication methods, see
 1450      *       RFC 4643).
 1451      */
 1452     if (nntp_caps.type != BROKEN)
 1453         check_extensions(0);
 1454 
 1455     /*
 1456      * If the user wants us to authenticate on connection startup, do it now.
 1457      * Some news servers return "201 no posting" first, but after successful
 1458      * authentication you get a "200 posting allowed". To find out if we are
 1459      * allowed to post after authentication issue a "MODE READER" again and
 1460      * interpret the response code.
 1461      */
 1462 
 1463     if (nntp_caps.type == CAPABILITIES && !nntp_caps.reader) {
 1464         if (nntp_caps.mode_reader) {
 1465             char buf[NNTP_STRLEN];
 1466 
 1467 #   ifdef DEBUG
 1468             if ((debug & DEBUG_NNTP) && verbose > 1)
 1469                 debug_print_file("NNTP", "nntp_open(MODE READER)");
 1470 #   endif /* DEBUG */
 1471             put_server("MODE READER");
 1472             switch (get_only_respcode(buf, sizeof(buf))) {
 1473                 /* just honor critical errors */
 1474                 case ERR_GOODBYE:
 1475                 case ERR_ACCESS:
 1476                     error_message(2, "%s", buf);
 1477                     return -1;
 1478 
 1479                 default:
 1480                     break;
 1481             }
 1482             check_extensions(0);
 1483         }
 1484     }
 1485 
 1486     if (force_auth_on_conn_open) {
 1487 #   ifdef DEBUG
 1488         if ((debug & DEBUG_NNTP) && verbose > 1)
 1489             debug_print_file("NNTP", "nntp_open(authenticate(force_auth_on_conn_open))");
 1490 #   endif /* DEBUG */
 1491 
 1492         if (!authenticate(nntp_server, userid, FALSE))  /* 3rd parameter is FALSE as we need to get prompted for username password here */
 1493             return -1;
 1494     }
 1495 
 1496     if ((nntp_caps.type == CAPABILITIES && nntp_caps.mode_reader) || nntp_caps.type != CAPABILITIES) {
 1497         if ((ret = mode_reader(&sec))) {
 1498             if (nntp_caps.type == CAPABILITIES)
 1499                 can_post = nntp_caps.post && !force_no_post;
 1500 
 1501             return ret;
 1502         }
 1503         if (nntp_caps.type == CAPABILITIES)
 1504             check_extensions(0);
 1505     }
 1506 
 1507     if (nntp_caps.type == CAPABILITIES) {
 1508         if (!nntp_caps.reader) {
 1509 #   ifdef DEBUG
 1510             if ((debug & DEBUG_NNTP) && verbose > 1)
 1511                 debug_print_file("NNTP", "CAPABILITIES did not announce READER");
 1512 #   endif /* DEBUG */
 1513             error_message(2, _("CAPABILITIES did not announce READER")); /* TODO: -> lang.c */
 1514             return -1; /* give up */
 1515         }
 1516         can_post = nntp_caps.post && !force_no_post;
 1517     }
 1518 
 1519     if (!is_reconnect && *line) {
 1520 #   if 0
 1521     /*
 1522      * gives wrong results if RFC 3977 server requests auth after
 1523      * CAPABILITIES is parsed (with no posting allowed) and after auth
 1524      * posting is allowed. as we will inform the user later on when he
 1525      * actually tries to post it should do no harm to skip this message
 1526      */
 1527         /* Inform user if he cannot post */
 1528         if (!can_post && !batch_mode)
 1529             wait_message(0, "%s\n", _(txt_cannot_post));
 1530 #   endif /* 0 */
 1531 
 1532         /* Remove leading white space and save server's second response */
 1533         linep = line;
 1534         while (isspace((int) *linep))
 1535             linep++;
 1536 
 1537         STRCPY(bug_nntpserver2, linep);
 1538 
 1539         /*
 1540          * Show user last server response line, do some nice formatting if
 1541          * response is longer than a screen wide.
 1542          *
 1543          * TODO: This only breaks the line once, but the response could be
 1544          * longer than two lines ...
 1545          */
 1546         if (!batch_mode || verbose) {
 1547             char *chr1, *chr2;
 1548             int j;
 1549 
 1550             j = atoi(get_val("COLUMNS", "80"));
 1551             chr1 = my_strdup((sec ? bug_nntpserver2 : bug_nntpserver1));
 1552 
 1553             if (j > MIN_COLUMNS_ON_TERMINAL && ((int) strlen(chr1)) >= j) {
 1554                 chr2 = chr1 + strlen(chr1) - 1;
 1555                 while (chr2 - chr1 >= j)
 1556                     chr2--;
 1557                 while (chr2 > chr1 && *chr2 != ' ')
 1558                     chr2--;
 1559                 if (chr2 != chr1)
 1560                     *chr2 = '\n';
 1561             }
 1562 
 1563             wait_message(0, "%s\n", chr1);
 1564             free(chr1);
 1565         }
 1566     }
 1567 
 1568     /*
 1569      * If CAPABILITIES failed, check if NNTP supports XOVER or OVER command
 1570      * We have to check that we _don't_ get an ERR_COMMAND
 1571      */
 1572     if (nntp_caps.type != CAPABILITIES) {
 1573         int i, j = 0;
 1574 
 1575         for (i = 0; i < 2 && j >= 0; i++) {
 1576             j = new_nntp_command(&xover_cmds[i], ERR_NCING, line, sizeof(line));
 1577             switch (j) {
 1578                 case ERR_COMMAND:
 1579                     break;
 1580 
 1581                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1582                     nntp_caps.over_cmd = &xover_cmds[i];
 1583 #   ifdef DEBUG
 1584                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1585                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xover_cmds[i]);
 1586 #   endif /* DEBUG */
 1587                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1588                         ;
 1589                     j = -1;
 1590                     break;
 1591 
 1592                 default:
 1593                     nntp_caps.over_cmd = &xover_cmds[i];
 1594                     j = -1;
 1595                     break;
 1596             }
 1597         }
 1598         for (i = 0, j = 0; i < 2 && j >= 0; i++) {
 1599             j = new_nntp_command(&xhdr_cmds[i], ERR_CMDSYN, line, sizeof(line));
 1600             switch (j) {
 1601                 case ERR_COMMAND:
 1602                     break;
 1603 
 1604                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1605                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1606 #   ifdef DEBUG
 1607                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1608                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xhdr_cmds[i]);
 1609 #   endif /* DEBUG */
 1610                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1611                         ;
 1612                     j = -1;
 1613                     break;
 1614 
 1615                 default:    /* usually ERR_CMDSYN (args missing), Typhoon/Twister sends ERR_NCING */
 1616                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1617                     j = -1;
 1618                     break;
 1619             }
 1620         }
 1621         /* no XPAT probing here, we do when it's needed */
 1622         nntp_caps.xpat = TRUE;
 1623 #   if 0
 1624         switch (new_nntp_command("XPAT Newsgroups <0> *", ERR_NOART, line, sizeof(line))) {
 1625             case ERR_NOART:
 1626                 nntp_caps.xpat = TRUE;
 1627                 break;
 1628 
 1629             default:
 1630                 break;
 1631         }
 1632 #   endif /* 0 */
 1633     } else {
 1634         if (!nntp_caps.over_cmd) {
 1635             /*
 1636              * CAPABILITIES didn't mention OVER or XOVER, try XOVER
 1637              */
 1638             switch (new_nntp_command(xover_cmds, ERR_NCING, line, sizeof(line))) {
 1639                 case ERR_COMMAND:
 1640                     break;
 1641 
 1642                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1643                     nntp_caps.over_cmd = xover_cmds;
 1644 #   ifdef DEBUG
 1645                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1646                         debug_print_file("NNTP", "nntp_open() %s skipping data", xover_cmds);
 1647 #   endif /* DEBUG */
 1648                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1649                         ;
 1650                     break;
 1651 
 1652                 default:
 1653                     nntp_caps.over_cmd = xover_cmds;
 1654                     break;
 1655             }
 1656         }
 1657         if (!nntp_caps.hdr_cmd) {
 1658             /*
 1659              * CAPABILITIES didn't mention HDR or XHDR, try XHDR
 1660              */
 1661             switch (new_nntp_command(xhdr_cmds, ERR_NCING, line, sizeof(line))) {
 1662                 case ERR_COMMAND:
 1663                     break;
 1664 
 1665                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1666                     nntp_caps.hdr_cmd = xhdr_cmds;
 1667 #   ifdef DEBUG
 1668                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1669                         debug_print_file("NNTP", "nntp_open() %s skipping data", xhdr_cmds);
 1670 #   endif /* DEBUG */
 1671                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1672                         ;
 1673                     break;
 1674 
 1675                 default:    /* ERR_NCING or ERR_CMDSYN */
 1676                     nntp_caps.hdr_cmd = xhdr_cmds;
 1677                     break;
 1678             }
 1679         }
 1680     }
 1681 
 1682     if (!nntp_caps.over_cmd) {
 1683         if (!is_reconnect && !batch_mode) {
 1684             wait_message(2, _(txt_no_xover_support));
 1685 
 1686             if (tinrc.cache_overview_files)
 1687                 wait_message(2, _(txt_caching_on));
 1688             else
 1689                 wait_message(2, _(txt_caching_off));
 1690         }
 1691     }
 1692 #   if 0
 1693     else {
 1694         /*
 1695          * TODO: issue warning if old index files found?
 1696          *        in index_newsdir?
 1697          */
 1698     }
 1699 #   endif /* 0 */
 1700 
 1701     if (!is_reconnect && !batch_mode && show_description && check_for_new_newsgroups) {
 1702         /*
 1703          * TODO:
 1704          * - add a tinrc var to turn LIST MOTD on/off?
 1705          *   (currently done automatically for -d, -q and -Q)
 1706          */
 1707         if (nntp_caps.list_motd)
 1708             list_motd();
 1709     }
 1710 
 1711     is_reconnect = TRUE;
 1712 
 1713 #endif /* NNTP_ABLE */
 1714 
 1715     DEBUG_IO((stderr, "nntp_open okay\n"));
 1716     return 0;
 1717 }
 1718 
 1719 
 1720 /*
 1721  * 'Public' function to shutdown the NNTP connection
 1722  */
 1723 void
 1724 nntp_close(
 1725     t_bool send_no_quit)
 1726 {
 1727 #ifdef NNTP_ABLE
 1728     if (read_news_via_nntp && !read_saved_news) {
 1729 #   ifdef DEBUG
 1730         if ((debug & DEBUG_NNTP) && verbose > 1)
 1731             debug_print_file("NNTP", "nntp_close(%s) END", bool_unparse(send_no_quit));
 1732 #   endif /* DEBUG */
 1733         close_server(send_no_quit);
 1734     }
 1735 #endif /* NNTP_ABLE */
 1736 }
 1737 
 1738 
 1739 #ifdef NNTP_ABLE
 1740 /*
 1741  * Get a response code from the server.
 1742  * Returns:
 1743  *  +ve NNTP return code
 1744  *  -1  on an error or user abort. We don't differentiate.
 1745  * If 'message' is not NULL, then any trailing text after the response
 1746  * code is copied into it.
 1747  * Does not perform authentication if required; use get_respcode()
 1748  * instead.
 1749  */
 1750 int
 1751 get_only_respcode(
 1752     char *message,
 1753     size_t mlen)
 1754 {
 1755     int respcode;
 1756     char *end, *ptr;
 1757 
 1758     ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1759 
 1760     if (tin_errno || ptr == NULL) {
 1761 #   ifdef DEBUG
 1762         if ((debug & DEBUG_NNTP) && verbose > 1)
 1763             debug_print_file("NNTP", "Error: tin_error<>0 or ptr==NULL in get_only_respcode()");
 1764 #   endif /* DEBUG */
 1765         return -1;
 1766     }
 1767 
 1768 #   ifdef DEBUG
 1769     if (debug & DEBUG_NNTP)
 1770         debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1771 #   endif /* DEBUG */
 1772     respcode = (int) strtol(ptr, &end, 10);
 1773     if (end == ptr) /* no leading numbers in response */
 1774         respcode = -1;
 1775     DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1776 
 1777     /*
 1778      * we also reconnect on ERR_FAULT if last_put was ARTICLE or LIST or POST
 1779      * as inn (2.2.3) sends ERR_FAULT on timeout
 1780      *
 1781      * what about other LIST cmds? (ACTIVE|COUNTS|OVERVIEW.FMT|...)
 1782      */
 1783     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")) {
 1784         /*
 1785          * Maybe server timed out.
 1786          * If so, retrying will force a reconnect.
 1787          */
 1788 #   ifdef DEBUG
 1789         if ((debug & DEBUG_NNTP) && verbose > 1)
 1790             debug_print_file("NNTP", "get_only_respcode() timeout");
 1791 #   endif /* DEBUG */
 1792         put_server(last_put);
 1793         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1794 
 1795         if (tin_errno || ptr == NULL) {
 1796 #   ifdef DEBUG
 1797             if ((debug & DEBUG_NNTP) && verbose > 1)
 1798                 debug_print_file("NNTP", "Error: tin_errno<>0 or ptr==NULL in get_only_respcode(retry)");
 1799 #   endif /* DEBUG */
 1800             return -1;
 1801         }
 1802 
 1803 #   ifdef DEBUG
 1804         if (debug & DEBUG_NNTP)
 1805             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1806 #   endif /* DEBUG */
 1807         respcode = (int) strtol(ptr, &end, 10);
 1808         if (end == ptr) /* no leading numbers in response */
 1809             respcode = -1;
 1810         DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1811     }
 1812     if (message != NULL && mlen > 1 && *end != '\0')        /* Pass out the rest of the text */
 1813         my_strncpy(message, ++end, mlen - 1);
 1814 
 1815     return respcode;
 1816 }
 1817 
 1818 
 1819 /*
 1820  * Get a response code from the server.
 1821  * Returns:
 1822  *  +ve NNTP return code
 1823  *  -1  on an error
 1824  * If 'message' is not NULL, then any trailing text after the response
 1825  *  code is copied into it.
 1826  * Performs authentication if required and repeats the last command if
 1827  * necessary after a timeout.
 1828  *
 1829  * TODO: make this handle 401 and 483 (RFC 3977) return codes.
 1830  *       as 401 requires the examination of the returned text besides the
 1831  *       return value, we have to "fix" all nntp_command(..., NULL, 0) and
 1832  *       get_only_respcode(NULL, 0) calls to do this properly.
 1833  */
 1834 int
 1835 get_respcode(
 1836     char *message,
 1837     size_t mlen)
 1838 {
 1839     int respcode;
 1840     char savebuf[NNTP_STRLEN];
 1841     char *ptr, *end;
 1842 
 1843     respcode = get_only_respcode(message, mlen);
 1844     if ((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) {
 1845         /*
 1846          * Server requires authentication.
 1847          */
 1848 #   ifdef DEBUG
 1849         if ((debug & DEBUG_NNTP) && verbose > 1)
 1850             debug_print_file("NNTP", "get_respcode() authentication");
 1851 #   endif /* DEBUG */
 1852         STRCPY(savebuf, last_put);
 1853 
 1854         if (!authenticate(nntp_server, userid, FALSE))
 1855             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
 1856 
 1857         if (nntp_caps.type == CAPABILITIES)
 1858             can_post = nntp_caps.post && !force_no_post;
 1859         else {
 1860             put_server("MODE READER");
 1861             if (get_only_respcode(message, mlen) == OK_CANPOST)
 1862                 can_post = TRUE && !force_no_post;
 1863         }
 1864         if (curr_group != NULL) {
 1865             DEBUG_IO((stderr, _("Rejoin current group\n")));
 1866             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
 1867             put_server(last_put);
 1868             if (s_gets(last_put, NNTP_STRLEN, nntp_rd_fp) == NULL)
 1869                 *last_put = '\0';
 1870 #   ifdef DEBUG
 1871             if (debug & DEBUG_NNTP)
 1872                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
 1873 #   endif /* DEBUG */
 1874             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
 1875         }
 1876         STRCPY(last_put, savebuf);
 1877 
 1878         put_server(last_put);
 1879         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1880 
 1881         if (tin_errno) {
 1882 #   ifdef DEBUG
 1883             if ((debug & DEBUG_NNTP) && verbose > 1)
 1884                 debug_print_file("NNTP", "Error: tin_errno <> 0");
 1885 #   endif /* DEBUG */
 1886             return -1;
 1887         }
 1888 
 1889 #   ifdef DEBUG
 1890         if (debug & DEBUG_NNTP)
 1891             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1892 #   endif /* DEBUG */
 1893         if (ptr == NULL)
 1894             return -1;
 1895 
 1896         respcode = (int) strtol(ptr, &end, 10);
 1897         if (end == ptr) /* no leading numbers in response */
 1898             return -1;
 1899 
 1900         if (message != NULL && mlen > 1)                /* Pass out the rest of the text */
 1901             strncpy(message, end, mlen - 1);
 1902     }
 1903     return respcode;
 1904 }
 1905 
 1906 
 1907 /*
 1908  * Do an NNTP command. Send command to server, and read the reply.
 1909  * If the reply code matches success, then return an open file stream
 1910  * Return NULL if we did not see the response we wanted.
 1911  * If message is not NULL, then the trailing text of the reply string is
 1912  * copied into it for the caller to process.
 1913  */
 1914 FILE *
 1915 nntp_command(
 1916     const char *command,
 1917     int success,
 1918     char *message,
 1919     size_t mlen)
 1920 {
 1921 DEBUG_IO((stderr, "nntp_command(%s)\n", command));
 1922 #   ifdef DEBUG
 1923     if ((debug & DEBUG_NNTP) && verbose > 1)
 1924         debug_print_file("NNTP", "nntp_command(%s)", command);
 1925 #   endif /* DEBUG */
 1926     put_server(command);
 1927 
 1928     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 1929         if (get_respcode(message, mlen) != success) {
 1930 #   ifdef DEBUG
 1931             if ((debug & DEBUG_NNTP) && verbose > 1)
 1932                 debug_print_file("NNTP", "nntp_command(%s) NOT_OK", command);
 1933 #   endif /* DEBUG */
 1934             /* error_message(2, "%s", message); */
 1935             return (FILE *) 0;
 1936         }
 1937     }
 1938 #   ifdef DEBUG
 1939     if ((debug & DEBUG_NNTP) && verbose > 1)
 1940         debug_print_file("NNTP", "nntp_command(%s) OK", command);
 1941 #   endif /* DEBUG */
 1942     return FAKE_NNTP_FP;
 1943 }
 1944 
 1945 
 1946 /*
 1947  * same as above, but with a slightly more useful return code.
 1948  * TODO: use it instead of nntp_command in the rest of the code
 1949  *       (wherever it is more useful).
 1950  */
 1951 int
 1952 new_nntp_command(
 1953     const char *command,
 1954     int success,
 1955     char *message,
 1956     size_t mlen)
 1957 {
 1958     int respcode = 0;
 1959 
 1960 DEBUG_IO((stderr, "new_nntp_command(%s)\n", command));
 1961 #   ifdef DEBUG
 1962     if ((debug & DEBUG_NNTP) && verbose > 1)
 1963         debug_print_file("NNTP", "new_nntp_command(%s)", command);
 1964 #   endif /* DEBUG */
 1965     put_server(command);
 1966 
 1967     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 1968         if ((respcode = get_respcode(message, mlen)) != success) {
 1969 #   ifdef DEBUG
 1970             if ((debug & DEBUG_NNTP) && verbose > 1)
 1971                 debug_print_file("NNTP", "new_nntp_command(%s) NOT_OK - Expected: %d, got: %d", command, success, respcode);
 1972 #   endif /* DEBUG */
 1973             return respcode;
 1974         }
 1975     }
 1976 #   ifdef DEBUG
 1977     if ((debug & DEBUG_NNTP) && verbose > 1)
 1978         debug_print_file("NNTP", "new_nntp_command(%s) OK", command);
 1979 #   endif /* DEBUG */
 1980     return respcode;
 1981 }
 1982 
 1983 
 1984 static void
 1985 list_motd(
 1986     void)
 1987 {
 1988     char *ptr;
 1989     char *p;
 1990     char buf[NNTP_STRLEN];
 1991     int i;
 1992     size_t len;
 1993     unsigned int l = 0;
 1994 
 1995     buf[0] = '\0';
 1996     i = new_nntp_command("LIST MOTD", OK_MOTD, buf, sizeof(buf));
 1997 
 1998     switch (i) {
 1999         case OK_MOTD:
 2000 #   ifdef HAVE_COLOR
 2001             fcol(tinrc.col_message);
 2002 #   endif /* HAVE_COLOR */
 2003             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 2004 #   ifdef DEBUG
 2005                 if (debug & DEBUG_NNTP)
 2006                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 2007 #   endif /* DEBUG */
 2008                 /*
 2009                  * RFC 6048 2.5.2 "The information MUST be in UTF-8"
 2010                  *
 2011                  * TODO: - store a hash value of the entire motd in the server-rc
 2012                  *         and only if it differs from the old value display the
 2013                  *         motd?
 2014                  *       - use some sort of pager?
 2015                  *       - -> lang.c
 2016                  */
 2017                 p = my_strdup(ptr);
 2018                 len = strlen(p);
 2019                 process_charsets(&p, &len, "UTF-8", tinrc.mm_local_charset, FALSE);
 2020                 my_printf("%s%s\n", _("MOTD: "), p);
 2021                 free(p);
 2022                 l++;
 2023             }
 2024 #   ifdef HAVE_COLOR
 2025             fcol(tinrc.col_normal);
 2026 #   endif /* HAVE_COLOR */
 2027             if (l) {
 2028                 my_flush();
 2029                 sleep((l >> 1) | 0x01);
 2030             }
 2031             break;
 2032 
 2033         default:    /* common response codes are 500, 501, 503 */
 2034             break;
 2035     }
 2036 }
 2037 #endif /* NNTP_ABLE */