"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/active.c" (23 Nov 2018, 35494 Bytes) of package /linux/misc/tin-2.4.3.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "active.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.2_vs_2.4.3.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : active.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-02-16
    6  *  Updated   : 2018-11-22
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1992-2019 Iain Lea <iain@bricbrac.de>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. The name of the author may not be used to endorse or promote
   21  *    products derived from this software without specific prior written
   22  *    permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
   25  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
   28  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   30  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  */
   36 
   37 
   38 #ifndef TIN_H
   39 #   include "tin.h"
   40 #endif /* !TIN_H */
   41 #ifndef TCURSES_H
   42 #   include "tcurses.h"
   43 #endif /* !TCURSES_H */
   44 
   45 /*
   46  * List of allowed separator chars in active file
   47  * unused in parse_active_line()
   48  */
   49 #define ACTIVE_SEP  " \n"
   50 
   51 #ifdef NNTP_ABLE
   52 #   ifdef DISABLE_PIPELINING
   53 #       define NUM_SIMULTANEOUS_GROUP_COMMAND 1
   54 #   else
   55 #       define NUM_SIMULTANEOUS_GROUP_COMMAND 50
   56 #   endif /* DISABLE_PIPELINING */
   57 #endif /* NNTP_ABLE */
   58 
   59 t_bool force_reread_active_file = FALSE;
   60 static time_t active_timestamp; /* time active file read (local) */
   61 
   62 
   63 /*
   64  * Local prototypes
   65  */
   66 static FILE *open_newgroups_fp(int idx);
   67 static FILE *open_news_active_fp(void);
   68 static int check_for_any_new_groups(void);
   69 static void active_add(struct t_group *ptr, t_artnum count, t_artnum max, t_artnum min, const char *moderated);
   70 static void append_group_line(char *active_file, char *group_path, t_artnum art_max, t_artnum art_min, char *base_dir);
   71 static void make_group_list(char *active_file, char *base_dir, char *fixed_base, char *group_path);
   72 static void read_active_file(void);
   73 static void read_newsrc_active_file(void);
   74 static void subscribe_new_group(char *group, char *autosubscribe, char *autounsubscribe);
   75 #ifdef NNTP_ABLE
   76     static t_bool do_read_newsrc_active_file(FILE *fp);
   77     static t_bool parse_count_line(char *line, t_artnum *max, t_artnum *min, t_artnum *count, char *moderated);
   78     static void read_active_counts(void);
   79 #else
   80     static void do_read_newsrc_active_file(FILE *fp);
   81 #endif /* NNTP_ABLE */
   82 
   83 
   84 t_bool
   85 need_reread_active_file(
   86     void)
   87 {
   88     return (force_reread_active_file || (tinrc.reread_active_file_secs != 0 &&
   89         (int) (time(NULL) - active_timestamp) >= tinrc.reread_active_file_secs));
   90 }
   91 
   92 
   93 /*
   94  * Resync active file when reread_active_file_secs have passed or
   95  * force_reread_active_file is set.
   96  * Return TRUE if a reread was performed
   97  */
   98 t_bool
   99 resync_active_file(
  100     void)
  101 {
  102     char *old_group = NULL;
  103     t_bool command_line = FALSE;
  104 
  105     if (!need_reread_active_file())
  106         return FALSE;
  107 
  108     reread_active_for_posted_arts = FALSE;
  109 
  110     if (selmenu.curr >= 0 && selmenu.max)
  111         old_group = my_strdup(CURR_GROUP.name);
  112 
  113     write_newsrc();
  114     read_news_active_file();
  115 
  116 #ifdef HAVE_MH_MAIL_HANDLING
  117     read_mail_active_file();
  118 #endif /* HAVE_MH_MAIL_HANDLING */
  119 
  120     if (read_cmd_line_groups())
  121         command_line = TRUE;
  122 
  123     read_newsrc(newsrc, bool_not(command_line));
  124 
  125     if (command_line)       /* Can't show only unread groups with cmd line groups */
  126         tinrc.show_only_unread_groups = FALSE;
  127     else
  128         toggle_my_groups(old_group);
  129 
  130     FreeIfNeeded(old_group);
  131     show_selection_page();
  132 
  133     return TRUE;
  134 }
  135 
  136 
  137 /*
  138  * Populate a slot in the active[] array
  139  * TODO: 1) Have a preinitialised default slot and block assign it for speed
  140  * TODO: 2) Lump count/max/min/moderat into a t_active, big patch but much cleaner throughout tin
  141  */
  142 static void
  143 active_add(
  144     struct t_group *ptr,
  145     t_artnum count,
  146     t_artnum max,
  147     t_artnum min,
  148     const char *moderated)
  149 {
  150     /* name - pre-initialised when group is made */
  151     ptr->aliasedto = ((moderated[0] == '=') ? my_strdup(moderated + 1) : NULL);
  152     ptr->description = NULL;
  153     /* spool - see below */
  154     ptr->moderated = moderated[0];
  155     ptr->count = count;
  156     ptr->xmax = max;
  157     ptr->xmin = min;
  158     /* type - see below */
  159     ptr->inrange = FALSE;
  160     ptr->read_during_session = FALSE;
  161     ptr->art_was_posted = FALSE;
  162     ptr->subscribed = FALSE;            /* not in my_group[] yet */
  163     ptr->newgroup = FALSE;
  164     ptr->bogus = FALSE;
  165     ptr->next = -1;                     /* hash chain */
  166     ptr->newsrc.xbitmap = (t_bitmap *) 0;
  167     ptr->attribute = (struct t_attribute *) 0;
  168     ptr->glob_filter = &glob_filter;
  169     set_default_bitmap(ptr);
  170 
  171     if (moderated[0] == '/') {
  172         ptr->type = GROUP_TYPE_SAVE;
  173         ptr->spooldir = my_strdup(moderated); /* TODO: Unix'ism, other OSs need transformation */
  174     } else {
  175         ptr->type = GROUP_TYPE_NEWS;
  176         ptr->spooldir = spooldir;       /* another global - sigh */
  177     }
  178 }
  179 
  180 
  181 /*
  182  * Decide how to handle a bogus groupname.
  183  * If we process them interactively, create an empty active[] for this
  184  * group and mark it bogus for display in the group selection page
  185  * Otherwise, bogus groups are not displayed and are dealt with when newsrc
  186  * is written.
  187  */
  188 t_bool
  189 process_bogus(
  190     char *name) /* return value is always ignored */
  191 {
  192     struct t_group *ptr;
  193 
  194     if (read_saved_news || tinrc.strip_bogus != BOGUS_SHOW)
  195         return FALSE;
  196 
  197     if ((ptr = group_add(name)) == NULL)
  198         return FALSE;
  199 
  200     active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "n");
  201     ptr->bogus = TRUE;      /* Mark it bogus */
  202 
  203     if (my_group_add(name, FALSE) < 0)
  204         return TRUE;
  205 
  206     return FALSE;       /* Nothing was printed yet */
  207 }
  208 
  209 
  210 /*
  211  * Parse line from news or mail active files
  212  */
  213 t_bool
  214 parse_active_line(
  215     char *line,
  216     t_artnum *max,
  217     t_artnum *min,
  218     char *moderated)
  219 {
  220     char *p = NULL, *q = NULL, *r = NULL;
  221     t_bool lineok = FALSE;
  222 
  223     if (line[0] == '#' || line[0] == '\0')
  224         return FALSE;
  225 
  226     if (strtok(line, ACTIVE_SEP)) {     /* skip group name */
  227         if ((p = strtok(NULL, ACTIVE_SEP))) {   /* group max count */
  228             if ((q = strtok(NULL, ACTIVE_SEP))) {   /* group min count */
  229                 r = strtok(NULL, ACTIVE_SEP);   /* mod status or path to mailgroup */
  230                 lineok = TRUE;
  231             }
  232         }
  233     }
  234 
  235     if (!p || !q || !r || !lineok) {
  236 #ifdef DEBUG
  237         /* TODO: This also logs broken non NNTP active lines (i.e. mail.active) to NNTP */
  238         if (debug & DEBUG_NNTP && verbose > 1)
  239             debug_print_file("NNTP", "Active file corrupt - %s", line);
  240 #endif /* DEBUG */
  241         return FALSE;
  242     }
  243 
  244     *max = atoartnum(p);
  245     *min = atoartnum(q);
  246     strcpy(moderated, r);
  247 
  248     return TRUE;
  249 }
  250 
  251 
  252 #ifdef NNTP_ABLE
  253 /*
  254  * Parse line from "LIST COUNTS" (RFC 6048)
  255  * group high low count status
  256  */
  257 static t_bool
  258 parse_count_line(
  259     char *line,
  260     t_artnum *max,
  261     t_artnum *min,
  262     t_artnum *count,
  263     char *moderated)
  264 {
  265     char *p = NULL, *q = NULL, *r = NULL, *s = NULL;
  266     t_bool lineok = FALSE;
  267 
  268     if (line[0] == '#' || line[0] == '\0')
  269         return FALSE;
  270 
  271     if (strtok(line, ACTIVE_SEP)) {     /* skip group name */
  272         if ((p = strtok(NULL, ACTIVE_SEP))) {   /* group max */
  273             if ((q = strtok(NULL, ACTIVE_SEP))) {   /* group min */
  274                 if ((r = strtok(NULL, ACTIVE_SEP))) { /* group count */
  275                     s = strtok(NULL, ACTIVE_SEP);   /* mod status or path to mailgroup */
  276                     lineok = TRUE;
  277                 }
  278             }
  279         }
  280     }
  281 
  282     if (!p || !q || !r || !s || !lineok) {
  283 #   ifdef DEBUG
  284         if (debug & DEBUG_NNTP && verbose > 1)
  285             debug_print_file("NNTP", "unparseable \"LIST COUNTS\" line: \"%s\"", line);
  286 #   endif /* DEBUG */
  287         return FALSE;
  288     }
  289 
  290     *max = atoartnum(p);
  291     *min = atoartnum(q);
  292     *count = atoartnum(r);
  293     strcpy(moderated, s);
  294 
  295     return TRUE;
  296 }
  297 #endif /* NNTP_ABLE */
  298 
  299 
  300 /*
  301  * Load the active information into active[] by counting the min/max/count
  302  * for each news group.
  303  * Parse a line from the .newsrc file
  304  * Send GROUP command to NNTP server directly to keep window.
  305  * We can't know the 'moderator' status and always return 'y'
  306  * But we don't change if the 'moderator' status is already checked by
  307  * read_active_file()
  308  * Returns TRUE if NNTP is enabled and authentication is needed
  309  */
  310 #ifdef NNTP_ABLE
  311 static t_bool
  312 #else
  313 static void
  314 #endif /* NNTP_ABLE */
  315 do_read_newsrc_active_file(
  316     FILE *fp)
  317 {
  318     char *ptr;
  319     char *p;
  320     char moderated[PATH_LEN];
  321     int window = 0;
  322     long processed = 0L;
  323     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  324     static char ngname[NNTP_GRPLEN + 1]; /* RFC 3977 3.1 limits group names to 497 octets */
  325     struct t_group *grpptr;
  326 #ifdef NNTP_ABLE
  327     t_bool need_auth = FALSE;
  328     char *ngnames[NUM_SIMULTANEOUS_GROUP_COMMAND];
  329     int index_i = 0;
  330     int index_o = 0;
  331 #endif /* NNTP_ABLE */
  332 
  333     rewind(fp);
  334 
  335     if (!batch_mode || verbose)
  336         wait_message(0, _(txt_reading_news_newsrc_file));
  337 
  338     while ((ptr = tin_fgets(fp, FALSE)) != NULL || window != 0) {
  339         if (ptr) {
  340             p = strpbrk(ptr, ":!");
  341 
  342             if (!p || *p != SUBSCRIBED) /* Invalid line or unsubscribed */
  343                 continue;
  344             *p = '\0';          /* Now ptr is the group name */
  345 
  346             /*
  347              * 128 should be enough for a groupname, >256 and we overflow buffers
  348              * later on
  349              * TODO: check RFCs for possible max. size
  350              */
  351             my_strncpy(ngname, ptr, 128);
  352             ptr = ngname;
  353         }
  354 
  355         if (read_news_via_nntp && !read_saved_news) {
  356 #ifdef NNTP_ABLE
  357             char buf[NNTP_STRLEN];
  358             char line[NNTP_STRLEN];
  359 
  360             if (window < NUM_SIMULTANEOUS_GROUP_COMMAND && ptr && (!list_active || (newsrc_active && list_active && group_find(ptr, FALSE)))) {
  361                 ngnames[index_i] = my_strdup(ptr);
  362                 snprintf(buf, sizeof(buf), "GROUP %s", ngnames[index_i]);
  363 #   ifdef DEBUG
  364                 if ((debug & DEBUG_NNTP) && verbose > 1)
  365                     debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  366 #   endif /* DEBUG */
  367                 put_server(buf);
  368                 index_i = (index_i + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  369                 window++;
  370             }
  371             if (window == NUM_SIMULTANEOUS_GROUP_COMMAND || ptr == NULL) {
  372                 int respcode = get_only_respcode(line, sizeof(line));
  373 
  374                 if (reconnected_in_last_get_server) {
  375                     /*
  376                      * If tin reconnected, last output is resended to server.
  377                      * So received data is for ngnames[last window_i].
  378                      * We resend all buffered command except for last window_i.
  379                      * And rotate buffer to use data received.
  380                      */
  381                     int i;
  382                     int j = index_o;
  383                     for (i = 0; i < window - 1; i++) {
  384                         snprintf(buf, sizeof(buf), "GROUP %s", ngnames[j]);
  385 #   ifdef DEBUG
  386                         if ((debug & DEBUG_NNTP) && verbose > 1)
  387                             debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  388 #   endif /* DEBUG */
  389                         put_server(buf);
  390                         j = (j + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  391                     }
  392                     if (--index_o < 0)
  393                         index_o = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  394                     if (--index_i < 0)
  395                         index_i = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  396                     if (index_i != index_o)
  397                         ngnames[index_o] = ngnames[index_i];
  398                 }
  399 
  400                 switch (respcode) {
  401 
  402                     case OK_GROUP:
  403                         {
  404                             char fmt[25];
  405 
  406                             snprintf(fmt, sizeof(fmt), "%%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%%ds", NNTP_GRPLEN);
  407                             if (sscanf(line, fmt, &count, &min, &max, ngname) != 4) {
  408 #   ifdef DEBUG
  409                                 if (debug & DEBUG_NNTP && verbose > 1)
  410                                     debug_print_file("NNTP", "Invalid response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  411 #   endif /* DEBUG */
  412                             }
  413                             if (strcmp(ngname, ngnames[index_o]) != 0) {
  414 #   ifdef DEBUG
  415                                 if (debug & DEBUG_NNTP && verbose > 1)
  416                                     debug_print_file("NNTP", "Groupname mismatch in response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  417 #   endif /* DEBUG */
  418                             }
  419                             ptr = ngname;
  420                             free(ngnames[index_o]);
  421                             index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  422                             window--;
  423                             break;
  424                         }
  425 
  426                     case ERR_NOAUTH:
  427                     case NEED_AUTHINFO:
  428                         need_auth = TRUE; /* delay auth till end of loop */
  429                         /* keep lint quiet: */
  430                         /* FALLTHROUGH */
  431 
  432                     case ERR_NOGROUP:
  433                         free(ngnames[index_o]);
  434                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  435                         window--;
  436                         continue;
  437 
  438                     case ERR_ACCESS:
  439                         tin_done(NNTP_ERROR_EXIT, "%s", line);
  440                         /* keep lint quiet: */
  441                         /* FALLTHROUGH */
  442 
  443                     default:
  444 #   ifdef DEBUG
  445                         if ((debug & DEBUG_NNTP) && verbose > 1)
  446                             debug_print_file("NNTP", "NOT_OK %s", line);
  447 #   endif /* DEBUG */
  448                         free(ngnames[index_o]);
  449                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  450                         window--;
  451                         continue;
  452                 }
  453             } else
  454                 continue;
  455 #endif /* NNTP_ABLE */
  456         } else {
  457             if (group_get_art_info(spooldir, ptr, GROUP_TYPE_NEWS, &count, &max, &min))
  458                 continue;
  459         }
  460 
  461         strcpy(moderated, "y");
  462 
  463         if (++processed % 5 == 0)
  464             spin_cursor();
  465 
  466         /*
  467          * Load group into group hash table
  468          * NULL means group already present, so we just fixup the counters
  469          * This call may implicitly ++num_active
  470          */
  471         if ((grpptr = group_add(ptr)) == NULL) {
  472             t_bool changed = FALSE;
  473 
  474             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  475                 continue;
  476 
  477             if (max > grpptr->xmax) {
  478                 grpptr->xmax = max;
  479                 changed = TRUE;
  480             }
  481             if (min > grpptr->xmin) {
  482                 grpptr->xmin = min;
  483                 changed = TRUE;
  484             }
  485             if (changed) {
  486                 grpptr->count = count;
  487                 expand_bitmap(grpptr, 0); /* TODO: expand_bitmap(grpptr,grpptr->xmin) should be enough */
  488             }
  489             continue;
  490         }
  491 
  492         /*
  493          * Load the new group in active[]
  494          */
  495         active_add(grpptr, count, max, min, moderated);
  496     }
  497 #ifdef NNTP_ABLE
  498     return need_auth;
  499 #endif /* NNTP_ABLE */
  500 }
  501 
  502 
  503 /*
  504  * Wrapper for do_read_newsrc_active_file() to handle
  505  * missing authentication
  506  */
  507 static void
  508 read_newsrc_active_file(
  509     void)
  510 {
  511     FILE *fp;
  512 #ifdef NNTP_ABLE
  513     t_bool need_auth;
  514 #endif /* NNTP_ABLE */
  515 
  516     /*
  517      * return immediately if no .newsrc can be found or .newsrc is empty
  518      * when function asked to use .newsrc
  519      */
  520     if ((fp = fopen(newsrc, "r")) == NULL)
  521         return;
  522 
  523     if (file_size(newsrc) <= 0L) {
  524         fclose(fp);
  525         return;
  526     }
  527 
  528 #ifdef NNTP_ABLE
  529     need_auth = do_read_newsrc_active_file(fp);
  530 #else
  531     do_read_newsrc_active_file(fp);
  532 #endif /* NNTP_ABLE */
  533 
  534 #ifdef NNTP_ABLE
  535     if (need_auth) { /* delayed auth */
  536         if (!authenticate(nntp_server, userid, FALSE) || do_read_newsrc_active_file(fp)) {
  537             tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
  538         }
  539     }
  540 #endif /* NNTP_ABLE */
  541 
  542     fclose(fp);
  543 
  544     /*
  545      * Exit if active file wasn't read correctly or is empty
  546      */
  547     if (tin_errno || !num_active) {
  548         if (newsrc_active && !num_active)
  549             tin_done(EXIT_FAILURE, _(txt_error_server_has_no_listed_groups), newsrc);
  550         else
  551             tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
  552     }
  553 
  554     if (!batch_mode || verbose)
  555         my_fputc('\n', stdout);
  556 }
  557 
  558 
  559 /*
  560  * Open the news active file locally or send the LIST command
  561  */
  562 static FILE *
  563 open_news_active_fp(
  564     void)
  565 {
  566 #ifdef NNTP_ABLE
  567     if (read_news_via_nntp && !read_saved_news)
  568         return (nntp_command("LIST", OK_GROUPS, NULL, 0));
  569 #endif /* NNTP_ABLE */
  570     return (fopen(news_active_file, "r"));
  571 }
  572 
  573 
  574 /*
  575  * Load the active file into active[]
  576  */
  577 static void
  578 read_active_file(
  579     void)
  580 {
  581     FILE *fp;
  582     char *ptr;
  583     char moderated[PATH_LEN];
  584     long processed = 0L;
  585     struct t_group *grpptr;
  586     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  587 
  588     if (!batch_mode || verbose)
  589         wait_message(0, _(txt_reading_news_active_file));
  590 
  591     if ((fp = open_news_active_fp()) == NULL) {
  592         if (cmd_line && !batch_mode)
  593             my_fputc('\n', stderr);
  594 
  595 #ifdef NNTP_ABLE
  596         if (read_news_via_nntp)
  597             tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  598 #   ifndef NNTP_ONLY
  599         else
  600             tin_done(EXIT_FAILURE, _(txt_cannot_open_active_file), news_active_file, tin_progname);
  601 #   endif /* !NNTP_ONLY */
  602 #else
  603         tin_done(EXIT_FAILURE, _(txt_cannot_open), news_active_file);
  604 #endif /* NNTP_ABLE */
  605     }
  606 
  607     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  608 #if defined(DEBUG) && defined(NNTP_ABLE)
  609         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  610             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  611 #endif /* DEBUG && NNTP_ABLE */
  612 
  613         if (!parse_active_line(ptr, &max, &min, moderated))
  614             continue;
  615 
  616         if (++processed % MODULO_COUNT_NUM == 0)
  617             spin_cursor();
  618 
  619         /*
  620          * Load group into group hash table
  621          * NULL means group already present, so we just fixup the counters
  622          * This call may implicitly ++num_active
  623          */
  624         if ((grpptr = group_add(ptr)) == NULL) {
  625             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  626                 continue;
  627 
  628             if (max > grpptr->xmax) {
  629                 grpptr->xmax = max;
  630                 grpptr->count = count;
  631             }
  632 
  633             if (min > grpptr->xmin) {
  634                 grpptr->xmin = min;
  635                 grpptr->count = count;
  636             }
  637 
  638             continue;
  639         }
  640 
  641         /*
  642          * Load the new group in active[]
  643          */
  644         active_add(grpptr, count, max, min, moderated);
  645     }
  646 
  647     TIN_FCLOSE(fp);
  648 
  649     /*
  650      * Exit if active file wasn't read correctly or is empty
  651      */
  652     if (tin_errno || !num_active)
  653         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), (read_news_via_nntp ? (read_saved_news ? news_active_file : _(txt_servers_active)) : news_active_file));
  654 
  655     if (!batch_mode || verbose)
  656         my_fputc('\n', stdout);
  657 }
  658 
  659 
  660 #ifdef NNTP_ABLE
  661 /*
  662  * Load the active file into active[] via LIST COUNTS
  663  */
  664 static void
  665 read_active_counts(
  666     void)
  667 {
  668     FILE *fp;
  669     char *ptr;
  670     char moderated[PATH_LEN];
  671     long processed = 0L;
  672     struct t_group *grpptr;
  673     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  674 
  675     if (!batch_mode || verbose)
  676         wait_message(0, _(txt_reading_news_active_file));
  677 
  678     if ((fp = nntp_command("LIST COUNTS", OK_GROUPS, NULL, 0)) == NULL) {
  679         if (cmd_line && !batch_mode)
  680             my_fputc('\n', stderr);
  681 
  682         tin_done(EXIT_FAILURE,_(txt_cannot_retrieve), ACTIVE_FILE);
  683     }
  684 
  685     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  686 #   ifdef DEBUG
  687         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  688             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  689 #   endif /* DEBUG */
  690 
  691         if (!parse_count_line(ptr, &max, &min, &count, moderated))
  692             continue;
  693 
  694         if (++processed % MODULO_COUNT_NUM == 0)
  695             spin_cursor();
  696 
  697         /*
  698          * Load group into group hash table
  699          * NULL means group already present, so we just fixup the counters
  700          * This call may implicitly ++num_active
  701          */
  702         if ((grpptr = group_add(ptr)) == NULL) {
  703             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  704                 continue;
  705 
  706             if (max > grpptr->xmax) {
  707                 grpptr->xmax = max;
  708                 grpptr->count = count;
  709             }
  710 
  711             if (min > grpptr->xmin) {
  712                 grpptr->xmin = min;
  713                 grpptr->count = count;
  714             }
  715 
  716             continue;
  717         }
  718 
  719         /*
  720          * Load the new group in active[]
  721          */
  722         active_add(grpptr, count, max, min, moderated);
  723     }
  724 
  725     /*
  726      * Exit if active file wasn't read correctly or is empty
  727      */
  728     if (tin_errno || !num_active)
  729         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), _(txt_servers_active));
  730 
  731     if (!batch_mode || verbose)
  732         my_fputc('\n', stdout);
  733 }
  734 #endif /* NNTP_ABLE */
  735 
  736 
  737 /*
  738  * Load the active file into active[]
  739  * Check and preload any new newgroups into my_group[]
  740  */
  741 int
  742 read_news_active_file(
  743     void)
  744 {
  745     FILE *fp;
  746     int newgrps = 0;
  747     t_bool do_group_cmds = !nntp_caps.list_counts;
  748 #ifdef NNTP_ABLE
  749     t_bool did_list_cmd = FALSE;
  750 #endif /* NNTP_ABLE */
  751 
  752     /*
  753      * Ignore -n if no .newsrc can be found or .newsrc is empty
  754      */
  755     if (newsrc_active) {
  756         if ((fp = fopen(newsrc, "r")) == NULL) {
  757             list_active = TRUE;
  758             newsrc_active = FALSE;
  759         } else {
  760             fclose(fp);
  761             if (file_size(newsrc) <= 0L) {
  762                 list_active = TRUE;
  763                 newsrc_active = FALSE;
  764             }
  765         }
  766     }
  767 
  768     /* Read an active file if it is allowed */
  769     if (list_active) {
  770 #ifdef NNTP_ABLE
  771         did_list_cmd = TRUE;
  772         if (read_news_via_nntp && nntp_caps.list_counts)
  773             read_active_counts();
  774         else
  775 #endif /* NNTP_ABLE */
  776             read_active_file();
  777     }
  778 
  779     /* Read .newsrc and check each group */
  780     if (newsrc_active) {
  781 #ifdef NNTP_ABLE
  782 #   ifndef DISABLE_PIPELINING
  783         /*
  784          * prefer LIST COUNTS, otherwise use LIST ACTIVE (-l) or GROUP (-n)
  785          * or both (-ln); LIST COUNTS/ACTIVE grplist is used up to
  786          * PIPELINE_LIMIT groups in newsrc
  787          */
  788         if (read_news_via_nntp && (list_active || nntp_caps.list_counts) && !did_list_cmd) {
  789             char buff[NNTP_STRLEN];
  790             char *ptr, *q;
  791             char moderated[PATH_LEN];
  792             int r = 0, j = 0;
  793             int i;
  794             struct t_group *grpptr;
  795             t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  796             t_bool need_auth = FALSE;
  797 
  798             *buff = '\0';
  799             /* we can't use for_each_group(i) yet, so we have to parse the newsrc */
  800             if ((fp = fopen(newsrc, "r")) != NULL) {
  801                 while (tin_fgets(fp, FALSE) != NULL)
  802                     j++;
  803                 rewind(fp);
  804                 if (j < PIPELINE_LIMIT) {
  805                     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  806                         if (!(q = strpbrk(ptr, ":!")))
  807                             continue;
  808                         *q = '\0';
  809                         if (nntp_caps.type == CAPABILITIES && (nntp_caps.list_active || nntp_caps.list_counts)) {
  810                             /* LIST ACTIVE or LIST COUNTS takes wildmats */
  811                             if (*buff && ((strlen(buff) + strlen(ptr)) < (NNTP_GRPLEN - 1))) { /* append group name */
  812                                 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", ptr);
  813                             } else {
  814                                 if (*buff) {
  815                                     put_server(buff);
  816                                     r++;
  817                                 }
  818                                 snprintf(buff, sizeof(buff), "LIST %s %s", nntp_caps.list_counts ? "COUNTS" : "ACTIVE", ptr);
  819                             }
  820                             continue;
  821                         } else
  822                             snprintf(buff, sizeof(buff), "LIST ACTIVE %s", ptr);
  823                         put_server(buff);
  824                         r++;
  825                         *buff = '\0';
  826                     }
  827                     if (*buff) {
  828                         put_server(buff);
  829                         r++;
  830                     }
  831                 } else {
  832                     do_group_cmds = TRUE;
  833                 }
  834                 fclose(fp);
  835 
  836                 if (j < PIPELINE_LIMIT) {
  837                     for (i = 0; i < r && !did_reconnect; i++) {
  838                         if ((j = get_only_respcode(buff, sizeof(buff))) != OK_GROUPS) {
  839                             /* TODO: add 483 (RFC 3977) code */
  840                             if (j == ERR_NOAUTH || j == NEED_AUTHINFO)
  841                                 need_auth = TRUE;
  842 #if 0 /* do we need something like this? */
  843                             if (j == ERR_CMDSYN)
  844                                 list_active = TRUE;
  845 #endif /* 0 */
  846                             continue;
  847                         } else {
  848                             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  849 #       ifdef DEBUG
  850                                 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  851                                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  852 #       endif /* DEBUG */
  853                                 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_counts) {
  854                                     if (!parse_count_line(ptr, &max, &min, &count, moderated))
  855                                         continue;
  856                                 } else {
  857                                     if (!parse_active_line(ptr, &max, &min, moderated))
  858                                         continue;
  859                                 }
  860 
  861                                 if ((grpptr = group_add(ptr)) == NULL) {
  862                                     if ((grpptr = group_find(ptr, FALSE)) == NULL)
  863                                         continue;
  864 
  865                                     if (max > grpptr->xmax) {
  866                                         grpptr->xmax = max;
  867                                         grpptr->count = count;
  868                                     }
  869                                     if (min > grpptr->xmin) {
  870                                         grpptr->xmin = min;
  871                                         grpptr->count = count;
  872                                     }
  873                                     continue;
  874                                 }
  875                                 active_add(grpptr, count, max, min, moderated);
  876                             }
  877                         }
  878                     }
  879                     if (need_auth) { /* retry after auth is overkill here, so just auth */
  880                         if (!authenticate(nntp_server, userid, FALSE))
  881                             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
  882                     }
  883                 }
  884                 did_reconnect = FALSE;
  885             }
  886         }
  887 #   endif /* !DISABLE_PIPELINING */
  888 #endif /* NNTP_ABLE */
  889         if (!nntp_caps.list_counts || do_group_cmds)
  890             read_newsrc_active_file();
  891     }
  892 
  893     (void) time(&active_timestamp);
  894     force_reread_active_file = FALSE;
  895 
  896     /*
  897      * check_for_any_new_groups() also does $AUTOSUBSCRIBE
  898      */
  899     if (check_for_new_newsgroups)
  900         newgrps = check_for_any_new_groups();
  901 
  902     /*
  903      * finally we have a list of all groups and can set the attributes
  904      */
  905     assign_attributes_to_groups();
  906 
  907     return newgrps;
  908 }
  909 
  910 
  911 /*
  912  * Open the active.times file locally or send the NEWGROUPS command
  913  * "NEWGROUPS yymmdd hhmmss"
  914  */
  915 static FILE *
  916 open_newgroups_fp(
  917     int idx)
  918 {
  919 #ifdef NNTP_ABLE
  920     char line[NNTP_STRLEN];
  921     struct tm *ngtm;
  922 
  923     if (read_news_via_nntp && !read_saved_news) {
  924         /*
  925          * not checking for caps_type == CAPABILITIES && reader as some
  926          * servers do not support it even if advertizing READER so we must
  927          * handle errors anyway and just issue the cmd.
  928          */
  929         if (idx == -1 || ((ngtm = localtime(&newnews[idx].time)) == NULL))
  930             return (FILE *) 0;
  931 
  932         /*
  933          * RFC 3077 states that we SHOULD use 4 digit year but some servers
  934          * still do not support it.
  935          */
  936         snprintf(line, sizeof(line), "NEWGROUPS %02d%02d%02d %02d%02d%02d",
  937             ngtm->tm_year % 100, ngtm->tm_mon + 1, ngtm->tm_mday,
  938             ngtm->tm_hour, ngtm->tm_min, ngtm->tm_sec);
  939 
  940         return (nntp_command(line, OK_NEWGROUPS, NULL, 0));
  941     }
  942 #endif /* NNTP_ABLE */
  943     return (fopen(active_times_file, "r"));
  944 }
  945 
  946 
  947 /*
  948  * Check for any newly created newsgroups.
  949  *
  950  * If reading news locally check the NEWSLIBDIR/active.times file.
  951  * Format:   Groupname Seconds Creator
  952  *
  953  * If reading news via NNTP issue a NEWGROUPS command.
  954  * Format:   (as active file) Groupname Maxart Minart moderated
  955  */
  956 static int
  957 check_for_any_new_groups(
  958     void)
  959 {
  960     FILE *fp;
  961     char *autosubscribe, *autounsubscribe;
  962     char *ptr, *line, buf[NNTP_STRLEN];
  963     char old_newnews_host[PATH_LEN];
  964     int newnews_index;
  965     int newgrps = 0;
  966     time_t old_newnews_time;
  967     time_t new_newnews_time;
  968 
  969     if (!batch_mode /* || verbose */)
  970         wait_message(0, _(txt_checking_new_groups));
  971 
  972     (void) time(&new_newnews_time);
  973 
  974     /*
  975      * find out if we have read news from here before otherwise -1
  976      */
  977     if ((newnews_index = find_newnews_index(nntp_server)) >= 0) {
  978         STRCPY(old_newnews_host, newnews[newnews_index].host);
  979         old_newnews_time = newnews[newnews_index].time;
  980     } else {
  981         STRCPY(old_newnews_host, "UNKNOWN");
  982         old_newnews_time = (time_t) 0;
  983     }
  984 
  985 #ifdef DEBUG
  986     if ((debug & DEBUG_NNTP) && verbose > 1)
  987         debug_print_file("NNTP", "Newnews old=[%lu]  new=[%lu]", (unsigned long int) old_newnews_time, (unsigned long int) new_newnews_time);
  988 #endif /* DEBUG */
  989 
  990     if ((fp = open_newgroups_fp(newnews_index)) != NULL) {
  991         /*
  992          * Need these later. They list user-defined groups to be
  993          * automatically subscribed or unsubscribed.
  994          */
  995         autosubscribe = getenv("AUTOSUBSCRIBE");
  996         autounsubscribe = getenv("AUTOUNSUBSCRIBE");
  997 
  998         while ((line = tin_fgets(fp, FALSE)) != NULL) {
  999             /*
 1000              * Split the group name off and subscribe. If we're reading local,
 1001              * we must check the creation date manually
 1002              */
 1003             if ((ptr = strchr(line, ' ')) != NULL) {
 1004                 if (!read_news_via_nntp && ((time_t) atol(ptr) < old_newnews_time || old_newnews_time == (time_t) 0))
 1005                     continue;
 1006 
 1007                 *ptr = '\0';
 1008             }
 1009             subscribe_new_group(line, autosubscribe, autounsubscribe);
 1010             newgrps++;
 1011         }
 1012         TIN_FCLOSE(fp);
 1013 
 1014         if (tin_errno)
 1015             return 0;               /* Don't update the time if we quit */
 1016     }
 1017 
 1018     /*
 1019      * Update (if already existing) or create (if new) the in-memory
 1020      * 'last time newgroups checked' slot for this server. It will be written
 1021      * out as part of tinrc.
 1022      */
 1023     if (newnews_index >= 0)
 1024         newnews[newnews_index].time = new_newnews_time;
 1025     else {
 1026         snprintf(buf, sizeof(buf), "%s %lu", nntp_server, (unsigned long int) new_newnews_time);
 1027         load_newnews_info(buf);
 1028     }
 1029 
 1030     if (!batch_mode)
 1031         my_fputc('\n', stdout);
 1032 
 1033     return newgrps;
 1034 }
 1035 
 1036 
 1037 /*
 1038  * Subscribe to a new news group:
 1039  * Handle the AUTOSUBSCRIBE/AUTOUNSUBSCRIBE env vars
 1040  * They hold a wildcard list of groups that should be automatically
 1041  * (un)subscribed when a new group is found
 1042  * If a group is autounsubscribed, completely ignore it
 1043  * If a group is autosubscribed, subscribe to it
 1044  * Otherwise, mark it as New for inclusion in selection screen
 1045  */
 1046 static void
 1047 subscribe_new_group(
 1048     char *group,
 1049     char *autosubscribe,
 1050     char *autounsubscribe)
 1051 {
 1052     int idx;
 1053     struct t_group *ptr;
 1054 
 1055     /*
 1056      * If we explicitly don't auto subscribe to this group, then don't bother going on
 1057      */
 1058     if ((autounsubscribe != NULL) && match_group_list(group, autounsubscribe))
 1059         return;
 1060 
 1061     /*
 1062      * Try to add the group to our selection list. If this fails, we're
 1063      * probably using -n, so we fake an entry with no counts. The count will
 1064      * be properly updated when we enter the group. Otherwise there is some
 1065      * mismatch in the active.times data and we ignore the newgroup.
 1066      */
 1067     if ((idx = my_group_add(group, FALSE)) < 0) {
 1068         if (list_active) {
 1069 /*          my_fprintf(stderr, "subscribe_new_group: %s not in active[] && list_active\n", group); */
 1070             return;
 1071         }
 1072 
 1073         if ((ptr = group_add(group)) != NULL)
 1074             active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "y");
 1075 
 1076         if ((idx = my_group_add(group, FALSE)) < 0)
 1077             return;
 1078     }
 1079 
 1080     if (!no_write && (autosubscribe != NULL) && match_group_list(group, autosubscribe)) {
 1081         if (!batch_mode || verbose)
 1082             my_printf(_(txt_autosubscribed), group);
 1083 
 1084         /*
 1085          * as subscribe_new_group() is called from check_for_any_new_groups()
 1086          * which has pending data on the socket if reading via NNTP we are not
 1087          * allowed to issue any NNTP commands yet
 1088          */
 1089         subscribe(&active[my_group[idx]], SUBSCRIBED, bool_not(read_news_via_nntp));
 1090         /*
 1091          * Bad kluge to stop group later appearing in New newsgroups. This
 1092          * effectively loses the group, and it has now been subscribed to and
 1093          * so will be reread later by read_newsrc()
 1094          */
 1095         selmenu.max--;
 1096     } else
 1097         active[my_group[idx]].newgroup = TRUE;
 1098 }
 1099 
 1100 
 1101 /*
 1102  * See if group is a member of group_list, returning a boolean.
 1103  * group_list is a comma separated list of newsgroups, ! implies NOT
 1104  * The same degree of wildcarding as used elsewhere in tin is allowed
 1105  */
 1106 t_bool
 1107 match_group_list(
 1108     const char *group,
 1109     const char *group_list)
 1110 {
 1111     char *separator;
 1112     char pattern[HEADER_LEN];
 1113     size_t group_len, list_len;
 1114     t_bool negate, accept = FALSE;
 1115 
 1116     list_len = strlen(group_list);
 1117     /*
 1118      * walk through comma-separated entries in list
 1119      */
 1120     while (list_len != 0) {
 1121         /*
 1122          * find end/length of this entry
 1123          */
 1124         separator = strchr(group_list, ',');
 1125         group_len = MIN(((separator == NULL) ? list_len : (size_t) (separator - group_list)), sizeof(pattern) - 1);
 1126 
 1127         if ((negate = (*group_list == '!'))) {
 1128             /*
 1129              * a '!' before the pattern inverts sense of match
 1130              */
 1131             group_list++;
 1132             group_len--;
 1133             list_len--;
 1134         }
 1135         /*
 1136          * copy out the entry and terminate it properly
 1137          */
 1138         strncpy(pattern, group_list, group_len);
 1139         pattern[group_len] = '\0';
 1140         /*
 1141          * case-insensitive wildcard match
 1142          */
 1143         if (GROUP_MATCH(group, pattern, TRUE))
 1144             accept = bool_not(negate);  /* matched! */
 1145 
 1146         /*
 1147          * now examine next entry if any
 1148          */
 1149         if (group_list[group_len] != '\0')
 1150             group_len++;    /* skip the separator */
 1151 
 1152         group_list += group_len;
 1153         list_len -= group_len;
 1154     }
 1155     return accept;
 1156 }
 1157 
 1158 
 1159 /*
 1160  * Add or update an entry to the in-memory newnews[] array (The times newgroups
 1161  * were last checked for a particular news server)
 1162  * If this is first time we've been called, zero out the array.
 1163  *
 1164  * Side effects:
 1165  *   'info' is modified. Caller should not depend on it.
 1166  */
 1167 void
 1168 load_newnews_info(
 1169     char *info)
 1170 {
 1171     char *ptr;
 1172     int i;
 1173     time_t new_time;
 1174 
 1175     /*
 1176      * initialize newnews[] if no entries
 1177      */
 1178     if (!num_newnews) {
 1179         for (i = 0; i < max_newnews; i++) {
 1180             newnews[i].host = NULL;
 1181             newnews[i].time = (time_t) 0;
 1182         }
 1183     }
 1184 
 1185     /*
 1186      * Split 'info' into hostname and time
 1187      */
 1188     if ((ptr = strchr(info, ' ')) == NULL)
 1189         return;
 1190 
 1191     *ptr++ = '\0';
 1192     new_time = (time_t) atol(ptr);
 1193 
 1194     /*
 1195      * If this is a new host entry, set it up
 1196      */
 1197     if ((i = find_newnews_index(info)) == -1) {
 1198         i = num_newnews++;
 1199 
 1200         if (i >= max_newnews)
 1201             expand_newnews();
 1202         newnews[i].host = my_strdup(info);
 1203     }
 1204 
 1205     newnews[i].time = new_time;
 1206 
 1207 #ifdef DEBUG
 1208     if ((debug & DEBUG_NNTP) && verbose > 1)
 1209         debug_print_file("NNTP", "ACTIVE host=[%s] time=[%lu]", newnews[i].host, (unsigned long int) newnews[i].time);
 1210 #endif /* DEBUG */
 1211 }
 1212 
 1213 
 1214 /*
 1215  * Return the index of cur_newnews_host in newnews[] or -1 if not found
 1216  */
 1217 int
 1218 find_newnews_index(
 1219     const char *cur_newnews_host)
 1220 {
 1221     int i;
 1222 
 1223     for (i = 0; i < num_newnews; i++) {
 1224         if (STRCMPEQ(cur_newnews_host, newnews[i].host))
 1225             return i;
 1226     }
 1227 
 1228     return -1;
 1229 }
 1230 
 1231 
 1232 /*
 1233  * Get a single status char from the moderated field. Used on selection screen
 1234  * and in header of group screen
 1235  */
 1236 char
 1237 group_flag(
 1238     char ch)
 1239 {
 1240     switch (ch) {
 1241         case 'm':
 1242             return 'M';
 1243 
 1244         case 'x':
 1245         case 'n':
 1246         case 'j':
 1247             return 'X';
 1248 
 1249         case '=':
 1250             return '=';
 1251 
 1252         default:
 1253             return ' ';
 1254     }
 1255 }
 1256 
 1257 
 1258 /* ex actived.c functions */
 1259 void
 1260 create_save_active_file(
 1261     void)
 1262 {
 1263     char *fb;
 1264     char group_path[PATH_LEN];
 1265     char local_save_active_file[PATH_LEN];
 1266 
 1267     joinpath(local_save_active_file, sizeof(local_save_active_file), rcdir, ACTIVE_SAVE_FILE);
 1268 
 1269     if (no_write && file_size(local_save_active_file) != -1L)
 1270         return;
 1271 
 1272     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, group_path, sizeof(group_path), NULL, FALSE)) {
 1273         wait_message(0, _(txt_creating_active));
 1274         print_active_head(local_save_active_file);
 1275 
 1276         while (strlen(group_path) && group_path[strlen(group_path) - 1] == '/')
 1277             group_path[strlen(group_path) - 1] = '\0';
 1278 
 1279         fb = my_strdup(group_path);
 1280         make_group_list(local_save_active_file, (cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, fb, group_path);
 1281         free(fb);
 1282     }
 1283 }
 1284 
 1285 
 1286 static void
 1287 make_group_list(
 1288     char *active_file,
 1289     char *base_dir,
 1290     char *fixed_base,
 1291     char *group_path)
 1292 {
 1293     DIR *dir;
 1294     DIR_BUF *direntry;
 1295     char *ptr;
 1296     char filename[PATH_LEN];
 1297     char path[PATH_LEN];
 1298     t_artnum art_max;
 1299     t_artnum art_min;
 1300     struct stat stat_info;
 1301     t_bool is_dir;
 1302 
 1303     if ((dir = opendir(group_path)) != NULL) {
 1304         is_dir = FALSE;
 1305         while ((direntry = readdir(dir)) != NULL) {
 1306             STRCPY(filename, direntry->d_name);
 1307             joinpath(path, sizeof(path), group_path, filename);
 1308             if (!(filename[0] == '.' && filename[1] == '\0') &&
 1309                 !(filename[0] == '.' && filename[1] == '.' && filename[2] == '\0')) {
 1310                 if (stat(path, &stat_info) != -1) {
 1311                     if (S_ISDIR(stat_info.st_mode))
 1312                         is_dir = TRUE;
 1313                 }
 1314             }
 1315             if (is_dir) {
 1316                 is_dir = FALSE;
 1317                 strcpy(group_path, path);
 1318 
 1319                 make_group_list(active_file, base_dir, fixed_base, group_path);
 1320                 find_art_max_min(group_path, &art_max, &art_min);
 1321                 append_group_line(active_file, group_path + strlen(fixed_base) + 1, art_max, art_min, fixed_base);
 1322                 if ((ptr = strrchr(group_path, '/')) != NULL) /* TODO: Unix'ism */
 1323                     *ptr = '\0';
 1324             }
 1325         }
 1326         CLOSEDIR(dir);
 1327     }
 1328 }
 1329 
 1330 
 1331 static void
 1332 append_group_line(
 1333     char *active_file,
 1334     char *group_path,
 1335     t_artnum art_max,
 1336     t_artnum art_min,
 1337     char *base_dir)
 1338 {
 1339     FILE *fp;
 1340     char *file_tmp;
 1341 
 1342     if (art_max == 0 && art_min == 1)
 1343         return;
 1344 
 1345     file_tmp = get_tmpfilename(active_file);
 1346 
 1347     if (!backup_file(active_file, file_tmp)) {
 1348         free(file_tmp);
 1349         return;
 1350     }
 1351 
 1352     if ((fp = fopen(active_file, "a+")) != NULL) {
 1353         char *ptr;
 1354         char *group_name;
 1355         int err;
 1356 
 1357         ptr = group_name = my_strdup(group_path);
 1358         ptr++;
 1359         while ((ptr = strchr(ptr, '/')) != NULL)
 1360             *ptr = '.';
 1361 
 1362         wait_message(0, "Appending=[%s %"T_ARTNUM_PFMT" %"T_ARTNUM_PFMT" %s]\n", group_name, art_max, art_min, base_dir);
 1363         print_group_line(fp, group_name, art_max, art_min, base_dir);
 1364         if ((err = ferror(fp)) || fclose(fp)) { /* TODO: issue warning? */
 1365             if (err) {
 1366                 clearerr(fp);
 1367                 fclose(fp);
 1368             }
 1369             err = rename(file_tmp, active_file);
 1370 #ifdef DEBUG
 1371             if ((debug & DEBUG_MISC) && err) /* TODO: is this the right debug-level? */
 1372                 perror_message(_(txt_rename_error), file_tmp, active_file);
 1373 #endif /* DEBUG */
 1374         }
 1375         free(group_name);
 1376     }
 1377     unlink(file_tmp);
 1378     free(file_tmp);
 1379 }