home *** CD-ROM | disk | FTP | other *** search
/ Unix System Administration Handbook 1997 October / usah_oct97.iso / news / nn.tar / nn-6.5.1 / nntp.c < prev    next >
C/C++ Source or Header  |  1996-08-26  |  31KB  |  1,290 lines

  1. /*
  2.  * nntp module for nn.
  3.  *
  4.  * The original taken from the nntp 1.5 clientlib.c
  5.  * Modified heavily for nn.
  6.  *
  7.  * Rene' Seindal (seindal@diku.dk) Thu Dec  1 18:41:23 1988
  8.  *
  9.  * I have modified Rene's code quite a lot for 6.4 -- I hope he
  10.  * can still recognize a bit here and a byte there; in any case,
  11.  * any mistakes are mine :-)  ++Kim
  12.  */
  13.  
  14.  
  15. #include "config.h"
  16.  
  17. /*
  18.  *     nn maintains a cache of recently used articles to improve efficiency.
  19.  *     To change the size of the cache, define NNTPCACHE in config.h to be
  20.  *    the new size of this cache.
  21.  */
  22.  
  23. #ifndef NNTPCACHE
  24. #define NNTPCACHE    10
  25. #endif
  26.  
  27. #ifdef NNTP
  28. #include <stdio.h>
  29. #include "nntp.h"
  30. #include <sys/socket.h>
  31. #ifndef EXCELAN
  32. #include <netdb.h>
  33. #endif
  34. #include <errno.h>
  35.  
  36. #ifdef NOV
  37. #include "hash.h"
  38. #include "hdbm.h"
  39. #include "newsoverview.h"
  40. #endif
  41.  
  42. /* This is necessary due to the definitions in m-XXX.h */
  43. #if !defined(NETWORK_DATABASE) || defined(NETWORK_BYTE_ORDER)
  44. #include <netinet/in.h>
  45. #endif
  46.  
  47. #ifdef EXCELAN
  48. #ifndef IPPORT_NNTP
  49. #define    IPPORT_NNTP    119
  50. #endif
  51. #endif
  52.  
  53.  
  54. /* nntp.c */
  55.  
  56. static debug_msg __APROTO((char *prefix, char *str));
  57. static io_error __APROTO((void));
  58. static void find_server __APROTO((void));
  59. static get_server_line __APROTO((char *string, int size));
  60. static get_server __APROTO((char *string, int size));
  61. static get_socket __APROTO((void));
  62. static connect_server __APROTO((void));
  63. static put_server __APROTO((char *string));
  64. static ask_server __APROTO(());
  65. static copy_text __APROTO((register FILE *fp));
  66. static do_set_group __APROTO((void));
  67. static struct cache *search_cache __APROTO((article_number art, group_header *gh));
  68. static struct cache *new_cache_slot __APROTO((void));
  69. static void clean_cache __APROTO((void));
  70. static sort_art_list __APROTO((register article_number *f1, register article_number *f2));
  71.  
  72.  
  73. import char *db_directory, *tmp_directory, *news_active;
  74.  
  75. export char nntp_server[256];    /* name of nntp server */
  76. export int nntp_failed = 0;    /* bool: t iff connection is broken in
  77.                    nntp_get_article() or nntp_get_active() */
  78.  
  79. export int nntp_cache_size = NNTPCACHE;
  80. export char *nntp_cache_dir = NULL;
  81.  
  82. export int nntp_local_server = 0;
  83. export int nntp_debug = 0;
  84.  
  85. import int silent, no_update;
  86.  
  87. import int sys_nerr;
  88. #ifndef __NetBSD__
  89. import char *sys_errlist[];
  90. #endif    /* __NetBSD__ */
  91. extern void nn_exitmsg();
  92. extern void sys_error();
  93. extern int sys_warning();
  94.  
  95. #define syserr() (errno >= 0 && errno < sys_nerr ? \
  96.           sys_errlist[errno] : "Unknown error.")
  97.  
  98. import char *mktemp();
  99.  
  100. static FILE *nntp_in = NULL;        /* fp for reading from server */
  101. static FILE *nntp_out = NULL;        /* fp for writing to server */
  102. static int is_connected = 0;        /* bool: t iff we are connected */
  103. static group_header *group_hd;        /* ptr to servers current group */
  104. static int group_is_set = 0;        /* bool: t iff group_hd is set */
  105. static int try_again = 0;        /* bool: t if timeout forces retry */
  106. static int can_post = 0;        /* bool: t iff NNTP server accepts postings */
  107.  
  108. #define ERR_TIMEOUT    503        /* Response code for timeout */
  109.                     /* Same value as ERR_FAULT */
  110.  
  111. #ifdef NO_RENAME
  112. static rename(old, new)
  113. char *old, *new;
  114. {
  115.     if (unlink(new) < 0 && errno != ENOENT) return -1;
  116.     if (link(old, new) < 0) return -1;
  117.     return unlink(old);
  118. }
  119. #endif /* NO_RENAME */
  120.  
  121. /*
  122.  * debug_msg: print a debug message.
  123.  *
  124.  *    The master appends prefix and str to a log file, and clients
  125.  *    prints it as a message.
  126.  *
  127.  *    This is controlled via the nntp-debug variable in nn, and
  128.  *    the option -D2 (or -D3 if the normal -D option should also
  129.  *    be turned on).  Debug output from the master is written in
  130.  *    $TMP/nnmaster.log.
  131.  */
  132.  
  133. static debug_msg(prefix, str)
  134. char *prefix, *str;
  135. {
  136.     static FILE *f = NULL;
  137.     
  138.     if (who_am_i == I_AM_MASTER) {
  139.     if (f == NULL) {
  140.         f = open_file(relative(tmp_directory, "nnmaster.log"), OPEN_CREATE);
  141.         if (f == NULL) {
  142.         nntp_debug = 0;
  143.         return;
  144.         }
  145.     }        
  146.     fprintf(f, "%s %s\n", prefix, str);
  147.     fflush(f);
  148.     return;
  149.     }
  150.  
  151.     msg("NNTP%s %s", prefix, str);
  152.     user_delay(1);
  153. }
  154.  
  155. /*
  156.  * io_error: signal an I/O error in talking to the server.
  157.  *
  158.  *     An nn client terminates a session with the user.  The master
  159.  *    simply closes the connection.  The flag nntp_failed is set, for
  160.  *    use by the master to terminate collection.
  161.  *
  162.  *    BUG: if the nntp server is forcibly killed, errno can contain a
  163.  *    bogus value, resulting in strange error messages.  It is
  164.  *    probably better just to write out the numerical value of errno.
  165.  */
  166.  
  167. static io_error()
  168. {
  169.     if (who_am_i != I_AM_MASTER) {
  170.     nn_exitmsg(1, "Lost connection to NNTP server %s: %s", nntp_server, syserr());
  171.         /* NOTREACHED */
  172.     }
  173.     nntp_failed = 1;
  174.     if (is_connected) {
  175.     log_entry('N', "Lost connection to server %s: %s", nntp_server, syserr());
  176.     nntp_close_server();
  177.     }
  178. }
  179.  
  180. /*
  181.  * find_server: Find out which host to use as NNTP server.
  182.  *
  183.  *     This is done by consulting the file NNTP_SERVER (defined in
  184.  *     config.h).  Set nntp_server[] to the host's name.
  185.  */
  186.  
  187. static void find_server()
  188. {
  189.     char *cp, *name, *getenv();
  190.     char buf[BUFSIZ];
  191.     FILE *fp;
  192.  
  193.     /*
  194.      * This feature cannot normally be enabled, because the database and
  195.      * the users rc file contains references to articles by number, and
  196.      * these numbers are not unique across NNTP servers.
  197.      */
  198. #ifdef NOV
  199.     /*
  200.      * But, since we no longer have a database to worry about, give the user
  201.      * the rope. If he wants to hang himself, then let him! :-)
  202.      * Let the user worry about keeping his .newsrc straight.
  203.      */
  204.     if ((cp = getenv("NNTPSERVER")) != NULL) {
  205.     strncpy(nntp_server, cp, sizeof nntp_server);
  206.     return;
  207.     }
  208. #endif /* NOV */
  209.  
  210.     name = NNTP_SERVER;
  211.     if (*name != '/')
  212.     name = relative(lib_directory, name);
  213.  
  214.     if ((fp = open_file(name, OPEN_READ)) != NULL) {
  215.     while (fgets(buf, sizeof buf, fp) != 0) {
  216.         if (*buf == '#' || *buf == '\n')
  217.         continue;
  218.         if ((cp = strchr(buf, '\n')) != 0)
  219.         *cp = '\0';
  220.         strncpy(nntp_server, buf, sizeof nntp_server);
  221.         fclose(fp);
  222.         return;
  223.     }
  224.     fclose(fp);
  225.     }
  226.  
  227.     if (who_am_i != I_AM_MASTER)
  228.     printf("\nCannot find name of NNTP server.\nCheck %s\n", name);
  229.  
  230.     sys_error("Failed to find name of NNTP server!");
  231. }
  232.  
  233. /*
  234.  * get_server_line: get a line from the server.
  235.  *
  236.  *     Expects to be connected to the server.
  237.  *     The line can be any kind of line, i.e., either response or text.
  238.  *    Returns length of line if no error.
  239.  *    If error and master, then return -1, else terminate.
  240.  */
  241.  
  242. static int
  243. get_server_line(string, size)
  244.     register char *string;
  245.     register int size;
  246. {
  247.     if (fgets(string, size, nntp_in) == NULL) {
  248.     io_error();
  249.     return -1;
  250.     }
  251.  
  252.     size = strlen(string);
  253.     if (size < 2 || !(string[size-2] == '\r' && string[size-1] == '\n'))
  254.     return size;            /* XXX */
  255.  
  256.     string[size-2] = '\0';        /* nuke CRLF */
  257.     return size-2;
  258. }
  259.  
  260. /*
  261.  * get_server: get a response line from the server.
  262.  *
  263.  *     Expects to be connected to the server.
  264.  *     Returns the numerical value of the reponse, or -1 in case of errors.
  265.  */
  266.  
  267. static get_server(string, size)
  268.     char *string;
  269.     int size;
  270. {
  271.     if (get_server_line(string, size) < 0)
  272.     return -1;
  273.  
  274.     if (nntp_debug) debug_msg("<<<", string);
  275.  
  276.     return isdigit(*string) ? atoi(string) : 0;
  277. }
  278.  
  279. /*
  280.  * get_socket:  get a connection to the nntp server.
  281.  *
  282.  * Errors can happen when YP services or DNS are temporarily down or
  283.  * hung, so we log errors and return failure rather than exitting if we
  284.  * are the master.  The effects of retrying every 15 minutes (or whatever
  285.  * the -r interval is) are not that bad.  Dave Olson, SGI
  286.  */
  287.  
  288. static get_socket()
  289. {
  290.     int s;
  291.     struct sockaddr_in sin;
  292. #ifndef EXCELAN
  293.     struct servent *getservbyname(), *sp;
  294.     struct hostent *gethostbyname(), *hp;
  295.  
  296. #ifdef h_addr
  297.     int     x = 0;
  298.     register char **cp;
  299. #endif /* h_addr */
  300.  
  301.     if ((sp = getservbyname("nntp", "tcp")) ==  NULL)
  302.     return sys_warning("nntp/tcp: Unknown service.\n");
  303.  
  304.     s = who_am_i == I_AM_MASTER ? 10 : 2;
  305.     while ((hp = gethostbyname(nntp_server)) == NULL) {
  306.     if (--s < 0) goto host_err;
  307.     sleep(10);
  308.     }
  309.  
  310.     clearobj(&sin, sizeof(sin), 1);
  311.     sin.sin_family = hp->h_addrtype;
  312.     sin.sin_port = sp->s_port;
  313.  
  314. #else /* EXCELAN */
  315.     char *machine;
  316.  
  317.     clearobj(&sin, sizeof(sin), 1);
  318.     sin.sin_family = AF_INET;
  319.     sin.sin_port = htons(0);
  320. #endif  /* EXCELAN */
  321.  
  322. #ifdef h_addr
  323.     /* get a socket and initiate connection -- use multiple addresses */
  324.  
  325.     s = x = -1;
  326.     for (cp = hp->h_addr_list; cp && *cp; cp++) {
  327.     s = socket(hp->h_addrtype, SOCK_STREAM, 0);
  328.     if (s < 0) goto sock_err;
  329. #ifdef NO_MEMMOVE
  330.     bcopy(*cp, (char *)&sin.sin_addr, hp->h_length);
  331. #else 
  332.     memmove((char *)&sin.sin_addr, *cp, hp->h_length);
  333. #endif /* NO_MEMMOVE */
  334.  
  335.     /* Quick hack to work around interrupting system calls.. */
  336.     while((x = connect(s, (struct sockaddr *)&sin, sizeof (sin))) < 0 &&
  337.         errno == EINTR) sleep(1);
  338.     if (x == 0)
  339.         break;
  340.     if (who_am_i != I_AM_MASTER)
  341.         msg("Connecting to %s failed: %s", nntp_server, syserr());
  342.     (void) close(s);
  343.     s = -1;
  344.     }
  345.     if (x < 0)
  346.     sys_warning("Giving up on NNTP server %s!", nntp_server);
  347. #else /* h_addr */                /* no name server */
  348. #ifdef EXCELAN
  349.     if ((s = socket(SOCK_STREAM, NULL, &sin, SO_KEEPALIVE)) < 0)
  350.     goto sock_err;
  351.     
  352.     sin.sin_port = htons(IPPORT_NNTP);
  353.     machine = nntp_server;
  354.     if ((sin.sin_addr.s_addr = rhost(&machine)) == -1) {
  355.     (void) close(s);
  356.     goto host_err;
  357.     }
  358.  
  359.     /* And then connect */
  360.     if (connect(s, &sin) < 0)
  361.     goto conn_err;
  362. #else /* not EXCELAN */
  363.     if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  364.     goto sock_err;
  365.     
  366.     /* And then connect */
  367. #ifdef NO_MEMMOVE
  368.     bcopy(hp->h_addr, (char *) &sin.sin_addr, hp->h_length);
  369. #else
  370.     memmove((char *)&sin.sin_addr, hp->h_addr, hp->h_length);
  371. #endif /* NO_MEMMOVE */
  372.     if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) < 0)
  373.     goto conn_err;
  374. #endif  /* EXCELAN */
  375. #endif /* h_addr */
  376.     return s;
  377.  
  378.  host_err:
  379.     sys_warning("NNTP server %s unknown.\n", nntp_server);
  380.     return -1;
  381.  
  382.  sock_err:
  383.     sys_warning("Can't get NNTP socket: %s", syserr());
  384.     return -1;
  385.  
  386.  conn_err:
  387.     (void) close(s);
  388.     if (who_am_i == I_AM_MASTER)
  389.     sys_warning("Connecting to %s failed: %s", nntp_server, syserr());
  390.     return -1;
  391. }
  392.  
  393. /*
  394.  * connect_server: initialise a connection to the nntp server.
  395.  *
  396.  *     It expects nntp_server[] to be set previously, by a call to
  397.  *     nntp_check.  It is called from nntp_get_article() and
  398.  *    nntp_get_active() if there is no established connection.
  399.  */
  400.  
  401. static connect_server()
  402. {
  403.     int sockt_rd, sockt_wr;
  404.     int response, response2;
  405.     char line[NNTP_STRLEN];
  406.     
  407.     if (who_am_i != I_AM_MASTER && !silent)
  408.     msg("Connecting to NNTP server %s ...", nntp_server);
  409.  
  410.     nntp_failed = 1;
  411.     is_connected = 0;
  412.  
  413.     sockt_rd = get_socket();
  414.     if (sockt_rd < 0)
  415.     return -1;
  416.  
  417.     if ((nntp_in = fdopen(sockt_rd, "r")) == NULL) {
  418.     close(sockt_rd);
  419.         return -1;
  420.     }
  421.     setvbuf(nntp_in, NULL, _IOLBF, 0);
  422.     sockt_wr = dup(sockt_rd);
  423.     if ((nntp_out = fdopen(sockt_wr, "w")) == NULL) {
  424.     close(sockt_wr);
  425.     fclose(nntp_in);
  426.         nntp_in = NULL;               /* from above */
  427.         return -1;
  428.     }
  429.  
  430.     /* Now get the server's signon message */
  431.     response = get_server(line, sizeof(line));
  432.  
  433.     if (who_am_i == I_AM_MASTER) {
  434.     if (response != OK_CANPOST && response != OK_NOPOST) {
  435.         log_entry('N', "Failed to connect to NNTP server");
  436.         log_entry('N', "Response: %s", line);
  437.         fclose(nntp_out);
  438.         fclose(nntp_in);
  439.         return -1;
  440.     }
  441.     } else {
  442.     switch (response) {
  443.     case OK_CANPOST:
  444.         can_post = 1;
  445.         break;
  446.     case OK_NOPOST:
  447.         can_post = 0;
  448.         break;
  449.     default:
  450.         nn_exitmsg(1, line);
  451.         /* NOTREACHED */
  452.     }
  453.     }
  454.  
  455.     /* Try and speak NNRP style reader protocol */
  456.     sprintf(line, "MODE READER");
  457.     if (put_server(line) >= 0) {
  458.         /* See what we got if from a NNRP compiant server like INN's nnrpd */
  459.         response = get_server(line, sizeof(line));
  460.  
  461.     switch (response) {
  462.     case OK_CANPOST:
  463.         can_post = 1;
  464.         break;
  465.     case OK_NOPOST:
  466.         can_post = 0;
  467.         break;
  468.     case ERR_COMMAND:
  469.     default:
  470.         /* if it doesn't understand MODE READER, we dont care.. :-) */
  471.         break;
  472.     }
  473.     }
  474.  
  475.     if (who_am_i != I_AM_MASTER && !silent)
  476.     msg("Connecting to NNTP server %s ... ok (%s)",
  477.         nntp_server, can_post ? "posting is allowed" : "no posting");
  478.  
  479.     is_connected = 1;
  480.     group_is_set = 0;
  481.     nntp_failed = 0;
  482.     try_again = 0;
  483.     return 0;
  484. }
  485.  
  486.  
  487. /*
  488.  * put_server:  send a line to the nntp server.
  489.  *
  490.  *     Expects to be connected to the server.
  491.  */
  492.  
  493. static put_server(string)
  494.     char *string;
  495. {
  496.     if (nntp_debug) debug_msg(">>>", string);
  497.  
  498.     fprintf(nntp_out, "%s\r\n", string);
  499.     if (fflush(nntp_out) == EOF) {
  500.     io_error();
  501.     return -1;
  502.     }
  503.     return 0;
  504. }
  505.  
  506. /*
  507.  * ask_server:  ask the server a question and return the answer.
  508.  *
  509.  *    Expects to be connected to the server.
  510.  *    Returns the numerical value of the reponse, or -1 in case of
  511.  *    errors.
  512.  *    Contains some code to handle server timeouts intelligently.
  513.  */
  514.  
  515. /* LIST XXX returns fatal ERR_FAULT code if requested list does not exist */
  516. /* This is only fatal for LIST ACTIVE -- else change to ERR_NOGROUPS */
  517. static int fix_list_response = 0;
  518.  
  519. /*VARARGS*/
  520. static ask_server(va_alist)
  521. va_dcl
  522. {
  523.     char buf[NNTP_STRLEN];
  524.     char *fmt;
  525.     int response;
  526.     int fix_err;
  527.     use_vararg;
  528.  
  529.     fix_err = fix_list_response;
  530.     fix_list_response = 0;
  531.  
  532.     start_vararg;
  533.     fmt = va_arg1(char *);
  534.     vsprintf(buf, fmt, va_args2toN);
  535.     end_vararg;
  536.  
  537.     if (put_server(buf) < 0)
  538.     return -1;
  539.     response = get_server(buf, sizeof(buf));
  540.  
  541.     /*
  542.      * Handle the response from the server.  Responses are handled as
  543.      * followes:
  544.      *
  545.      * 100-199    Informational.  Passed back. (should they be ignored?).
  546.      * 200-299    Ok messages.  Passed back.
  547.      * 300-399    Ok and proceed.  Can not happen in nn.
  548.      * 400-499    Errors (no article, etc).  Passed up and handled there.
  549.      * 500-599    Fatal NNTP errors.  Handled below.
  550.      */
  551.     if (response == ERR_GOODBYE || response > ERR_COMMAND) {
  552.     if (fix_err && response == ERR_FAULT) return ERR_NOGROUP;
  553.  
  554.     nntp_failed = 1;
  555.     nntp_close_server();
  556.  
  557.     if (response != ERR_TIMEOUT && response != ERR_GOODBYE) {
  558.             /* if not timeout and not goodbye, complain */
  559.         sys_error("NNTP %s response: %d", buf, response);
  560.         /* NOTREACHED */
  561.     }
  562.     if (response == ERR_GOODBYE) {
  563.         sys_warning("NNTP %s response: %d", buf, response);
  564.     }
  565.  
  566.     try_again = 1;
  567.     group_is_set = 0;
  568.     }
  569.     return response;
  570. }
  571.  
  572. /*
  573.  * copy_text: copy text response into file.
  574.  *
  575.  *     Copies a text response into an open file.
  576.  *    Return -1 on error, 0 otherwise.  It is treated as an error, if
  577.  *    the returned response it not what was expected.
  578.  */
  579.  
  580. static int last_copy_blank;
  581.  
  582. static copy_text(fp)
  583. register FILE *fp;
  584. {
  585.     char buf[NNTP_STRLEN];
  586.     register char *cp;
  587.     register int nlines;
  588.  
  589.     nlines = 0;
  590.     last_copy_blank = 0;
  591.     while (get_server_line(buf, sizeof buf) >= 0) {
  592.     cp = buf;
  593.     if (*cp == '.')
  594.         if (*++cp == '\0') {
  595.         if (nlines <= 0) break;
  596.         if (nntp_debug) {
  597.             sprintf(buf, "%d lines", nlines);
  598.             debug_msg("COPY", buf);
  599.         }
  600.         return 0;
  601.         }
  602.     last_copy_blank = (*cp == NUL);
  603.     fputs(cp, fp);
  604.     putc('\n', fp);
  605.     nlines++;
  606.     }
  607.     fclose(fp);
  608.     if (nntp_debug) debug_msg("COPY", "EMPTY");
  609.     return -1;
  610. }
  611.  
  612.  
  613. static do_set_group()
  614. {
  615.     int n;
  616.  
  617.     switch (n = ask_server("GROUP %s", group_hd->group_name)) {
  618.      case OK_GROUP:
  619.     group_is_set = 1;
  620.     return 1;
  621.  
  622.      case ERR_NOGROUP:
  623.     log_entry('N', "NNTP: group %s not found", group_hd->group_name);
  624.     return -1;
  625.  
  626.      default:
  627.     if (try_again) return 0;    /* Handle nntp server timeouts */
  628.     break;
  629.     }
  630.     if (!nntp_failed) {
  631.     log_entry('N', "GROUP %s response: %d", group_hd->group_name, n);
  632.     nntp_failed = 1;
  633.     }
  634.     return -1;
  635. }
  636.  
  637. /*
  638.  * The following functions implements a simple lru cache of recently
  639.  * accessed articles.  It is a simple way to improve effeciency.  Files
  640.  * must be kept by name, because the rest of the code expects to be able
  641.  * to open an article multiple times, and get separate file pointers.
  642.  */
  643.  
  644. struct cache {
  645.     char        *file_name;    /* file name */
  646.     article_number    art;        /* article stored in file */
  647.     group_header    *grp;        /* from this group */
  648.     unsigned        time;        /* time last accessed */
  649. } cache[NNTPCACHE];
  650.  
  651. static unsigned time_counter = 1;        /* virtual time */
  652.  
  653. /*
  654.  * search_cache: search the cache for an (article, group) pair.
  655.  *
  656.  *     Returns a pointer to the slot where it is, null otherwise
  657.  */
  658.  
  659. static struct cache *search_cache(art, gh)
  660.     article_number art;
  661.     group_header *gh;
  662. {
  663.     struct cache *cptr = cache;
  664.     int i;
  665.  
  666.     if (who_am_i == I_AM_MASTER) return NULL;
  667.  
  668.     if (nntp_cache_size > NNTPCACHE) nntp_cache_size = NNTPCACHE;
  669.  
  670.     for (i = 0; i < nntp_cache_size; i++, cptr++)
  671.     if (cptr->art == art && cptr->grp == gh) {
  672.         cptr->time = time_counter++;
  673.         return cptr;
  674.     }
  675.     return NULL;
  676. }
  677.  
  678. /*
  679.  * new_cache_slot: get a free cache slot.
  680.  *
  681.  *     Returns a pointer to the allocated slot.
  682.  *     Frees the old filename, and allocates a new, unused filename.
  683.  *    Cache files can also stored in a common directory defined in
  684.  *    ~/.nn or CACHE_DIRECTORY if defined in config.h.
  685.  */
  686.  
  687. static struct cache *new_cache_slot()
  688. {
  689.     register struct cache *cptr = cache;
  690.     int i, lru;
  691.     unsigned min_time = time_counter;
  692.     char name[FILENAME];
  693.  
  694.     if (nntp_cache_dir == NULL) {
  695. #ifdef CACHE_DIRECTORY
  696.     nntp_cache_dir = CACHE_DIRECTORY;
  697. #else /* CACHE_DIRECTORY */
  698.     if (who_am_i == I_AM_MASTER)
  699.         nntp_cache_dir = db_directory;
  700.     else
  701.         nntp_cache_dir = nn_directory;
  702. #endif /* CACHE_DIRECTORY */
  703.     }
  704.  
  705.     if (who_am_i == I_AM_MASTER) {
  706.     cptr = &cache[0];
  707.     if (cptr->file_name == NULL)
  708.         cptr->file_name = mk_file_name(nntp_cache_dir, "master_cache");
  709.     return cptr;
  710.     }
  711.  
  712.     for (i = 0; i < nntp_cache_size; i++, cptr++)
  713.     if (min_time > cptr->time) {
  714.         min_time = cptr->time;
  715.         lru = i;
  716.     }
  717.     cptr = &cache[lru];
  718.  
  719.     if (cptr->file_name == NULL) {
  720.     sprintf(name, "%s/nn-%d.%02d~", nntp_cache_dir, process_id, lru);
  721.     cptr->file_name = copy_str(name);
  722.     } else
  723.     unlink(cptr->file_name);
  724.  
  725.     cptr->time = time_counter++;
  726.     return cptr;
  727. }
  728.  
  729. /*
  730.  * clean_cache: clean up the cache.
  731.  *
  732.  *     Removes all allocated files.
  733.  */
  734.  
  735. static void clean_cache()
  736. {
  737.     struct cache *cptr = cache;
  738.     int i;
  739.  
  740.     for (i = 0; i < nntp_cache_size; i++, cptr++)
  741.     if (cptr->file_name)
  742.         unlink(cptr->file_name);
  743. }
  744.  
  745. /*
  746.  * nntp_check: Find out whether we need to use NNTP.
  747.  *
  748.  *     This is done by comparing the NNTP servers name with whatever
  749.  *     nn_gethostname() returns.
  750.  *    use_nntp and news_active are initialised as a side effect.
  751.  */
  752.  
  753. nntp_check()
  754. {
  755.     char host[128];
  756.     char *server_real_name;
  757.  
  758.     if (nntp_local_server) return;
  759.  
  760.     find_server();
  761.     nn_gethostname(host, sizeof host);
  762.     strncpy(host, (gethostbyname(host))->h_name, sizeof host);
  763.  
  764.     server_real_name = (gethostbyname(nntp_server))->h_name;
  765.     use_nntp = (strcmp(host, server_real_name) != 0);
  766.  
  767.     if (use_nntp) {
  768.     freeobj(news_active);
  769. #ifndef NOV
  770.     news_active = mk_file_name(db_directory, "ACTIVE");
  771. #else /* NOV */
  772.     news_active = mk_file_name(nn_directory, "ACTIVE");
  773. #endif /* NOV */
  774.     }
  775. }
  776.  
  777. /*
  778.  * nntp_no_post: Check to see whether posting is allowed.
  779.  */
  780.  
  781. nntp_no_post()
  782. {
  783.     if (!is_connected && connect_server() < 0)
  784.     return 1;            /* If we cannot connect, neither can inews */
  785.     if (can_post == 0) {
  786.     msg("NNTP server does not allow postings from this host.  Sorry!");
  787.     return 1;
  788.     }
  789.     return 0;
  790. }
  791.  
  792.  
  793. /*
  794.  * nntp_set_group: set the server's current group.
  795.  *
  796.  *     Actual communication is delayed until an article is accessed, to
  797.  *     avoid unnecessary traffic.
  798.  */
  799.  
  800. nntp_set_group(gh)
  801.     group_header *gh;
  802. {
  803.     group_hd = gh;
  804.     group_is_set = 0;
  805.     return 0;
  806. }
  807.  
  808. #ifndef NOV
  809. /*
  810.  * nntp_get_active:  get a copy of the active file.
  811.  *
  812.  *     If we are the master get a copy of the file from the nntp server.
  813.  *     nnadmin just uses the one we already got.  In this way the master
  814.  *    can maintain a remote copy of the servers active file.
  815.  *    We try to be a little smart, if not inefficient, about the
  816.  *    modification times on the local active file.
  817.  *    Even when the master is running on the nntp server, a separate
  818.  *    copy of the active file will be made for access via NFS.
  819.  */
  820.  
  821. nntp_get_active()
  822. {
  823.     FILE *old, *new;
  824.     char bufo[NNTP_STRLEN], bufn[NNTP_STRLEN];
  825.     char *new_name;
  826.     int same, n;
  827.  
  828.     if (who_am_i != I_AM_MASTER)
  829.     return access(news_active, 4);
  830.  
  831.  again:
  832.     if (!is_connected && connect_server() < 0)
  833.     return -1;
  834.  
  835.     new_name = mktemp(relative(db_directory, ".actXXXXXX"));
  836.  
  837.     switch (n = ask_server("LIST")) {
  838.      case OK_GROUPS:
  839.     new = open_file(new_name, OPEN_CREATE_RW|MUST_EXIST);
  840.     if (copy_text(new) == 0) {
  841.         if (fflush(new) != EOF) break;
  842.         fclose(new);
  843.     }
  844.     unlink(new_name);
  845.     if (!nntp_failed) {
  846.         log_entry('N', "LIST empty");
  847.         nntp_failed = 1;
  848.     }
  849.     return -1;
  850.      default:
  851.     if (try_again) goto again; /* Handle nntp server timeouts */
  852.     log_entry('N', "LIST response: %d", n);
  853.     return -1;
  854.     }
  855.  
  856.     rewind(new);
  857.     same = 0;
  858.     if ((old = open_file(news_active, OPEN_READ)) != NULL) {
  859.     do {
  860.         fgets(bufo, sizeof bufo, old);
  861.         fgets(bufn, sizeof bufn, new);
  862.     } while (!feof(old) && !feof(new) && strcmp(bufo, bufn) == 0);
  863.     same = feof(old) && feof(new);
  864.     fclose(old);
  865.     }
  866.  
  867.     fclose(new);
  868.  
  869.     if (same)
  870.     unlink(new_name);
  871.     else
  872.     if (rename(new_name, news_active) != 0)
  873.         sys_error("Cannot rename %s to %s", new_name, news_active);
  874.  
  875.     return 0;
  876. }
  877. #endif /* NOV */
  878.  
  879. /*
  880.  * nntp_get_newsgroups:  get a copy of the newsgroups file.
  881.  *
  882.  *    Use the "LIST NEWSGROUPS" command to get the newsgroup descriptions.
  883.  *    Based on code from: olson%anchor.esd@sgi.com (Dave Olson)
  884.  */
  885.  
  886. FILE *nntp_get_newsgroups()
  887. {
  888.     char *new_name;
  889.     FILE *new;
  890.     int n;
  891.  
  892.     new_name = mktemp(relative(tmp_directory, "nngrXXXXXX"));
  893.     new = open_file(new_name, OPEN_CREATE_RW|OPEN_UNLINK);
  894.     if (new == NULL) return NULL;
  895.  
  896.  again:
  897.     if (!is_connected && connect_server() < 0) goto err;
  898.  
  899.     fix_list_response = 1;
  900.     switch (n = ask_server("LIST NEWSGROUPS")) {
  901.      case ERR_NOGROUP:        /* really ERR_FAULT */
  902.     goto err;
  903.  
  904.      case OK_GROUPS:
  905.     if (copy_text(new) == 0) {
  906.         if (fflush(new) != EOF) break;
  907.         fclose(new);
  908.     }
  909.     if (!nntp_failed) {
  910.         log_entry('N', "LIST NEWSGROUPS empty");
  911.         nntp_failed = 1;
  912.     }
  913.     return NULL;
  914.  
  915.      default:
  916.     if (try_again) goto again; /* Handle nntp server timeouts */
  917.     log_entry('N', "LIST NEWSGROUPS response: %d", n);
  918.     goto err;
  919.     }
  920.     rewind(new);
  921.     return new;
  922.  
  923.  err:
  924.     fclose(new);
  925.     return NULL;
  926. }
  927.  
  928. #ifndef NOV
  929. /*
  930.  * nntp_get_article_list: get list of all article numbers in group
  931.  *
  932.  *     Sends XHDR command to the server, and parses the following
  933.  *    text response to get a list of article numbers which is saved
  934.  *    in a list and returned.
  935.  *    Return NULL on error.  It is treated as an error, if
  936.  *    the returned response it not what was expected.
  937.  */
  938.  
  939. static article_number *article_list = NULL;
  940. static long art_list_length = 0;
  941.  
  942. static sort_art_list(f1, f2)
  943. register article_number *f1, *f2;
  944. {
  945.     return (*f1 < *f2) ? -1 : (*f1 == *f2) ? 0 : 1;
  946. }
  947.  
  948. article_number *nntp_get_article_list(gh)
  949. group_header *gh;
  950. {
  951.     char buf[NNTP_STRLEN];
  952.     register article_number *art;
  953.     register char *cp;
  954.     register long count = 0;    /* No. of completions plus one */
  955.     int n;
  956.     static int try_listgroup = 1;
  957.  
  958.  again:
  959.     if (!is_connected && connect_server() < 0)
  960.     return NULL;
  961.  
  962.     /* it is really an extreme waste of time to use XHDR since all we    */
  963.     /* are interested in is the article numbers (as we do locally).    */
  964.     /* If somebody hacks up an nntp server that understands LISTGROUP    */
  965.     /* they will get much less load on the nntp server            */
  966.     /* It should simply return the existing article numbers is the group*/
  967.     /* -- they don't even have to be sorted (only XHDR needs that)    */
  968.  
  969.     if (try_listgroup) {
  970.     switch (n = ask_server("LISTGROUP %s", group_hd->group_name)) {
  971.      case OK_GROUP:
  972.         break;
  973.      default:
  974.         if (try_again) goto again; /* Handle nntp server timeouts */
  975.         log_entry('N', "LISTGROUP response: %d", n);
  976.         return NULL;
  977.      case ERR_COMMAND:
  978.         try_listgroup = 0;
  979.         goto again;    /* error may have closed down server connection */
  980.     }
  981.     }
  982.     if (!try_listgroup) {
  983.     if (group_is_set == 0)
  984.         switch (do_set_group()) {
  985.          case -1:
  986.         return NULL;
  987.          case 0:
  988.         goto again;
  989.          case 1:
  990.         break;
  991.         }
  992.  
  993.     switch (n = ask_server("XHDR message-id %ld-%ld",
  994.         (long)gh->first_db_article, (long)gh->last_db_article)) {
  995.      case OK_HEAD:
  996.         break;
  997.      default:
  998.         if (try_again) goto again; /* Handle nntp server timeouts */
  999.         log_entry('N', "XHDR response: %d", n);
  1000.         return NULL;
  1001.      case ERR_COMMAND:
  1002.         nntp_failed = 2;
  1003.         return NULL;
  1004.     }
  1005.     }
  1006.  
  1007.     count = 0;
  1008.     art = article_list;
  1009.     
  1010.     while (get_server_line(buf, sizeof buf) >= 0) {
  1011.     cp = buf;
  1012.     if (*cp == '.' && *++cp == '\0') break;
  1013.  
  1014.     if (count == art_list_length) {
  1015.         art_list_length += 250;
  1016.         article_list = resizeobj(article_list, article_number, art_list_length + 1);
  1017.         art = article_list + count;
  1018.     }
  1019.     *art++ = atol(cp);
  1020.     count++;
  1021.     }
  1022.  
  1023.     if (article_list != NULL) {
  1024.     *art = 0;
  1025.     if (try_listgroup && count > 1)
  1026.         quicksort(article_list, count, article_number, sort_art_list);
  1027.     }
  1028.     return article_list;
  1029. }
  1030. #endif    /* NOV */
  1031.  
  1032. /*
  1033.  * nntp_get_article: get an article from the server.
  1034.  *
  1035.  *     Returns a FILE pointer.
  1036.  *    If necessary the server's current group is set.
  1037.  *    The article (header and body) are copied into a file, so they
  1038.  *    are seekable (nn likes that).
  1039.  */
  1040.  
  1041. static char *mode_cmd[] = {
  1042.     "ARTICLE",
  1043.     "HEAD",
  1044.     "BODY"
  1045. };
  1046.  
  1047. FILE *nntp_get_article(article, mode)
  1048. article_number article;
  1049. int mode;    /* 0 => whole article, 1 => head only, 2 => body only */
  1050. {
  1051.     FILE *tmp;
  1052.     static struct cache *cptr;
  1053.     int n;
  1054.     
  1055.  again:
  1056.     if (!is_connected && connect_server() < 0) {
  1057.     return NULL;
  1058.     }
  1059.  
  1060.     /*
  1061.      * Set the server group to the current group
  1062.      */
  1063.     if (group_is_set == 0)
  1064.     switch (do_set_group()) {
  1065.      case -1:
  1066.         return NULL;
  1067.      case 0:
  1068.         goto again;
  1069.      case 1:
  1070.         break;
  1071.     }
  1072.  
  1073.     /*
  1074.      * Search the cache for the requested article, and allocate a new
  1075.      * slot if necessary (if appending body, we already got it).
  1076.      */
  1077.  
  1078.     if (mode != 2) {
  1079.     cptr = search_cache(article, group_hd);
  1080.     if (cptr != NULL) goto out;
  1081.     cptr = new_cache_slot();
  1082.     }
  1083.     
  1084.     /*
  1085.      * Copy the article.
  1086.      */
  1087.     switch (n = ask_server("%s %ld", mode_cmd[mode], (long)article)) {
  1088.      case OK_ARTICLE:
  1089.      case OK_HEAD:
  1090.     tmp = open_file(cptr->file_name, OPEN_CREATE|MUST_EXIST);
  1091.     if (copy_text(tmp) < 0)
  1092.         return NULL;
  1093.  
  1094.     if (mode == 1 && !last_copy_blank)
  1095.         fputc(NL, tmp); /* add blank line after header */
  1096.  
  1097.     if (fclose(tmp) == EOF) goto err;
  1098.     cptr->art = article;
  1099.     cptr->grp = group_hd;
  1100.     goto out;
  1101.  
  1102.      case OK_BODY:
  1103.     tmp = open_file(cptr->file_name, OPEN_APPEND|MUST_EXIST);
  1104.     fseek(tmp, (off_t)0, 2);
  1105.     if (copy_text(tmp) < 0)
  1106.         return NULL;
  1107.     if (fclose(tmp) == EOF) goto err;
  1108.     goto out;
  1109.     
  1110.      case ERR_NOARTIG:
  1111.              /* Matt Heffron: ANUNEWS on VMS uses no such article error */
  1112.      case ERR_NOART:
  1113.     return NULL;
  1114.  
  1115.      default:
  1116.     if (try_again) goto again; /* Handle nntp server timeouts */
  1117.     /* Matt Heffron: Which group? */
  1118.     log_entry('N', "ARTICLE %ld response: %d (in Group %s)",
  1119.                     (long)article, n, group_hd->group_name);
  1120.     nntp_failed = 1;
  1121.     return NULL;
  1122.     }
  1123.  
  1124.  out:
  1125.     return open_file(cptr->file_name, OPEN_READ|MUST_EXIST);
  1126.  
  1127.  err:
  1128.     sys_error('N', "Cannot write temporary file %s", cptr->file_name);
  1129. }
  1130.  
  1131. /*
  1132.  *    Return local file name holding article
  1133.  */
  1134.  
  1135. char *nntp_get_filename(art, gh)
  1136. article_number art;
  1137. group_header *gh;
  1138. {
  1139.     struct cache *cptr;
  1140.  
  1141.     cptr = search_cache(art, gh);
  1142.  
  1143.     return cptr == NULL ? NULL : cptr->file_name;
  1144. }
  1145.  
  1146. /*
  1147.  * nntp_close_server: close the connection to the server.
  1148.  */
  1149.  
  1150. void
  1151. nntp_close_server()
  1152. {
  1153.     if (!is_connected)
  1154.     return;
  1155.  
  1156.     if (!nntp_failed) {            /* avoid infinite recursion */
  1157.     int n;
  1158.  
  1159.     n = ask_server("QUIT");
  1160.     if (n != OK_GOODBYE)
  1161.         ;                /* WHAT NOW ??? */
  1162.     }
  1163.  
  1164.     (void) fclose(nntp_out);
  1165.     (void) fclose(nntp_in);
  1166.  
  1167.     is_connected = 0;
  1168. }
  1169.  
  1170. /*
  1171.  * nntp_cleanup:  clean up after an nntp session.
  1172.  *
  1173.  *    Called from nn_exit().
  1174.  */
  1175.  
  1176. nntp_cleanup()
  1177. {
  1178.     if (is_connected)
  1179.     nntp_close_server();
  1180.     clean_cache();
  1181. }
  1182.  
  1183.  
  1184. /*************************************************************/
  1185.  
  1186. #ifdef NOV
  1187.  
  1188. /*
  1189. ** Prime the nntp server to snarf the overview file for a newsgroup.
  1190. ** Sends the XOVER command and prepares to read the result.
  1191. */
  1192. struct novgroup *
  1193. nntp_get_overview(gh, first, last)
  1194. group_header *gh;
  1195. article_number first, last;
  1196. {
  1197.   int    n;
  1198.  
  1199. again:
  1200.   if (!is_connected && connect_server() < 0) {
  1201.     return NULL;
  1202.   }
  1203.   nntp_set_group(gh);
  1204.   switch (do_set_group()) {
  1205.   case -1:
  1206.     return NULL;
  1207.   case 0:
  1208.     goto again;
  1209.   }
  1210.  
  1211.   n = ask_server("XOVER %d-%d", first, last);
  1212.   switch (n) {
  1213.     
  1214.   case OK_NOV:
  1215.     return novstream(nntp_in);
  1216.  
  1217.   default:
  1218.     if (try_again) 
  1219.       goto again; /* Handle nntp server timeouts */
  1220.     log_entry('N', "XOVER response: %d", n);
  1221.     return NULL;
  1222.  
  1223.   }
  1224. }
  1225.  
  1226. /*
  1227.  * nntp_fopen_list(cmd):  Send some variant of a LIST command to the
  1228.  * NNTP server.  returns NULL if the file to be LISTed doesn't exist
  1229.  * on the server, else returns the nntp_in FILE descriptor, thus
  1230.  * simulating fopen().
  1231.  * nntp_fgets() is later used to read a line from the nntp_in FILE.
  1232.  */
  1233.  
  1234. FILE *
  1235. nntp_fopen_list(cmd)
  1236. char *cmd;
  1237. {
  1238.   int    n;
  1239.  
  1240. again:
  1241.   if (!is_connected && connect_server() < 0) 
  1242.     return NULL;
  1243.  
  1244.   fix_list_response = 1;
  1245.   switch (n = ask_server(cmd)) {
  1246.   case ERR_NOGROUP:        /* really ERR_FAULT - no such file on server */
  1247.     return NULL;
  1248.  
  1249.   case OK_GROUPS:        /* aka NNTP_LIST_FOLLOWS_VAL */
  1250.     return nntp_in;
  1251.  
  1252.   default:
  1253.     if (try_again) goto again;    /* Handle nntp server timeouts */
  1254.     log_entry('N', "`%s' response: %d", cmd, n);
  1255.     return NULL;
  1256.   }
  1257. }
  1258.  
  1259. /*
  1260.  * nntp_fgets() - Get a line from a file stored on the NNTP server.
  1261.  * Strips any hidden "." at beginning of line and returns a pointer to
  1262.  * the line.  line will be terminated by NL NUL.
  1263.  * Returns NULL when NNTP sends the terminating "." to indicate EOF.
  1264.  */
  1265. char *
  1266. nntp_fgets(buf, bufsize)
  1267. char *buf;
  1268. int bufsize;
  1269. {
  1270.     char *cp;
  1271.     register int size;
  1272.  
  1273.     if ((size = get_server_line(buf, bufsize - 1)) < 0)
  1274.     return NULL;    /* Can't happen with NOV (we'd rather die first) */
  1275.  
  1276.     cp = buf;
  1277.     if (*cp == '.') {
  1278.         if (*++cp == '\0')
  1279.         return NULL;
  1280.     }
  1281.     cp[size]   = '\n';
  1282.     cp[size+1] = '\0';
  1283.     return cp;
  1284. }
  1285.  
  1286. #endif /* NOV */
  1287.  
  1288. #endif /* NNTP */
  1289.  
  1290.