"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/nntplib.c" (20 Nov 2019, 55617 Bytes) of package /linux/misc/tin-2.4.4.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.4.3_vs_2.4.4.

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