"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/nntplib.c" (3 Dec 2018, 55068 Bytes) of package /linux/misc/tin-2.4.3.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.2_vs_2.4.3.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : nntplib.c
    4  *  Author    : S. Barber & I. Lea
    5  *  Created   : 1991-01-12
    6  *  Updated   : 2018-12-03
    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(void);
   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         return -errno;
  246     }
  247 
  248     if ((sockt_wr = s_dup(sockt_rd)) < 0) {
  249         perror("server_init: dup()");
  250         return -errno;
  251     }
  252 
  253 #   ifdef TLI /* Transport Level Interface */
  254     if (t_sync(sockt_rd) < 0) { /* Sync up new fd with TLI */
  255         t_error("server_init: t_sync()");
  256         s_fclose(nntp_rd_fp);
  257         nntp_rd_fp = NULL;
  258         return -EPROTO;
  259     }
  260 #   else
  261     if ((nntp_wr_fp = (TCP *) s_fdopen(sockt_wr, "w")) == NULL) {
  262         perror("server_init: fdopen() #2");
  263         s_fclose(nntp_rd_fp);
  264         nntp_rd_fp = NULL;
  265         return -errno;
  266     }
  267 #   endif /* TLI */
  268 
  269     last_put[0] = '\0';     /* no retries in get_respcode */
  270     /*
  271      * Now get the server's signon message
  272      */
  273     return (get_respcode(text, mlen));
  274 }
  275 #endif /* NNTP_ABLE */
  276 
  277 
  278 /*
  279  * get_tcp_socket -- get us a socket connected to the specified server.
  280  *
  281  *  Parameters: "machine" is the machine the server is running on.
  282  *          "service" is the service to connect to on the server.
  283  *          "port" is the port to connect to on the server.
  284  *
  285  *  Returns:    Socket connected to the server if all if ok
  286  *          EPROTO      for internal socket errors
  287  *          EHOSTUNREACH    if specified NNTP port/server can't be located
  288  *          errno       any valid errno value on connection
  289  *
  290  *  Side effects:   Connects to server.
  291  *
  292  *  Errors:     Returned & printed via perror.
  293  */
  294 #if defined(NNTP_ABLE) && !defined(INET6)
  295 static int
  296 get_tcp_socket(
  297     char *machine,      /* remote host */
  298     char *service,      /* nttp/smtp etc. */
  299     unsigned short port)    /* tcp port number */
  300 {
  301     int s = -1;
  302     int save_errno = 0;
  303     struct sockaddr_in sock_in;
  304 #   ifdef TLI /* Transport Level Interface */
  305     char device[20];
  306     char *env_device;
  307     extern int t_errno;
  308     extern struct hostent *gethostbyname();
  309     struct hostent *hp;
  310     struct t_call *callptr;
  311 
  312     /*
  313      * Create a TCP transport endpoint.
  314      */
  315     if ((env_device = getenv("DEV_TCP")) != NULL) /* SCO uses DEV_TCP, most other OS use /dev/tcp */
  316         STRCPY(device, env_device);
  317     else
  318         strcpy(device, "/dev/tcp");
  319 
  320     if ((s = t_open(device, O_RDWR, (struct t_info *) 0)) < 0) {
  321         t_error(txt_error_topen);
  322         return -EPROTO;
  323     }
  324     if (t_bind(s, (struct t_bind *) 0, (struct t_bind *) 0) < 0) {
  325         t_error("t_bind");
  326         t_close(s);
  327         return -EPROTO;
  328     }
  329     memset((char *) &sock_in, '\0', sizeof(sock_in));
  330     sock_in.sin_family = AF_INET;
  331     sock_in.sin_port = htons(port);
  332 
  333     if (!isdigit((unsigned char) *machine)
  334 #       ifdef HAVE_INET_ATON
  335         || !inet_aton(machine, &sock_in)
  336 #       else
  337 #           ifdef HAVE_INET_ADDR
  338         || (long) (sock_in.sin_addr.s_addr = inet_addr(machine)) == INADDR_NONE
  339 #           endif /* HAVE_INET_ADDR */
  340 #       endif /* HAVE_INET_ATON */
  341     ) {
  342         if ((hp = gethostbyname(machine)) == NULL) {
  343             my_fprintf(stderr, _(txt_gethostbyname), "gethostbyname() ", machine);
  344             t_close(s);
  345             return -EHOSTUNREACH;
  346         }
  347         memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
  348     }
  349 
  350     /*
  351      * Allocate a t_call structure and initialize it.
  352      * Let t_alloc() initialize the addr structure of the t_call structure.
  353      */
  354     if ((callptr = (struct t_call *) t_alloc(s, T_CALL, T_ADDR)) == NULL) {
  355         t_error("t_alloc");
  356         t_close(s);
  357         return -EPROTO;
  358     }
  359 
  360     callptr->addr.maxlen = sizeof(sock_in);
  361     callptr->addr.len = sizeof(sock_in);
  362     callptr->addr.buf = (char *) &sock_in;
  363     callptr->opt.len = 0;           /* no options */
  364     callptr->udata.len = 0;         /* no user data with connect */
  365 
  366     /*
  367      * Connect to the server.
  368      */
  369     if (t_connect(s, callptr, (struct t_call *) 0) < 0) {
  370         save_errno = t_errno;
  371         if (save_errno == TLOOK)
  372             fprintf(stderr, "%s", _(txt_error_server_unavailable));
  373         else
  374             t_error("t_connect");
  375         t_free((char *) callptr, T_CALL);
  376         t_close(s);
  377         return -save_errno;
  378     }
  379 
  380     /*
  381      * Now replace the timod module with the tirdwr module so that
  382      * standard read() and write() system calls can be used on the
  383      * descriptor.
  384      */
  385 
  386     t_free((char *) callptr, T_CALL);
  387 
  388     if (ioctl(s, I_POP, NULL) < 0) {
  389         perror("I_POP(timod)");
  390         t_close(s);
  391         return -EPROTO;
  392     }
  393 
  394     if (ioctl(s, I_PUSH, "tirdwr") < 0) {
  395         perror("I_PUSH(tirdwr)");
  396         t_close(s);
  397         return -EPROTO;
  398     }
  399 
  400 #   else
  401 #       ifndef EXCELAN
  402     struct servent *sp;
  403     struct hostent *hp;
  404 #           ifdef h_addr
  405     int x = 0;
  406     char **cp;
  407 #           endif /* h_addr */
  408     static char *alist[2] = {0, 0};
  409     static struct hostent def;
  410     static struct in_addr defaddr;
  411     static char namebuf[256];
  412 
  413 #           ifdef HAVE_GETSERVBYNAME
  414     if ((sp = (struct servent *) getservbyname(service, "tcp")) == NULL) {
  415         my_fprintf(stderr, _(txt_error_unknown_service), service);
  416         return -EHOSTUNREACH;
  417     }
  418 #           else
  419     sp = my_malloc(sizeof(struct servent));
  420     sp->s_port = htons(IPPORT_NNTP);
  421 #           endif /* HAVE_GETSERVBYNAME */
  422 
  423     /* If not a raw ip address, try nameserver */
  424     if (!isdigit((unsigned char) *machine)
  425 #           ifdef HAVE_INET_ATON
  426         || !inet_aton(machine, &defaddr)
  427 #           else
  428 #               ifdef HAVE_INET_ADDR
  429         || (long) (defaddr.s_addr = (long) inet_addr(machine)) == -1
  430 #               endif /* HAVE_INET_ADDR */
  431 #           endif /* HAVE_INET_ATON */
  432         )
  433     {
  434         hp = gethostbyname(machine);
  435     } else {
  436         /* Raw ip address, fake */
  437         STRCPY(namebuf, machine);
  438         def.h_name = (char *) namebuf;
  439         def.h_addr_list = alist;
  440         def.h_addr_list[0] = (char *) &defaddr;
  441         def.h_length = sizeof(struct in_addr);
  442         def.h_addrtype = AF_INET;
  443         def.h_aliases = 0;
  444         hp = &def;
  445     }
  446 
  447     if (hp == NULL) {
  448         my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
  449         return -EHOSTUNREACH;
  450     }
  451 
  452     memset((char *) &sock_in, '\0', sizeof(sock_in));
  453     sock_in.sin_family = hp->h_addrtype;
  454     sock_in.sin_port = htons(port);
  455 /*  sock_in.sin_port = sp->s_port; */
  456 #       else
  457     memset((char *) &sock_in, '\0', sizeof(sock_in));
  458     sock_in.sin_family = AF_INET;
  459 #       endif /* !EXCELAN */
  460 
  461     /*
  462      * The following is kinda gross. The name server under 4.3
  463      * returns a list of addresses, each of which should be tried
  464      * in turn if the previous one fails. However, 4.2 hostent
  465      * structure doesn't have this list of addresses.
  466      * Under 4.3, h_addr is a #define to h_addr_list[0].
  467      * We use this to figure out whether to include the NS specific
  468      * code...
  469      */
  470 
  471 #       ifdef h_addr
  472     /*
  473      * Get a socket and initiate connection -- use multiple addresses
  474      */
  475     for (cp = hp->h_addr_list; cp && *cp; cp++) {
  476 #           if defined(__hpux) && defined(SVR4)
  477         unsigned long socksize, socksizelen;
  478 #           endif /* __hpux && SVR4 */
  479 
  480         if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
  481             perror("socket");
  482             return -errno;
  483         }
  484 
  485         memcpy((char *) &sock_in.sin_addr, *cp, hp->h_length);
  486 
  487 #           ifdef HAVE_INET_NTOA
  488         if (x < 0)
  489             my_fprintf(stderr, _(txt_trying), (char *) inet_ntoa(sock_in.sin_addr));
  490 #           endif /* HAVE_INET_NTOA */
  491 
  492 #           if defined(__hpux) && defined(SVR4) /* recommended by raj@cup.hp.com */
  493 #               define HPSOCKSIZE 0x8000
  494         getsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
  495         if (socksize < HPSOCKSIZE) {
  496             socksize = HPSOCKSIZE;
  497             setsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
  498         }
  499         socksize = 0;
  500         socksizelen = sizeof(socksize);
  501         getsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
  502         if (socksize < HPSOCKSIZE) {
  503             socksize = HPSOCKSIZE;
  504             setsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
  505         }
  506 #           endif /* __hpux && SVR4 */
  507 
  508         if ((x = connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in))) == 0)
  509             break;
  510 
  511         save_errno = errno;                                 /* Keep for later */
  512 #           ifdef HAVE_INET_NTOA
  513         my_fprintf(stderr, _(txt_connection_to), (char *) inet_ntoa(sock_in.sin_addr));
  514         perror("");
  515 #           endif /* HAVE_INET_NTOA */
  516         (void) s_close(s);
  517     }
  518 
  519     if (x < 0) {
  520         my_fprintf(stderr, "%s", _(txt_giving_up));
  521         return -save_errno;                 /* Return the last errno we got */
  522     }
  523 #       else
  524 
  525 #           ifdef EXCELAN
  526     if ((s = socket(SOCK_STREAM, (struct sockproto *) NULL, &sock_in, SO_KEEPALIVE)) < 0) {
  527         perror("socket");
  528         return -errno;
  529     }
  530 
  531     /* set up addr for the connect */
  532     memset((char *) &sock_in, '\0', sizeof(sock_in));
  533     sock_in.sin_family = AF_INET;
  534     sock_in.sin_port = htons(IPPORT_NNTP);
  535 
  536     if ((sock_in.sin_addr.s_addr = rhost(&machine)) == -1) {
  537         my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
  538         return -1;
  539     }
  540 
  541     /* And connect */
  542     if (connect(s, (struct sockaddr *) &sock_in) < 0) {
  543         save_errno = errno;
  544         perror("connect");
  545         (void) s_close(s);
  546         return -save_errno;
  547     }
  548 
  549 #           else
  550     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
  551         perror("socket");
  552         return -errno;
  553     }
  554 
  555     /* And then connect */
  556 
  557     memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
  558 
  559     if (connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in)) < 0) {
  560         save_errno = errno;
  561         perror("connect");
  562         (void) s_close(s);
  563         return -save_errno;
  564     }
  565 
  566 #           endif /* !EXCELAN */
  567 #       endif /* !h_addr */
  568 #   endif /* !TLI */
  569     return s;
  570 }
  571 #endif /* NNTP_ABLE && !INET6 */
  572 
  573 
  574 #if defined(NNTP_ABLE) && defined(INET6)
  575 /*
  576  * get_tcp6_socket -- get us a socket connected to the server.
  577  *
  578  * Parameters:   "machine" is the machine the server is running on.
  579  *                "port" is the portnumber to connect to.
  580  *
  581  * Returns:      Socket connected to the news server if
  582  *               all is ok, else -1 on error.
  583  *
  584  * Side effects: Connects to server via IPv4 or IPv6.
  585  *
  586  * Errors:       Printed via my_fprintf.
  587  */
  588 static int
  589 get_tcp6_socket(
  590     char *machine,
  591     unsigned short port)
  592 {
  593     char mymachine[MAXHOSTNAMELEN + 1];
  594     char myport[12];
  595     int s = -1, err, ec = 0, es = 0;
  596     struct addrinfo hints, *res, *res0;
  597 
  598     snprintf(mymachine, sizeof(mymachine), "%s", machine);
  599     snprintf(myport, sizeof(myport), "%u", port);
  600 
  601 /* just in case */
  602 #   ifdef AF_UNSPEC
  603 #       define ADDRFAM  AF_UNSPEC
  604 #   else
  605 #       ifdef PF_UNSPEC
  606 #           define ADDRFAM  PF_UNSPEC
  607 #       else
  608 #           define ADDRFAM  AF_INET
  609 #       endif /* PF_UNSPEC */
  610 #   endif /* AF_UNSPEC */
  611     memset(&hints, 0, sizeof(hints));
  612 /*  hints.ai_flags = AI_CANONNAME; */
  613     hints.ai_family = (force_ipv4 ? AF_INET : (force_ipv6 ? AF_INET6 : ADDRFAM));
  614     hints.ai_socktype = SOCK_STREAM;
  615     res0 = (struct addrinfo *) 0;
  616     if ((err = getaddrinfo(mymachine, myport, &hints, &res0))) {
  617         my_fprintf(stderr, "\ngetaddrinfo: %s\n", gai_strerror(err));
  618         return -1;
  619     }
  620     err = -1;
  621     for (res = res0; res; res = res->ai_next) {
  622         if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
  623             es = errno;
  624             continue;
  625         }
  626         if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
  627             ec = errno;
  628             s_close(s);
  629         }
  630         else {
  631             es = ec = err = 0;
  632             break;
  633         }
  634     }
  635     if (res0 != NULL)
  636         freeaddrinfo(res0);
  637     if (err < 0) {
  638         my_fprintf(stderr, "%s", _(txt_error_socket_or_connect_problem));
  639         if (es)
  640             my_fprintf(stderr, "\tsocket(2): %s\n", strerror(es));
  641         if (ec)
  642             my_fprintf(stderr, "\tconnect(2): %s\n", strerror(ec));
  643         sleep(3);
  644         return -1;
  645     }
  646     return s;
  647 }
  648 #endif /* NNTP_ABLE && INET6 */
  649 
  650 
  651 #ifdef DECNET
  652 /*
  653  * get_dnet_socket -- get us a socket connected to the server.
  654  *
  655  *  Parameters: "machine" is the machine the server is running on.
  656  *          "service" is the name of the service to connect to.
  657  *
  658  *  Returns:    Socket connected to the news server if
  659  *          all is ok, else -1 on error.
  660  *
  661  *  Side effects:   Connects to server.
  662  *
  663  *  Errors:     Printed via nerror.
  664  */
  665 static int
  666 get_dnet_socket(
  667     char *machine,
  668     char *service)
  669 {
  670 #   ifdef NNTP_ABLE
  671     int s, area, node;
  672     struct sockaddr_dn sdn;
  673     struct nodeent *getnodebyname(), *np;
  674 
  675     memset((char *) &sdn, '\0', sizeof(sdn));
  676 
  677     switch (s = sscanf(machine, "%d%*[.]%d", &area, &node)) {
  678         case 1:
  679             node = area;
  680             area = 0;
  681             /* FALLTHROUGH */
  682         case 2:
  683             node += area * 1024;
  684             sdn.sdn_add.a_len = 2;
  685             sdn.sdn_family = AF_DECnet;
  686             sdn.sdn_add.a_addr[0] = node % 256;
  687             sdn.sdn_add.a_addr[1] = node / 256;
  688             break;
  689 
  690         default:
  691             if ((np = getnodebyname(machine)) == NULL) {
  692                 my_fprintf(stderr, _(txt_gethostbyname), "", machine);
  693                 return -1;
  694             } else {
  695                 memcpy((char *) sdn.sdn_add.a_addr, np->n_addr, np->n_length);
  696                 sdn.sdn_add.a_len = np->n_length;
  697                 sdn.sdn_family = np->n_addrtype;
  698             }
  699             break;
  700     }
  701     sdn.sdn_objnum = 0;
  702     sdn.sdn_flags = 0;
  703     sdn.sdn_objnamel = strlen("NNTP");
  704     memcpy(&sdn.sdn_objname[0], "NNTP", sdn.sdn_objnamel);
  705 
  706     if ((s = socket(AF_DECnet, SOCK_STREAM, 0)) < 0) {
  707         nerror("socket");
  708         return -1;
  709     }
  710 
  711     /* And then connect */
  712 
  713     if (connect(s, (struct sockaddr *) &sdn, sizeof(sdn)) < 0) {
  714         nerror("connect");
  715         s_close(s);
  716         return -1;
  717     }
  718 
  719     return s;
  720 #   else
  721     return -1;
  722 #   endif /* NNTP_ABLE */
  723 }
  724 #endif /* DECNET */
  725 
  726 
  727 /*----------------------------------------------------------------------
  728  * Ideally the code after this point should be the only interface to the
  729  * NNTP internals...
  730  */
  731 
  732 /*
  733  * u_put_server -- send data to the server. Do not flush output.
  734  */
  735 #ifdef NNTP_ABLE
  736 void
  737 u_put_server(
  738     const char *string)
  739 {
  740     s_puts(string, nntp_wr_fp);
  741 #   ifdef DEBUG
  742     if (debug & DEBUG_NNTP) {
  743         if (strcmp(string, "\r\n"))
  744             debug_print_file("NNTP", ">>>%s%s", logtime(), string);
  745     }
  746 #   endif /* DEBUG */
  747 }
  748 
  749 
  750 /*
  751  * Send 'string' to the NNTP server, terminating it with CR and LF, as per
  752  * ARPA standard.
  753  *
  754  * Returns: Nothing.
  755  *
  756  *  Side effects: Talks to the server.
  757  *                Closes connection if things are not right.
  758  *
  759  * Note: This routine flushes the buffer each time it is called. For large
  760  *       transmissions (i.e., posting news) don't use it. Instead, do the
  761  *       fprintf's yourself, and then a final fflush.
  762  *       Only cache commands, don't cache data transmissions.
  763  */
  764 void
  765 put_server(
  766     const char *string)
  767 {
  768     if (*string && strlen(string)) {
  769         DEBUG_IO((stderr, "put_server(%s)\n", string));
  770         s_puts(string, nntp_wr_fp);
  771         s_puts("\r\n", nntp_wr_fp);
  772 #   ifdef DEBUG
  773         if (debug & DEBUG_NNTP)
  774             debug_print_file("NNTP", ">>>%s%s", logtime(), string);
  775 #   endif /* DEBUG */
  776         /*
  777          * remember the last command we wrote to be able to resend it after a
  778          * reconnect. reconnection is handled by get_server()
  779          *
  780          * don't cache "LIST [ACTIVE|COUNTS|NEWSGROUPS] something" as we
  781          * would need to resend all of them but we remember just the last
  782          * one. we cache "LIST cmd." instead, this will slow down things, but
  783          * that's ok on reconnect.
  784          */
  785         if (strcmp(last_put, string))
  786             STRCPY(last_put, string);
  787         if (!strncmp(string, "LIST ACTIVE ", 12))
  788             last_put[11] = '\0'; /* "LIST ACTIVE" */
  789         else if (!strncmp(string, "LIST COUNTS ", 12))
  790             last_put[11] = '\0'; /* "LIST COUNTS" */
  791         else if (!strncmp(string, "LIST NEWSGROUPS ", 16))
  792             last_put[15] = '\0'; /* "LIST NEWSGROUPS" */
  793     }
  794     (void) s_flush(nntp_wr_fp);
  795 }
  796 
  797 
  798 /*
  799  * Reconnect to server after a timeout, reissue last command to
  800  * get us back into the pre-timeout state
  801  */
  802 static int
  803 reconnect(
  804     int retry)
  805 {
  806     char buf[NNTP_STRLEN];
  807     int save_signal_context = signal_context;
  808 
  809     /*
  810      * Tear down current connection
  811      * Close the NNTP connection with prejudice
  812      */
  813     if (nntp_wr_fp)
  814         s_fclose(nntp_wr_fp);
  815     if (nntp_rd_fp)
  816         s_fclose(nntp_rd_fp);
  817     nntp_rd_fp = nntp_wr_fp = NULL;
  818 
  819     if (!tinrc.auto_reconnect)
  820         ring_bell();
  821 
  822     DEBUG_IO((stderr, _("\nServer timed out, trying reconnect # %d\n"), retry));
  823 
  824     /*
  825      * set signal_context temporary to cReconnect to avoid trouble when receiving
  826      * SIGWINCH while being in prompt_yn()
  827      */
  828     signal_context = cReconnect;
  829 
  830     /*
  831      * Exit tin if there are no more tries or if the user says no to reconnect.
  832      * The exit code stops tin from trying to disconnect again - the connection
  833      * is already dead
  834      */
  835     if (retry > NNTP_TRY_RECONNECT || (!tinrc.auto_reconnect && prompt_yn(_(txt_reconnect_to_news_server), TRUE) != 1)) {
  836         if (!strcmp("POST", last_put)) {
  837             unlink(backup_article_name(article_name));
  838             rename_file(article_name, dead_article);
  839             if (tinrc.keep_dead_articles)
  840                 append_file(dead_article, dead_articles);
  841         }
  842         if (retry > NNTP_TRY_RECONNECT) {
  843 #   ifdef DEBUG
  844             /* TODO: -> lang.c */
  845             if ((debug & DEBUG_NNTP) && verbose > 1)
  846                 debug_print_file("NNTP", "reconnect(%d) limit %d reached, giving up.", retry, NNTP_TRY_RECONNECT);
  847 #   endif /* DEBUG */
  848         }
  849         tin_done(NNTP_ERROR_EXIT, _("NNTP connection error. Exiting..."));      /* user said no to reconnect or no more retries */
  850     }
  851 
  852     /* reset signal_context */
  853     signal_context = save_signal_context;
  854 
  855     clear_message();
  856     strcpy(buf, last_put);          /* Keep copy here, it will be clobbered a lot otherwise */
  857 
  858     if (!nntp_open()) {
  859         /* Re-establish our current group and resend last command */
  860         if (curr_group != NULL) {
  861             DEBUG_IO((stderr, _("Rejoin current group\n")));
  862             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
  863             put_server(last_put);
  864             s_gets(last_put, NNTP_STRLEN, nntp_rd_fp);
  865 #   ifdef DEBUG
  866             if (debug & DEBUG_NNTP)
  867                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
  868 #   endif /* DEBUG */
  869             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
  870         }
  871         DEBUG_IO((stderr, _("Resend last command (%s)\n"), buf));
  872         put_server(buf);
  873         did_reconnect = TRUE;
  874         retry = NNTP_TRY_RECONNECT;
  875     }
  876 
  877     return retry;
  878 }
  879 
  880 
  881 /*
  882  * Read a line of data from the NNTP socket. If something gives, do reconnect
  883  *
  884  *  Parameters: "string" has the buffer space for the line received.
  885  *          "size" is maximum size of the buffer to read.
  886  *
  887  *  Returns:    NULL on end of stream, or a line of data.
  888  *              Basically, we try to act like fgets() on an NNTP stream.
  889  *
  890  *  Side effects:   Talks to server, changes contents of "string".
  891  *          Reopens connection when necessary and requested.
  892  *          Exits via tin_done() if fatal error occurs.
  893  */
  894 char *
  895 get_server(
  896     char *string,
  897     int size)
  898 {
  899     static int retry_cnt = 0;
  900 
  901     reconnected_in_last_get_server = FALSE;
  902     errno = 0;
  903 
  904     /*
  905      * NULL socket reads indicates socket has closed. Try a few times more
  906      *
  907      * Leave the s_gets() after a timeout for these cases:
  908      *   -some servers do not close the connection but simply do not send any
  909      *    response data
  910      *   -the network connection went down
  911      */
  912 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  913     alarm(tinrc.nntp_read_timeout_secs);
  914 #   endif /* HAVE_ALARM && SIGALRM */
  915     while (nntp_rd_fp == NULL || s_gets(string, size, nntp_rd_fp) == NULL) {
  916         if (errno == EINTR) {
  917             errno = 0;
  918 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  919             alarm(tinrc.nntp_read_timeout_secs);        /* Restart the timer */
  920 #   endif /* HAVE_ALARM && SIGALRM */
  921             continue;
  922         }
  923 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  924         alarm(0);
  925 #   endif /* HAVE_ALARM && SIGALRM */
  926         if (quitting)                       /* Don't bother to reconnect */
  927             tin_done(NNTP_ERROR_EXIT, NULL);        /* And don't try to disconnect again! */
  928 
  929 #   ifdef DEBUG
  930         if (errno != 0 && errno != EINTR)   /* Will only confuse end users */
  931             perror_message("get_server()");
  932 #   endif /* DEBUG */
  933 
  934         /*
  935          * Reconnect only if command was not "QUIT" anyway (in which case a
  936          * reconnection would be useless because the connection will be
  937          * closed immediately). Also prevents tin from asking to reconnect
  938          * when user is quitting tin if tinrc.auto_reconnect is false.
  939          */
  940         if (strcmp(last_put, "QUIT")) {
  941             /*
  942              * Typhoon v2.1.1.363 colses the connection right after an unknown
  943              * command, (i.e. CAPABILITIES) so we avoid the reissue it on a
  944              * reconnect if it was the last command.
  945              */
  946             if (!strcmp(last_put, "CAPABILITIES")) {
  947                 strcpy(last_put, "MODE READER");
  948                 nntp_caps.type = BROKEN;
  949             }
  950             retry_cnt = reconnect(++retry_cnt);     /* Will abort when out of tries */
  951             reconnected_in_last_get_server = TRUE;
  952         } else {
  953             /*
  954              * Use standard NNTP closing message and response code if user is
  955              * quitting tin and leave loop.
  956              */
  957             strncpy(string, _(txt_nntp_ok_goodbye), size - 3);
  958             strcat(string, "\r\n");     /* tin_fgets() needs CRLF */
  959             break;
  960         }
  961     }
  962 #   if defined(HAVE_ALARM) && defined(SIGALRM)
  963     alarm(0);
  964 #   endif /* HAVE_ALARM && SIGALRM */
  965     retry_cnt = 0;
  966     return string;
  967 }
  968 
  969 
  970 /*
  971  * Send "QUIT" command and close the connection to the server
  972  *
  973  * Side effects: Closes the connection to the server.
  974  *                You can't use "put_server" or "get_server" after this
  975  *                routine is called.
  976  *
  977  * TODO: remember servers response string and if it contains anything else
  978  *       than just "." (i.e. transfer statistics) present it to the user?
  979  *
  980  */
  981 static void
  982 close_server(
  983     void)
  984 {
  985     if (nntp_wr_fp == NULL || nntp_rd_fp == NULL)
  986         return;
  987 
  988     if (!batch_mode || verbose) {
  989         char *msg;
  990 
  991         msg = strunc(_(txt_disconnecting), cCOLS - 1);
  992         my_fputs(msg, stdout);
  993         my_fputc('\n', stdout);
  994         free(msg);
  995     }
  996     nntp_command("QUIT", OK_GOODBYE, NULL, 0);
  997     quitting = TRUE;                                        /* Don't reconnect just for this */
  998 
  999     (void) s_fclose(nntp_wr_fp);
 1000     (void) s_fclose(nntp_rd_fp);
 1001     s_end();
 1002     nntp_wr_fp = nntp_rd_fp = NULL;
 1003 }
 1004 
 1005 
 1006 /*
 1007  * Try and use CAPABILITIES here. Get this list before issuing other NNTP
 1008  * commands because the correct methods may be mentioned in the list of
 1009  * extensions.
 1010  *
 1011  * Sets up: t_capabilities nntp_caps
 1012  */
 1013 int
 1014 check_extensions(
 1015     int rvl)
 1016 {
 1017     char *d;
 1018     char *ptr;
 1019     char buf[NNTP_STRLEN];
 1020     int i;
 1021     int ret = 0;
 1022 
 1023     buf[0] = '\0';
 1024 
 1025     /* rvl > 0 = manually send "CAPABILITIES" to avoid endless AUTH loop */
 1026     i = rvl ? rvl : new_nntp_command("CAPABILITIES", INF_CAPABILITIES, buf, sizeof(buf));
 1027     switch (i) {
 1028         case INF_CAPABILITIES:
 1029             /* clear capabilities */
 1030             nntp_caps.type = CAPABILITIES;
 1031             nntp_caps.version = 0;
 1032             nntp_caps.mode_reader = FALSE;
 1033             nntp_caps.reader = FALSE;
 1034             nntp_caps.post = FALSE;
 1035             nntp_caps.list_active = FALSE;
 1036             nntp_caps.list_active_times = FALSE;
 1037             nntp_caps.list_distrib_pats = FALSE;
 1038             nntp_caps.list_headers = FALSE;
 1039             FreeAndNull(nntp_caps.headers_range);
 1040             FreeAndNull(nntp_caps.headers_id);
 1041             nntp_caps.list_newsgroups = FALSE;
 1042             nntp_caps.list_overview_fmt = FALSE;
 1043             nntp_caps.list_motd = FALSE;
 1044             nntp_caps.list_subscriptions = FALSE;
 1045             nntp_caps.list_distributions = FALSE;
 1046             nntp_caps.list_moderators = FALSE;
 1047             nntp_caps.list_counts = FALSE;
 1048             nntp_caps.xpat = TRUE; /* toggles to false if fails, INN > 2.7.0 announces it */
 1049             nntp_caps.hdr = FALSE;
 1050             nntp_caps.hdr_cmd = NULL;
 1051             nntp_caps.over = FALSE;
 1052             nntp_caps.over_msgid = FALSE;
 1053             nntp_caps.over_cmd = NULL;
 1054             nntp_caps.newnews = FALSE;
 1055             FreeAndNull(nntp_caps.implementation);
 1056             nntp_caps.starttls = FALSE;
 1057             nntp_caps.authinfo_user = FALSE;
 1058             nntp_caps.authinfo_sasl = FALSE;
 1059             nntp_caps.authinfo_state = FALSE;
 1060             nntp_caps.sasl = SASL_NONE;
 1061             nntp_caps.compress = FALSE;
 1062             nntp_caps.compress_algorithm = COMPRESS_NONE;
 1063 #if 0
 1064             nntp_caps.streaming = FALSE;
 1065             nntp_caps.ihave = FALSE;
 1066 #endif /* 0 */
 1067 #ifndef BROKEN_LISTGROUP
 1068             nntp_caps.broken_listgroup = FALSE;
 1069 #else
 1070             nntp_caps.broken_listgroup = TRUE;
 1071 #endif /* !BROKEN_LISTGROUP */
 1072 
 1073             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1074 #       ifdef DEBUG
 1075                 if (debug & DEBUG_NNTP)
 1076                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1077 #       endif /* DEBUG */
 1078                 /* look for version number(s) */
 1079                 if (!nntp_caps.version && nntp_caps.type == CAPABILITIES) {
 1080                     if (!strncasecmp(ptr, "VERSION", 7)) {
 1081                         d = ptr + 7;
 1082                         d = strpbrk(d, " \t");
 1083                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1084                             d++;
 1085                             nntp_caps.version = (unsigned int) atoi(d);
 1086                             d = strpbrk(d, " \t");
 1087                         }
 1088                     }
 1089                 }
 1090                 /* we currently only support CAPABILITIES VERSION 2 */
 1091                 if (nntp_caps.version == 2) {
 1092                     /*
 1093                      * check for LIST variants
 1094                      */
 1095                     if (!strncasecmp(ptr, "LIST", 4)) {
 1096                         d = ptr + 4;
 1097                         d = strpbrk(d, " \t");
 1098                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1099                             d++;
 1100                             if (!strncasecmp(d, "ACTIVE.TIMES", 12))
 1101                                 nntp_caps.list_active_times = TRUE;
 1102                             else if (!strncasecmp(d, "ACTIVE", 6))
 1103                                 nntp_caps.list_active = TRUE;
 1104                             else if (!strncasecmp(d, "DISTRIB.PATS", 12))
 1105                                 nntp_caps.list_distrib_pats = TRUE;
 1106                             else if (!strncasecmp(d, "DISTRIBUTIONS", 13)) /* RFC 6048 */
 1107                                 nntp_caps.list_distributions = TRUE;
 1108                             else if (!strncasecmp(d, "HEADERS", 7))
 1109                                 nntp_caps.list_headers = TRUE; /* HDR requires LIST HEADERS, but not vice versa */
 1110                             else if (!strncasecmp(d, "NEWSGROUPS", 10))
 1111                                 nntp_caps.list_newsgroups = TRUE;
 1112                             else if (!strncasecmp(d, "OVERVIEW.FMT", 12)) /* OVER requires OVERVIEW.FMT, but not vice versa */
 1113                                 nntp_caps.list_overview_fmt = TRUE;
 1114                             else if (!strncasecmp(d, "MOTD", 4)) /* RFC 6048 */
 1115                                 nntp_caps.list_motd = TRUE;
 1116                             else if (!strncasecmp(d, "SUBSCRIPTIONS", 13)) /* RFC 6048 */
 1117                                 nntp_caps.list_subscriptions = TRUE;
 1118                             else if (!strncasecmp(d, "MODERATORS", 10)) /* RFC 6048 */
 1119                                 nntp_caps.list_moderators = TRUE;
 1120                             else if (!strncasecmp(d, "COUNTS", 6)) /* RFC 6048 */
 1121                                 nntp_caps.list_counts = TRUE;
 1122                             d = strpbrk(d, " \t");
 1123                         }
 1124                     } else if (!strncasecmp(ptr, "IMPLEMENTATION", 14)) {
 1125                         FreeIfNeeded(nntp_caps.implementation);
 1126                         nntp_caps.implementation = my_strdup(ptr + 14);
 1127                         str_trim(nntp_caps.implementation);
 1128                     } else if (!strcasecmp(ptr, "MODE-READER")) {
 1129                         if (!nntp_caps.reader)
 1130                             nntp_caps.mode_reader = TRUE;
 1131                     } else if (!strcasecmp(ptr, "READER")) { /* if we saw READER, "LIST ACTIVE" and "LIST NEWSGROUPS" must be implemented */
 1132                         nntp_caps.reader = TRUE;
 1133                         nntp_caps.mode_reader = FALSE;
 1134                         nntp_caps.list_newsgroups = TRUE;
 1135                         nntp_caps.list_active = TRUE;
 1136                     } else if (!strcasecmp(ptr, "POST"))
 1137                         nntp_caps.post = TRUE;
 1138                     else if (!strcasecmp(ptr, "NEWNEWS"))
 1139                         nntp_caps.newnews = TRUE;
 1140                     else if (!strcasecmp(ptr, "XPAT")) /* extension, RFC 2980 */
 1141                         nntp_caps.xpat = TRUE;
 1142                     else if (!strcasecmp(ptr, "STARTTLS"))
 1143                         nntp_caps.starttls = TRUE;
 1144                     /*
 1145                      * NOTE: if we saw OVER, LIST OVERVIEW.FMT _must_ be implemented
 1146                      */
 1147                     else if (!strncasecmp(ptr, &xover_cmds[1], strlen(&xover_cmds[1]))) {
 1148                         nntp_caps.list_overview_fmt = nntp_caps.over = TRUE;
 1149                         nntp_caps.over_cmd = &xover_cmds[1];
 1150                         d = ptr + strlen(&xover_cmds[1]);
 1151                         d = strpbrk(d, " \t");
 1152                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1153                             d++;
 1154                             if (!strcasecmp(d, "MSGID"))
 1155                                 nntp_caps.over_msgid = TRUE;
 1156                             d = strpbrk(d, " \t");
 1157                         }
 1158                     }
 1159                     /*
 1160                      * NOTE: if we saw HDR, LIST HEADERS _must_ be implemented
 1161                      */
 1162                     else if (!strncasecmp(ptr, &xhdr_cmds[1], strlen(&xhdr_cmds[1]))) {
 1163                         nntp_caps.hdr_cmd = &xhdr_cmds[1];
 1164                         nntp_caps.list_headers = nntp_caps.hdr = TRUE;
 1165                         nntp_caps.headers_range = my_strdup("");
 1166                         nntp_caps.headers_id = my_strdup("");
 1167                     } else if (!strncasecmp(ptr, "AUTHINFO", 8)) {
 1168                         d = ptr + 8;
 1169                         d = strpbrk(d, " \t");
 1170                         if (d == NULL) /* AUTHINFO without args */
 1171                             nntp_caps.authinfo_state = TRUE;
 1172                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1173                             d++;
 1174                             if (!strncasecmp(d, "USER", 4))
 1175                                 nntp_caps.authinfo_user = TRUE;
 1176                             if (!strncasecmp(d, "SASL", 4))
 1177                                 nntp_caps.authinfo_sasl = TRUE;
 1178                             d = strpbrk(d, " \t");
 1179                         }
 1180                     } else if (!strncasecmp(ptr, "SASL", 4)) {
 1181                         d = ptr + 4;
 1182                         d = strpbrk(d, " \t");
 1183                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1184                             d++;
 1185                             if (!strncasecmp(d, "CRAM-MD5", 8)) { /* RFC 2195 */
 1186                                 nntp_caps.authinfo_sasl = TRUE;
 1187                                 nntp_caps.sasl |= SASL_CRAM_MD5;
 1188                             }
 1189                             if (!strncasecmp(d, "DIGEST-MD5", 10)) { /* RFC 2831 */
 1190                                 nntp_caps.authinfo_sasl = TRUE;
 1191                                 nntp_caps.sasl |= SASL_DIGEST_MD5;
 1192                             }
 1193                             if (!strncasecmp(d, "PLAIN", 5)) { /* RFC 4616 */
 1194                                 nntp_caps.authinfo_sasl = TRUE;
 1195                                 nntp_caps.sasl |= SASL_PLAIN;
 1196                             }
 1197                             if (!strncasecmp(d, "GSSAPI", 6)) { /* RFC 4752 */
 1198                                 nntp_caps.authinfo_sasl = TRUE;
 1199                                 nntp_caps.sasl |= SASL_GSSAPI;
 1200                             }
 1201                             if (!strncasecmp(d, "EXTERNAL", 8)) { /* RFC 4422 */
 1202                                 nntp_caps.authinfo_sasl = TRUE;
 1203                                 nntp_caps.sasl |= SASL_EXTERNAL;
 1204                             }
 1205                             /* inn also can do these */
 1206                             if (!strncasecmp(d, "OTP", 3)) { /* RFC 2444 */
 1207                                 nntp_caps.authinfo_sasl = TRUE;
 1208                                 nntp_caps.sasl |= SASL_OTP;
 1209                             }
 1210                             if (!strncasecmp(d, "NTLM", 4)) { /* Microsoft */
 1211                                 nntp_caps.authinfo_sasl = TRUE;
 1212                                 nntp_caps.sasl |= SASL_NTLM;
 1213                             }
 1214                             if (!strncasecmp(d, "LOGIN", 5)) { /* Microsoft */
 1215                                 nntp_caps.authinfo_sasl = TRUE;
 1216                                 nntp_caps.sasl |= SASL_LOGIN;
 1217                             }
 1218                         }
 1219                     } else if (!strncasecmp(ptr, "COMPRESS", 8)) { /* RFC 8054 */
 1220                         d = ptr + 8;
 1221                         d = strpbrk(d, " \t");
 1222                         while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
 1223                             d++;
 1224                             if (!strncasecmp(d, "DEFLATE", 7)) {
 1225                                 nntp_caps.compress = TRUE;
 1226                                 nntp_caps.compress_algorithm |= COMPRESS_DEFLATE;
 1227                             }
 1228                         }
 1229                     }
 1230 #       if 0 /* we don't need these */
 1231                     else if (!strcasecmp(ptr, "IHAVE"))
 1232                         nntp_caps.ihave = TRUE;
 1233                     else if (!strcasecmp(ptr, "STREAMING"))
 1234                         nntp_caps.streaming = TRUE;
 1235 #       endif /* 0 */
 1236                 /* XZVER, XZHDR, ... */
 1237                 } else
 1238                     nntp_caps.type = NONE;
 1239             }
 1240             break;
 1241 
 1242         /*
 1243          * XanaNewz 2 Server Version 2.0.0.3 doesn't know CAPABILITIES
 1244          * but responds with 400 _without_ closing the connection. If
 1245          * you use tin on a XanaNewz 2 Server comment out the following
 1246          * case.
 1247          */
 1248 #       if 1
 1249         case ERR_GOODBYE:
 1250             ret = i;
 1251             error_message(2, "%s", buf);
 1252             break;
 1253 #       endif /* 1 */
 1254 
 1255         default:
 1256             break;
 1257     }
 1258 
 1259     return ret;
 1260 }
 1261 
 1262 
 1263 /*
 1264  * Switch INN into NNRP mode with 'mode reader'
 1265  */
 1266 static int
 1267 mode_reader(
 1268     t_bool *sec)
 1269 {
 1270     int ret = 0;
 1271 
 1272     if (!nntp_caps.reader) {
 1273         char line[NNTP_STRLEN];
 1274 #   ifdef DEBUG
 1275         if ((debug & DEBUG_NNTP) && verbose > 1)
 1276             debug_print_file("NNTP", "mode_reader(MODE READER)");
 1277 #   endif /* DEBUG */
 1278         DEBUG_IO((stderr, "nntp_command(MODE READER)\n"));
 1279         put_server("MODE READER");
 1280 
 1281         /*
 1282          * According to RFC 3977 (5.3), MODE READER may only return the
 1283          * following response codes:
 1284          *
 1285          *   200 (OK_CANPOST)     Hello, you can post
 1286          *   201 (OK_NOPOST)      Hello, you can't post
 1287          *   502 (ERR_ACCESS)     Service unavailable
 1288          *
 1289          * However, there are servers out there (e.g. Delegate 9.8.x) that do
 1290          * not implement this command and therefore return ERR_COMMAND (500).
 1291          * Unfortunately there are some new servers out there (i.e. INN 2.4.0
 1292          * (20020220 prerelease) which do return ERR_COMMAND if they are feed
 1293          * only servers.
 1294          */
 1295 
 1296         switch ((ret = get_respcode(line, sizeof(line)))) {
 1297             case OK_CANPOST:
 1298                 can_post = TRUE && !force_no_post;
 1299                 *sec = TRUE;
 1300                 ret = 0;
 1301                 break;
 1302 
 1303             case OK_NOPOST:
 1304                 can_post = FALSE;
 1305                 *sec = TRUE;
 1306                 ret = 0;
 1307                 break;
 1308 
 1309             case ERR_GOODBYE:
 1310             case ERR_ACCESS:
 1311                 error_message(2, "%s", line);
 1312                 return ret;
 1313 
 1314             case ERR_COMMAND:
 1315 #   if 1
 1316                 ret = 0;
 1317                 break;
 1318 #   endif /* 1 */
 1319 
 1320             default:
 1321                 break;
 1322         }
 1323     }
 1324     return ret;
 1325 }
 1326 #endif /* NNTP_ABLE */
 1327 
 1328 
 1329 /*
 1330  * Open a connection to the NNTP server. Authenticate if necessary or
 1331  * desired, and test if the server supports XOVER.
 1332  * Returns: 0   success
 1333  *        > 0   NNTP error response code
 1334  *        < 0   -errno from system call or similar error
 1335  */
 1336 int
 1337 nntp_open(
 1338     void)
 1339 {
 1340 #ifdef NNTP_ABLE
 1341     char *linep;
 1342     char line[NNTP_STRLEN]= { '\0' };
 1343     int ret;
 1344     t_bool sec = FALSE;
 1345     /* It appears that is_reconnect guards code that should be run only once */
 1346     static t_bool is_reconnect = FALSE;
 1347 
 1348     if (!read_news_via_nntp)
 1349         return 0;
 1350 
 1351 #   ifdef DEBUG
 1352     if ((debug & DEBUG_NNTP) && verbose > 1)
 1353         debug_print_file("NNTP", "nntp_open() BEGIN");
 1354 #   endif /* DEBUG */
 1355 
 1356     if (nntp_server == NULL) {
 1357         error_message(2, _(txt_cannot_get_nntp_server_name));
 1358         error_message(2, _(txt_server_name_in_file_env_var), NNTP_SERVER_FILE);
 1359         return -EHOSTUNREACH;
 1360     }
 1361 
 1362     if (!batch_mode || verbose) {
 1363         if (nntp_tcp_port != IPPORT_NNTP)
 1364             wait_message(0, _(txt_connecting_port), nntp_server, nntp_tcp_port);
 1365         else
 1366             wait_message(0, _(txt_connecting), nntp_server);
 1367     }
 1368 
 1369 #   ifdef DEBUG
 1370     if ((debug & DEBUG_NNTP) && verbose > 1)
 1371         debug_print_file("NNTP", "nntp_open() %s:%d", nntp_server, nntp_tcp_port);
 1372 #   endif /* DEBUG */
 1373 
 1374     ret = server_init(nntp_server, NNTP_TCP_NAME, nntp_tcp_port, line, sizeof(line));
 1375     DEBUG_IO((stderr, "server_init returns %d,%s\n", ret, line));
 1376 
 1377     if ((!batch_mode || verbose) && ret >= 0)
 1378         my_fputc('\n', stdout);
 1379 
 1380 #   ifdef DEBUG
 1381     if ((debug & DEBUG_NNTP) && verbose > 1)
 1382         debug_print_file("NNTP", "nntp_open() %s", line);
 1383 #   endif /* DEBUG */
 1384 
 1385     switch (ret) {
 1386         /*
 1387          * ret < 0 : some error from system call
 1388          * ret > 0 : NNTP response code
 1389          *
 1390          * According to the ietf-nntp mailinglist:
 1391          *   200 you may (try to) do anything
 1392          *   201 you may not POST
 1393          *   All unrecognised 200 series codes should be assumed as success.
 1394          *   All unrecognised 300 series codes should be assumed as notice to continue.
 1395          *   All unrecognised 400 series codes should be assumed as temporary error.
 1396          *   All unrecognised 500 series codes should be assumed as error.
 1397          */
 1398 
 1399         case OK_CANPOST:
 1400             can_post = TRUE && !force_no_post;
 1401             break;
 1402 
 1403         case OK_NOPOST:
 1404             can_post = FALSE;
 1405             break;
 1406 
 1407         default:
 1408             if (ret >= 200 && ret <= 299) {
 1409                 can_post = TRUE && !force_no_post;
 1410                 break;
 1411             }
 1412             if (ret < 0)
 1413                 error_message(2, _(txt_failed_to_connect_to_server), nntp_server);
 1414             else
 1415                 error_message(2, "%s", line);
 1416 
 1417             return ret;
 1418     }
 1419     if (!is_reconnect && *line) {
 1420         /* remove leading whitespace and save server's initial response */
 1421         linep = line;
 1422         while (isspace((int) *linep))
 1423             linep++;
 1424 
 1425         STRCPY(bug_nntpserver1, linep);
 1426     }
 1427 
 1428     /*
 1429      * Find out which NNTP extensions are available
 1430      * - Typhoon v2.1.1.363 closes the connection after an unknown command
 1431      *   (i.e. CAPABILITIES) but as we are not allowed to cache CAPABILITIES
 1432      *   we reissue the command on reconnect. To prevent a loop we catch this
 1433      *   case.
 1434      *
 1435      * TODO: The authentication method required may be mentioned in the list
 1436      *       of extensions. (For details about authentication methods, see
 1437      *       RFC 4643).
 1438      */
 1439     if (nntp_caps.type != BROKEN)
 1440         check_extensions(0);
 1441 
 1442     /*
 1443      * If the user wants us to authenticate on connection startup, do it now.
 1444      * Some news servers return "201 no posting" first, but after successful
 1445      * authentication you get a "200 posting allowed". To find out if we are
 1446      * allowed to post after authentication issue a "MODE READER" again and
 1447      * interpret the response code.
 1448      */
 1449 
 1450     if (nntp_caps.type == CAPABILITIES && !nntp_caps.reader) {
 1451         if (nntp_caps.mode_reader) {
 1452             char buf[NNTP_STRLEN];
 1453 
 1454 #   ifdef DEBUG
 1455             if ((debug & DEBUG_NNTP) && verbose > 1)
 1456                 debug_print_file("NNTP", "nntp_open(MODE READER)");
 1457 #   endif /* DEBUG */
 1458             put_server("MODE READER");
 1459             switch (get_only_respcode(buf, sizeof(buf))) {
 1460                 /* just honor ciritical errors */
 1461                 case ERR_GOODBYE:
 1462                 case ERR_ACCESS:
 1463                     error_message(2, "%s", buf);
 1464                     return -1;
 1465 
 1466                 default:
 1467                     break;
 1468             }
 1469             check_extensions(0);
 1470         }
 1471     }
 1472 
 1473     if (force_auth_on_conn_open) {
 1474 #   ifdef DEBUG
 1475         if ((debug & DEBUG_NNTP) && verbose > 1)
 1476             debug_print_file("NNTP", "nntp_open(authenticate(force_auth_on_conn_open))");
 1477 #   endif /* DEBUG */
 1478 
 1479         if (!authenticate(nntp_server, userid, FALSE))  /* 3rd parameter is FALSE as we need to get prompted for username password here */
 1480             return -1;
 1481     }
 1482 
 1483     if ((nntp_caps.type == CAPABILITIES && nntp_caps.mode_reader) || nntp_caps.type != CAPABILITIES) {
 1484         if ((ret = mode_reader(&sec))) {
 1485             if (nntp_caps.type == CAPABILITIES)
 1486                 can_post = nntp_caps.post && !force_no_post;
 1487 
 1488             return ret;
 1489         }
 1490         if (nntp_caps.type == CAPABILITIES)
 1491             check_extensions(0);
 1492     }
 1493 
 1494     if (nntp_caps.type == CAPABILITIES) {
 1495         if (!nntp_caps.reader) {
 1496 #   ifdef DEBUG
 1497             if ((debug & DEBUG_NNTP) && verbose > 1)
 1498                 debug_print_file("NNTP", "CAPABILITIES did not announce READER");
 1499 #   endif /* DEBUG */
 1500             error_message(2, _("CAPABILITIES did not announce READER")); /* TODO: -> lang.c */
 1501             return -1; /* give up */
 1502         }
 1503         can_post = nntp_caps.post && !force_no_post;
 1504     }
 1505 
 1506     if (!is_reconnect && *line) {
 1507 #   if 0
 1508     /*
 1509      * gives wrong results if RFC 3977 server requestes auth after
 1510      * CAPABILITIES is parsed (with no posting allowed) and after auth
 1511      * posting is allowed. as we will inform the user later on when he
 1512      * actually tries to post it should do no harm to skip this message
 1513      */
 1514         /* Inform user if he cannot post */
 1515         if (!can_post && !batch_mode)
 1516             wait_message(0, "%s\n", _(txt_cannot_post));
 1517 #   endif /* 0 */
 1518 
 1519         /* Remove leading white space and save server's second response */
 1520         linep = line;
 1521         while (isspace((int) *linep))
 1522             linep++;
 1523 
 1524         STRCPY(bug_nntpserver2, linep);
 1525 
 1526         /*
 1527          * Show user last server response line, do some nice formatting if
 1528          * response is longer than a screen wide.
 1529          *
 1530          * TODO: This only breaks the line once, but the response could be
 1531          * longer than two lines ...
 1532          */
 1533         if (!batch_mode || verbose) {
 1534             char *chr1, *chr2;
 1535             int j;
 1536 
 1537             j = atoi(get_val("COLUMNS", "80"));
 1538             chr1 = my_strdup((sec ? bug_nntpserver2 : bug_nntpserver1));
 1539 
 1540             if (((int) strlen(chr1)) >= j) {
 1541                 chr2 = chr1 + strlen(chr1) - 1;
 1542                 while (chr2 - chr1 >= j)
 1543                     chr2--;
 1544                 while (chr2 > chr1 && *chr2 != ' ')
 1545                     chr2--;
 1546                 if (chr2 != chr1)
 1547                     *chr2 = '\n';
 1548             }
 1549 
 1550             wait_message(0, "%s\n", chr1);
 1551             free(chr1);
 1552         }
 1553     }
 1554 
 1555     /*
 1556      * If CAPABILITIES failed, check if NNTP supports XOVER or OVER command
 1557      * We have to check that we _don't_ get an ERR_COMMAND
 1558      */
 1559     if (nntp_caps.type != CAPABILITIES) {
 1560         int i, j = 0;
 1561 
 1562         for (i = 0; i < 2 && j >= 0; i++) {
 1563             j = new_nntp_command(&xover_cmds[i], ERR_NCING, line, sizeof(line));
 1564             switch (j) {
 1565                 case ERR_COMMAND:
 1566                     break;
 1567 
 1568                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1569                     nntp_caps.over_cmd = &xover_cmds[i];
 1570 #   ifdef DEBUG
 1571                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1572                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xover_cmds[i]);
 1573 #   endif /* DEBUG */
 1574                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1575                         ;
 1576                     j = -1;
 1577                     break;
 1578 
 1579                 default:
 1580                     nntp_caps.over_cmd = &xover_cmds[i];
 1581                     j = -1;
 1582                     break;
 1583             }
 1584         }
 1585         for (i = 0, j = 0; i < 2 && j >= 0; i++) {
 1586             j = new_nntp_command(&xhdr_cmds[i], ERR_CMDSYN, line, sizeof(line));
 1587             switch (j) {
 1588                 case ERR_COMMAND:
 1589                     break;
 1590 
 1591                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1592                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1593 #       ifdef DEBUG
 1594                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1595                         debug_print_file("NNTP", "nntp_open() %s skipping data", &xhdr_cmds[i]);
 1596 #       endif /* DEBUG */
 1597                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1598                         ;
 1599                     j = -1;
 1600                     break;
 1601 
 1602                 default:    /* usually ERR_CMDSYN (args missing), Typhoon/Twister sends ERR_NCING */
 1603                     nntp_caps.hdr_cmd = &xhdr_cmds[i];
 1604                     j = -1;
 1605                     break;
 1606             }
 1607         }
 1608         /* no XPAT probing here, we do when it's needed */
 1609         nntp_caps.xpat = TRUE;
 1610 #       if 0
 1611         switch (new_nntp_command("XPAT Newsgroups <0> *", ERR_NOART, line, sizeof(line))) {
 1612             case ERR_NOART:
 1613                 nntp_caps.xpat = TRUE;
 1614                 break;
 1615 
 1616             default:
 1617                 break;
 1618         }
 1619 #       endif /* 0 */
 1620     } else {
 1621         if (!nntp_caps.over_cmd) {
 1622             /*
 1623              * CAPABILITIES didn't mention OVER or XOVER, try XOVER
 1624              */
 1625             switch (new_nntp_command(xover_cmds, ERR_NCING, line, sizeof(line))) {
 1626                 case ERR_COMMAND:
 1627                     break;
 1628 
 1629                 case OK_XOVER:  /* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
 1630                     nntp_caps.over_cmd = xover_cmds;
 1631 #   ifdef DEBUG
 1632                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1633                         debug_print_file("NNTP", "nntp_open() %s skipping data", xover_cmds);
 1634 #   endif /* DEBUG */
 1635                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1636                         ;
 1637                     break;
 1638 
 1639                 default:
 1640                     nntp_caps.over_cmd = xover_cmds;
 1641                     break;
 1642             }
 1643         }
 1644         if (!nntp_caps.hdr_cmd) {
 1645             /*
 1646              * CAPABILITIES didn't mention HDR or XHDR, try XHDR
 1647              */
 1648             switch (new_nntp_command(xhdr_cmds, ERR_NCING, line, sizeof(line))) {
 1649                 case ERR_COMMAND:
 1650                     break;
 1651 
 1652                 case 221:   /* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
 1653                     nntp_caps.hdr_cmd = xhdr_cmds;
 1654 #       ifdef DEBUG
 1655                     if ((debug & DEBUG_NNTP) && verbose > 1)
 1656                         debug_print_file("NNTP", "nntp_open() %s skipping data", xhdr_cmds);
 1657 #       endif /* DEBUG */
 1658                     while (tin_fgets(FAKE_NNTP_FP, FALSE))
 1659                         ;
 1660                     break;
 1661 
 1662                 default:    /* ERR_NCING or ERR_CMDSYN */
 1663                     nntp_caps.hdr_cmd = xhdr_cmds;
 1664                     break;
 1665             }
 1666         }
 1667     }
 1668 
 1669     if (!nntp_caps.over_cmd) {
 1670         if (!is_reconnect && !batch_mode) {
 1671             wait_message(2, _(txt_no_xover_support));
 1672 
 1673             if (tinrc.cache_overview_files)
 1674                 wait_message(2, _(txt_caching_on));
 1675             else
 1676                 wait_message(2, _(txt_caching_off));
 1677         }
 1678     }
 1679 #   if 0
 1680     else {
 1681         /*
 1682          * TODO: issue warning if old index files found?
 1683          *        in index_newsdir?
 1684          */
 1685     }
 1686 #   endif /* 0 */
 1687 
 1688     if (!is_reconnect && !batch_mode && show_description && check_for_new_newsgroups) {
 1689         /*
 1690          * TODO:
 1691          * - add a tinrc var to turn LIST MOTD on/off?
 1692          *   (currently done automatically for -d, -q and -Q)
 1693          */
 1694         if (nntp_caps.list_motd)
 1695             list_motd();
 1696     }
 1697 
 1698     is_reconnect = TRUE;
 1699 
 1700 #endif /* NNTP_ABLE */
 1701 
 1702     DEBUG_IO((stderr, "nntp_open okay\n"));
 1703     return 0;
 1704 }
 1705 
 1706 
 1707 /*
 1708  * 'Public' function to shutdown the NNTP connection
 1709  */
 1710 void
 1711 nntp_close(
 1712     void)
 1713 {
 1714 #ifdef NNTP_ABLE
 1715     if (read_news_via_nntp && !read_saved_news) {
 1716 #   ifdef DEBUG
 1717         if ((debug & DEBUG_NNTP) && verbose > 1)
 1718             debug_print_file("NNTP", "nntp_close() END");
 1719 #   endif /* DEBUG */
 1720         close_server();
 1721     }
 1722 #endif /* NNTP_ABLE */
 1723 }
 1724 
 1725 
 1726 #ifdef NNTP_ABLE
 1727 /*
 1728  * Get a response code from the server.
 1729  * Returns:
 1730  *  +ve NNTP return code
 1731  *  -1  on an error or user abort. We don't differentiate.
 1732  * If 'message' is not NULL, then any trailing text after the response
 1733  * code is copied into it.
 1734  * Does not perform authentication if required; use get_respcode()
 1735  * instead.
 1736  */
 1737 int
 1738 get_only_respcode(
 1739     char *message,
 1740     size_t mlen)
 1741 {
 1742     int respcode;
 1743     char *end, *ptr;
 1744 
 1745     ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1746 
 1747     if (tin_errno || ptr == NULL) {
 1748 #   ifdef DEBUG
 1749         if ((debug & DEBUG_NNTP) && verbose > 1)
 1750             debug_print_file("NNTP", "Error: tin_error<>0 or ptr==NULL in get_only_respcode()");
 1751 #   endif /* DEBUG */
 1752         return -1;
 1753     }
 1754 
 1755 #   ifdef DEBUG
 1756     if (debug & DEBUG_NNTP)
 1757         debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1758 #   endif /* DEBUG */
 1759     respcode = (int) strtol(ptr, &end, 10);
 1760     if (end == ptr) /* no leading numbers in response */
 1761         respcode = -1;
 1762     DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1763 
 1764     /*
 1765      * we also reconnect on ERR_FAULT if last_put was ARTICLE or LIST or POST
 1766      * as inn (2.2.3) sends ERR_FAULT on timeout
 1767      *
 1768      * what about other LIST cmds? (ACTIVE|COUNTS|OVERVIEW.FMT|...)
 1769      */
 1770     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")) {
 1771         /*
 1772          * Maybe server timed out.
 1773          * If so, retrying will force a reconnect.
 1774          */
 1775 #   ifdef DEBUG
 1776         if ((debug & DEBUG_NNTP) && verbose > 1)
 1777             debug_print_file("NNTP", "get_only_respcode() timeout");
 1778 #   endif /* DEBUG */
 1779         put_server(last_put);
 1780         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1781 
 1782         if (tin_errno || ptr == NULL) {
 1783 #   ifdef DEBUG
 1784             if ((debug & DEBUG_NNTP) && verbose > 1)
 1785                 debug_print_file("NNTP", "Error: tin_errno<>0 or ptr==NULL in get_only_respcode(retry)");
 1786 #   endif /* DEBUG */
 1787             return -1;
 1788         }
 1789 
 1790 #   ifdef DEBUG
 1791         if (debug & DEBUG_NNTP)
 1792             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1793 #   endif /* DEBUG */
 1794         respcode = (int) strtol(ptr, &end, 10);
 1795         if (end == ptr) /* no leading numbers in response */
 1796             respcode = -1;
 1797         DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
 1798     }
 1799     if (message != NULL && mlen > 1 && *end != '\0')        /* Pass out the rest of the text */
 1800         my_strncpy(message, ++end, mlen - 1);
 1801 
 1802     return respcode;
 1803 }
 1804 
 1805 
 1806 /*
 1807  * Get a response code from the server.
 1808  * Returns:
 1809  *  +ve NNTP return code
 1810  *  -1  on an error
 1811  * If 'message' is not NULL, then any trailing text after the response
 1812  *  code is copied into it.
 1813  * Performs authentication if required and repeats the last command if
 1814  * necessary after a timeout.
 1815  *
 1816  * TODO: make this handle 401 and 483 (RFC 3977) return codes.
 1817  *       as 401 requires the examination of the returned text besides the
 1818  *       return value, we have to "fix" all nntp_command(..., NULL, 0) and
 1819  *       get_only_respcode(NULL, 0) calls to do this properly.
 1820  */
 1821 int
 1822 get_respcode(
 1823     char *message,
 1824     size_t mlen)
 1825 {
 1826     int respcode;
 1827     char savebuf[NNTP_STRLEN];
 1828     char *ptr, *end;
 1829 
 1830     respcode = get_only_respcode(message, mlen);
 1831     if ((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) {
 1832         /*
 1833          * Server requires authentication.
 1834          */
 1835 #   ifdef DEBUG
 1836         if ((debug & DEBUG_NNTP) && verbose > 1)
 1837             debug_print_file("NNTP", "get_respcode() authentication");
 1838 #   endif /* DEBUG */
 1839         STRCPY(savebuf, last_put);
 1840 
 1841         if (!authenticate(nntp_server, userid, FALSE))
 1842             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
 1843 
 1844         if (nntp_caps.type == CAPABILITIES)
 1845             can_post = nntp_caps.post && !force_no_post;
 1846         else {
 1847             put_server("MODE READER");
 1848             if (get_only_respcode(message, mlen) == OK_CANPOST)
 1849                 can_post = TRUE && !force_no_post;
 1850         }
 1851         if (curr_group != NULL) {
 1852             DEBUG_IO((stderr, _("Rejoin current group\n")));
 1853             snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
 1854             put_server(last_put);
 1855             s_gets(last_put, NNTP_STRLEN, nntp_rd_fp);
 1856 #   ifdef DEBUG
 1857             if (debug & DEBUG_NNTP)
 1858                 debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
 1859 #   endif /* DEBUG */
 1860             DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
 1861         }
 1862         STRCPY(last_put, savebuf);
 1863 
 1864         put_server(last_put);
 1865         ptr = tin_fgets(FAKE_NNTP_FP, FALSE);
 1866 
 1867         if (tin_errno) {
 1868 #   ifdef DEBUG
 1869             if ((debug & DEBUG_NNTP) && verbose > 1)
 1870                 debug_print_file("NNTP", "Error: tin_errno <> 0");
 1871 #   endif /* DEBUG */
 1872             return -1;
 1873         }
 1874 
 1875 #   ifdef DEBUG
 1876         if (debug & DEBUG_NNTP)
 1877             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1878 #   endif /* DEBUG */
 1879         if (ptr == NULL)
 1880             return -1;
 1881 
 1882         respcode = (int) strtol(ptr, &end, 10);
 1883         if (end == ptr) /* no leading numbers in response */
 1884             return -1;
 1885 
 1886         if (message != NULL && mlen > 1)                /* Pass out the rest of the text */
 1887             strncpy(message, end, mlen - 1);
 1888     }
 1889     return respcode;
 1890 }
 1891 
 1892 
 1893 /*
 1894  * Do an NNTP command. Send command to server, and read the reply.
 1895  * If the reply code matches success, then return an open file stream
 1896  * Return NULL if we did not see the response we wanted.
 1897  * If message is not NULL, then the trailing text of the reply string is
 1898  * copied into it for the caller to process.
 1899  */
 1900 FILE *
 1901 nntp_command(
 1902     const char *command,
 1903     int success,
 1904     char *message,
 1905     size_t mlen)
 1906 {
 1907 DEBUG_IO((stderr, "nntp_command(%s)\n", command));
 1908 #   ifdef DEBUG
 1909     if ((debug & DEBUG_NNTP) && verbose > 1)
 1910         debug_print_file("NNTP", "nntp_command(%s)", command);
 1911 #   endif /* DEBUG */
 1912     put_server(command);
 1913 
 1914     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 1915         if (get_respcode(message, mlen) != success) {
 1916 #   ifdef DEBUG
 1917             if ((debug & DEBUG_NNTP) && verbose > 1)
 1918                 debug_print_file("NNTP", "nntp_command(%s) NOT_OK", command);
 1919 #   endif /* DEBUG */
 1920             /* error_message(2, "%s", message); */
 1921             return (FILE *) 0;
 1922         }
 1923     }
 1924 #   ifdef DEBUG
 1925     if ((debug & DEBUG_NNTP) && verbose > 1)
 1926         debug_print_file("NNTP", "nntp_command(%s) OK", command);
 1927 #   endif /* DEBUG */
 1928     return FAKE_NNTP_FP;
 1929 }
 1930 
 1931 
 1932 /*
 1933  * same as above, but with a slightly more useful return code.
 1934  * TODO: use it instead of nntp_command in the rest of the code
 1935  *       (wherever it is more useful).
 1936  */
 1937 int
 1938 new_nntp_command(
 1939     const char *command,
 1940     int success,
 1941     char *message,
 1942     size_t mlen)
 1943 {
 1944     int respcode = 0;
 1945 
 1946 DEBUG_IO((stderr, "new_nntp_command(%s)\n", command));
 1947 #   ifdef DEBUG
 1948     if ((debug & DEBUG_NNTP) && verbose > 1)
 1949         debug_print_file("NNTP", "new_nntp_command(%s)", command);
 1950 #   endif /* DEBUG */
 1951     put_server(command);
 1952 
 1953     if (!bool_equal(dangerous_signal_exit, TRUE)) {
 1954         if ((respcode = get_respcode(message, mlen)) != success) {
 1955 #   ifdef DEBUG
 1956             if ((debug & DEBUG_NNTP) && verbose > 1)
 1957                 debug_print_file("NNTP", "new_nntp_command(%s) NOT_OK - Expected: %d, got: %d", command, success, respcode);
 1958 #   endif /* DEBUG */
 1959             return respcode;
 1960         }
 1961     }
 1962 #   ifdef DEBUG
 1963     if ((debug & DEBUG_NNTP) && verbose > 1)
 1964         debug_print_file("NNTP", "new_nntp_command(%s) OK", command);
 1965 #   endif /* DEBUG */
 1966     return respcode;
 1967 }
 1968 
 1969 
 1970 static void
 1971 list_motd(
 1972     void)
 1973 {
 1974     char *ptr;
 1975     char *p;
 1976     char buf[NNTP_STRLEN];
 1977     int i;
 1978     size_t len;
 1979     unsigned int l = 0;
 1980 
 1981     buf[0] = '\0';
 1982     i = new_nntp_command("LIST MOTD", OK_MOTD, buf, sizeof(buf));
 1983 
 1984     switch (i) {
 1985         case OK_MOTD:
 1986 #   ifdef HAVE_COLOR
 1987             fcol(tinrc.col_message);
 1988 #   endif /* HAVE_COLOR */
 1989             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
 1990 #   ifdef DEBUG
 1991                 if (debug & DEBUG_NNTP)
 1992                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
 1993 #   endif /* DEBUG */
 1994                 /*
 1995                  * RFC 6048 2.5.2 "The information MUST be in UTF-8"
 1996                  *
 1997                  * TODO: - store a hash value of the entire motd in the server-rc
 1998                  *         and only if it differs from the old value display the
 1999                  *         motd?
 2000                  *       - use some sort of pager?
 2001                  *       - -> lang.c
 2002                  */
 2003                 p = my_strdup(ptr);
 2004                 len = strlen(p);
 2005                 process_charsets(&p, &len, "UTF-8", tinrc.mm_local_charset, FALSE);
 2006                 my_printf("%s%s\n", _("MOTD: "), p);
 2007                 free(p);
 2008                 l++;
 2009             }
 2010 #   ifdef HAVE_COLOR
 2011             fcol(tinrc.col_normal);
 2012 #   endif /* HAVE_COLOR */
 2013             if (l) {
 2014                 my_flush();
 2015                 sleep((l >> 1) | 0x01);
 2016             }
 2017             break;
 2018 
 2019         default:    /* common response codes are 500, 501, 503 */
 2020             break;
 2021     }
 2022 }
 2023 #endif /* NNTP_ABLE */