"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/nntplib.c" (24 Dec 2016, 54290 Bytes) of archive /linux/misc/tin-2.4.1.tar.gz:


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.0_vs_2.4.1.

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