"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/active.c" (12 Oct 2016, 35442 Bytes) of package /linux/misc/tin-2.4.1.tar.gz:


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : active.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-02-16
    6  *  Updated   : 2016-10-10
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1992-2017 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_actve_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         if (debug & DEBUG_NNTP)
  238             debug_print_file("NNTP", txt_bad_active_file, line);
  239 #endif /* DEBUG */
  240         error_message(2, _(txt_bad_active_file), line);
  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)
  285             debug_print_file("NNTP", _(txt_unparseable_counts), line);
  286 #   endif /* DEBUG */
  287         error_message(2, _(txt_unparseable_counts), line);
  288         return FALSE;
  289     }
  290 
  291     *max = atoartnum(p);
  292     *min = atoartnum(q);
  293     *count = atoartnum(r);
  294     strcpy(moderated, s);
  295 
  296     return TRUE;
  297 }
  298 #endif /* NNTP_ABLE */
  299 
  300 
  301 /*
  302  * Load the active information into active[] by counting the min/max/count
  303  * for each news group.
  304  * Parse a line from the .newsrc file
  305  * Send GROUP command to NNTP server directly to keep window.
  306  * We can't know the 'moderator' status and always return 'y'
  307  * But we don't change if the 'moderator' status is already checked by
  308  * read_active_file()
  309  * Returnes TRUE if NNTP is enabled and authentication is needed
  310  */
  311 #ifdef NNTP_ABLE
  312 static t_bool
  313 #else
  314 static void
  315 #endif /* NNTP_ABLE */
  316 do_read_newsrc_active_file(
  317     FILE *fp)
  318 {
  319     char *ptr;
  320     char *p;
  321     char moderated[PATH_LEN];
  322     int window = 0;
  323     long processed = 0L;
  324     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  325     static char ngname[NNTP_GRPLEN + 1]; /* RFC 3977 3.1 limits group names to 497 octets */
  326     struct t_group *grpptr;
  327 #ifdef NNTP_ABLE
  328     t_bool need_auth = FALSE;
  329     char *ngnames[NUM_SIMULTANEOUS_GROUP_COMMAND];
  330     int index_i = 0;
  331     int index_o = 0;
  332 #endif /* NNTP_ABLE */
  333 
  334 
  335     rewind(fp);
  336 
  337     if (!batch_mode || verbose)
  338         wait_message(0, _(txt_reading_news_newsrc_file));
  339 
  340     while ((ptr = tin_fgets(fp, FALSE)) != NULL || window != 0) {
  341         if (ptr) {
  342             p = strpbrk(ptr, ":!");
  343 
  344             if (!p || *p != SUBSCRIBED) /* Invalid line or unsubscribed */
  345                 continue;
  346             *p = '\0';          /* Now ptr is the group name */
  347 
  348             /*
  349              * 128 should be enough for a groupname, >256 and we overflow buffers
  350              * later on
  351              * TODO: check RFCs for possible max. size
  352              */
  353             my_strncpy(ngname, ptr, 128);
  354             ptr = ngname;
  355         }
  356 
  357         if (read_news_via_nntp && !read_saved_news) {
  358 #ifdef NNTP_ABLE
  359             char buf[NNTP_STRLEN];
  360             char line[NNTP_STRLEN];
  361 
  362             if (window < NUM_SIMULTANEOUS_GROUP_COMMAND && ptr && (!list_active || (newsrc_active && list_active && group_find(ptr, FALSE)))) {
  363                 ngnames[index_i] = my_strdup(ptr);
  364                 snprintf(buf, sizeof(buf), "GROUP %s", ngnames[index_i]);
  365 #   ifdef DEBUG
  366                 if (debug & DEBUG_NNTP)
  367                     debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  368 #   endif /* DEBUG */
  369                 put_server(buf);
  370                 index_i = (index_i + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  371                 window++;
  372             }
  373             if (window == NUM_SIMULTANEOUS_GROUP_COMMAND || ptr == NULL) {
  374                 int respcode = get_only_respcode(line, sizeof(line));
  375 
  376                 if (reconnected_in_last_get_server) {
  377                     /*
  378                      * If tin reconnected, last output is resended to server.
  379                      * So received data is for ngnames[last window_i].
  380                      * We resend all buffered command except for last window_i.
  381                      * And rotate buffer to use data received.
  382                      */
  383                     int i;
  384                     int j = index_o;
  385                     for (i = 0; i < window - 1; i++) {
  386                         snprintf(buf, sizeof(buf), "GROUP %s", ngnames[j]);
  387 #   ifdef DEBUG
  388                         if (debug & DEBUG_NNTP)
  389                             debug_print_file("NNTP", "read_newsrc_active_file() %s", buf);
  390 #   endif /* DEBUG */
  391                         put_server(buf);
  392                         j = (j + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  393                     }
  394                     if (--index_o < 0)
  395                         index_o = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  396                     if (--index_i < 0)
  397                         index_i = NUM_SIMULTANEOUS_GROUP_COMMAND - 1;
  398                     if (index_i != index_o)
  399                         ngnames[index_o] = ngnames[index_i];
  400                 }
  401 
  402                 switch (respcode) {
  403 
  404                     case OK_GROUP:
  405                         {
  406                             char fmt[25];
  407 
  408                             snprintf(fmt, sizeof(fmt), "%%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%"T_ARTNUM_SFMT" %%%ds", NNTP_GRPLEN);
  409                             if (sscanf(line, fmt, &count, &min, &max, ngname) != 4) {
  410                                 error_message(2, _(txt_error_invalid_response_to_group), line);
  411 #   ifdef DEBUG
  412                                 if (debug & DEBUG_NNTP) /* TODO: -> lang.c */
  413                                     debug_print_file("NNTP", "Invalid response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  414 #   endif /* DEBUG */
  415                             }
  416                             if (strcmp(ngname, ngnames[index_o]) != 0) {
  417                                 error_message(2, _(txt_error_wrong_newsgroupname_in_group_response), ngname, ngnames[index_o], line);
  418 #   ifdef DEBUG
  419                                 if (debug & DEBUG_NNTP) /* TODO: -> lang.c */
  420                                     debug_print_file("NNTP", "Groupname mismatch in response to \"GROUP %s\": \"%s\"", ngnames[index_o], line);
  421 #   endif /* DEBUG */
  422                             }
  423                             ptr = ngname;
  424                             free(ngnames[index_o]);
  425                             index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  426                             window--;
  427                             break;
  428                         }
  429 
  430                     case ERR_NOAUTH:
  431                     case NEED_AUTHINFO:
  432                         need_auth = TRUE; /* delay auth till end of loop */
  433                         /* keep lint quiet: */
  434                         /* FALLTHROUGH */
  435 
  436                     case ERR_NOGROUP:
  437                         free(ngnames[index_o]);
  438                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  439                         window--;
  440                         continue;
  441 
  442                     case ERR_ACCESS:
  443                         tin_done(NNTP_ERROR_EXIT, "%s", line);
  444                         /* keep lint quiet: */
  445                         /* FALLTHROUGH */
  446 
  447                     default:
  448 #   ifdef DEBUG
  449                         if (debug & DEBUG_NNTP)
  450                             debug_print_file("NNTP", "NOT_OK %s", line);
  451 #   endif /* DEBUG */
  452                         free(ngnames[index_o]);
  453                         index_o = (index_o + 1) % NUM_SIMULTANEOUS_GROUP_COMMAND;
  454                         window--;
  455                         continue;
  456                 }
  457             } else
  458                 continue;
  459 #endif /* NNTP_ABLE */
  460         } else {
  461             if (group_get_art_info(spooldir, ptr, GROUP_TYPE_NEWS, &count, &max, &min))
  462                 continue;
  463         }
  464 
  465         strcpy(moderated, "y");
  466 
  467         if (++processed % 5 == 0)
  468             spin_cursor();
  469 
  470         /*
  471          * Load group into group hash table
  472          * NULL means group already present, so we just fixup the counters
  473          * This call may implicitly ++num_active
  474          */
  475         if ((grpptr = group_add(ptr)) == NULL) {
  476             t_bool changed = FALSE;
  477 
  478             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  479                 continue;
  480 
  481             if (max > grpptr->xmax) {
  482                 grpptr->xmax = max;
  483                 changed = TRUE;
  484             }
  485             if (min > grpptr->xmin) {
  486                 grpptr->xmin = min;
  487                 changed = TRUE;
  488             }
  489             if (changed) {
  490                 grpptr->count = count;
  491                 expand_bitmap(grpptr, 0); /* TODO: expand_bitmap(grpptr,grpptr->xmin) should be enough */
  492             }
  493             continue;
  494         }
  495 
  496         /*
  497          * Load the new group in active[]
  498          */
  499         active_add(grpptr, count, max, min, moderated);
  500     }
  501 #ifdef NNTP_ABLE
  502     return need_auth;
  503 #endif /* NNTP_ABLE */
  504 }
  505 
  506 
  507 /*
  508  * Wrapper for do_read_newsrc_active_file() to handle
  509  * missing authentication
  510  */
  511 static void
  512 read_newsrc_active_file(
  513     void)
  514 {
  515     FILE *fp;
  516 #ifdef NNTP_ABLE
  517     t_bool need_auth;
  518 #endif /* NNTP_ABLE */
  519 
  520     /*
  521      * return immediately if no .newsrc can be found or .newsrc is empty
  522      * when function asked to use .newsrc
  523      */
  524     if ((fp = fopen(newsrc, "r")) == NULL)
  525         return;
  526 
  527     if (file_size(newsrc) <= 0L) {
  528         fclose(fp);
  529         return;
  530     }
  531 
  532 #ifdef NNTP_ABLE
  533     need_auth = do_read_newsrc_active_file(fp);
  534 #else
  535     do_read_newsrc_active_file(fp);
  536 #endif /* NNTP_ABLE */
  537 
  538 #ifdef NNTP_ABLE
  539     if (need_auth) { /* delayed auth */
  540         if (!authenticate(nntp_server, userid, FALSE) || do_read_newsrc_active_file(fp)) {
  541             tin_done(EXIT_FAILURE, _(txt_auth_failed), ERR_ACCESS);
  542         }
  543     }
  544 #endif /* NNTP_ABLE */
  545 
  546     fclose(fp);
  547 
  548     /*
  549      * Exit if active file wasn't read correctly or is empty
  550      */
  551     if (tin_errno || !num_active) {
  552         if (newsrc_active && !num_active)
  553             tin_done(EXIT_FAILURE, _(txt_error_server_has_no_listed_groups), newsrc);
  554         else
  555             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));
  556     }
  557 
  558     if (!batch_mode || verbose)
  559         my_fputc('\n', stdout);
  560 }
  561 
  562 
  563 /*
  564  * Open the news active file locally or send the LIST command
  565  */
  566 static FILE *
  567 open_news_active_fp(
  568     void)
  569 {
  570 #ifdef NNTP_ABLE
  571     if (read_news_via_nntp && !read_saved_news)
  572         return (nntp_command("LIST", OK_GROUPS, NULL, 0));
  573 #endif /* NNTP_ABLE */
  574     return (fopen(news_active_file, "r"));
  575 }
  576 
  577 
  578 /*
  579  * Load the active file into active[]
  580  */
  581 static void
  582 read_active_file(
  583     void)
  584 {
  585     FILE *fp;
  586     char *ptr;
  587     char moderated[PATH_LEN];
  588     long processed = 0L;
  589     struct t_group *grpptr;
  590     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  591 
  592     if (!batch_mode || verbose)
  593         wait_message(0, _(txt_reading_news_active_file));
  594 
  595     if ((fp = open_news_active_fp()) == NULL) {
  596         if (cmd_line && !batch_mode)
  597             my_fputc('\n', stderr);
  598 
  599 #ifdef NNTP_ABLE
  600         if (read_news_via_nntp)
  601             tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  602 #   ifndef NNTP_ONLY
  603         else
  604             tin_done(EXIT_FAILURE, _(txt_cannot_open_active_file), news_active_file, tin_progname);
  605 #   endif /* !NNTP_ONLY */
  606 #else
  607         tin_done(EXIT_FAILURE, _(txt_cannot_open), news_active_file);
  608 #endif /* NNTP_ABLE */
  609     }
  610 
  611     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  612 #if defined(DEBUG) && defined(NNTP_ABLE)
  613         if (debug & DEBUG_NNTP)
  614             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  615 #endif /* DEBUG && NNTP_ABLE */
  616 
  617         if (!parse_active_line(ptr, &max, &min, moderated))
  618             continue;
  619 
  620         if (++processed % MODULO_COUNT_NUM == 0)
  621             spin_cursor();
  622 
  623         /*
  624          * Load group into group hash table
  625          * NULL means group already present, so we just fixup the counters
  626          * This call may implicitly ++num_active
  627          */
  628         if ((grpptr = group_add(ptr)) == NULL) {
  629             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  630                 continue;
  631 
  632             if (max > grpptr->xmax) {
  633                 grpptr->xmax = max;
  634                 grpptr->count = count;
  635             }
  636 
  637             if (min > grpptr->xmin) {
  638                 grpptr->xmin = min;
  639                 grpptr->count = count;
  640             }
  641 
  642             continue;
  643         }
  644 
  645         /*
  646          * Load the new group in active[]
  647          */
  648         active_add(grpptr, count, max, min, moderated);
  649     }
  650 
  651     TIN_FCLOSE(fp);
  652 
  653     /*
  654      * Exit if active file wasn't read correctly or is empty
  655      */
  656     if (tin_errno || !num_active)
  657         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));
  658 
  659     if (!batch_mode || verbose)
  660         my_fputc('\n', stdout);
  661 }
  662 
  663 
  664 #ifdef NNTP_ABLE
  665 /*
  666  * Load the active file into active[] via LIST COUNTS
  667  */
  668 static void
  669 read_active_counts(
  670     void)
  671 {
  672     FILE *fp;
  673     char *ptr;
  674     char moderated[PATH_LEN];
  675     long processed = 0L;
  676     struct t_group *grpptr;
  677     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  678 
  679     if (!batch_mode || verbose)
  680         wait_message(0, _(txt_reading_news_active_file));
  681 
  682     if ((fp = nntp_command("LIST COUNTS", OK_GROUPS, NULL, 0)) == NULL) {
  683         if (cmd_line && !batch_mode)
  684             my_fputc('\n', stderr);
  685 
  686         tin_done(EXIT_FAILURE,_(txt_cannot_retrieve), ACTIVE_FILE);
  687     }
  688 
  689     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  690 #   ifdef DEBUG
  691         if (debug & DEBUG_NNTP)
  692             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  693 #   endif /* DEBUG */
  694 
  695         if (!parse_count_line(ptr, &max, &min, &count, moderated))
  696             continue;
  697 
  698         if (++processed % MODULO_COUNT_NUM == 0)
  699             spin_cursor();
  700 
  701         /*
  702          * Load group into group hash table
  703          * NULL means group already present, so we just fixup the counters
  704          * This call may implicitly ++num_active
  705          */
  706         if ((grpptr = group_add(ptr)) == NULL) {
  707             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  708                 continue;
  709 
  710             if (max > grpptr->xmax) {
  711                 grpptr->xmax = max;
  712                 grpptr->count = count;
  713             }
  714 
  715             if (min > grpptr->xmin) {
  716                 grpptr->xmin = min;
  717                 grpptr->count = count;
  718             }
  719 
  720             continue;
  721         }
  722 
  723         /*
  724          * Load the new group in active[]
  725          */
  726         active_add(grpptr, count, max, min, moderated);
  727     }
  728 
  729     /*
  730      * Exit if active file wasn't read correctly or is empty
  731      */
  732     if (tin_errno || !num_active)
  733         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), _(txt_servers_active));
  734 
  735     if (!batch_mode || verbose)
  736         my_fputc('\n', stdout);
  737 }
  738 #endif /* NNTP_ABLE */
  739 
  740 
  741 /*
  742  * Load the active file into active[]
  743  * Check and preload any new newgroups into my_group[]
  744  */
  745 int
  746 read_news_active_file(
  747     void)
  748 {
  749     FILE *fp;
  750     int newgrps = 0;
  751     t_bool do_group_cmds = !nntp_caps.list_counts;
  752 #ifdef NNTP_ABLE
  753     t_bool did_list_cmd = FALSE;
  754 #endif /* NNTP_ABLE */
  755 
  756     /*
  757      * Ignore -n if no .newsrc can be found or .newsrc is empty
  758      */
  759     if (newsrc_active) {
  760         if ((fp = fopen(newsrc, "r")) == NULL) {
  761             list_active = TRUE;
  762             newsrc_active = FALSE;
  763         } else {
  764             fclose(fp);
  765             if (file_size(newsrc) <= 0L) {
  766                 list_active = TRUE;
  767                 newsrc_active = FALSE;
  768             }
  769         }
  770     }
  771 
  772     /* Read an active file if it is allowed */
  773     if (list_active) {
  774 #ifdef NNTP_ABLE
  775         did_list_cmd = TRUE;
  776         if (read_news_via_nntp && nntp_caps.list_counts)
  777             read_active_counts();
  778         else
  779 #endif /* NNTP_ABLE */
  780             read_active_file();
  781     }
  782 
  783     /* Read .newsrc and check each group */
  784     if (newsrc_active) {
  785 #ifdef NNTP_ABLE
  786 #   ifndef DISABLE_PIPELINING
  787         /*
  788          * prefer LIST COUNTS, otherwise use LIST ACIVE (-l) or GROUP (-n)
  789          * or both (-ln); LIST COUNTS/ACTIVE grplist is used up to
  790          * PIPELINE_LIMIT groups in newsrc
  791          */
  792         if (read_news_via_nntp && (list_active || nntp_caps.list_counts) && !did_list_cmd) {
  793             char buff[NNTP_STRLEN];
  794             char *ptr, *q;
  795             char moderated[PATH_LEN];
  796             int r = 0, j = 0;
  797             int i;
  798             struct t_group *grpptr;
  799             t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  800             t_bool need_auth = FALSE;
  801 
  802             *buff = '\0';
  803             /* we can't use for_each_group(i) yet, so we have to prase the newsrc */
  804             if ((fp = fopen(newsrc, "r")) != NULL) {
  805                 while (tin_fgets(fp, FALSE) != NULL)
  806                     j++;
  807                 rewind(fp);
  808                 if (j < PIPELINE_LIMIT) {
  809                     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  810                         if (!(q = strpbrk(ptr, ":!")))
  811                             continue;
  812                         *q = '\0';
  813                         if (nntp_caps.type == CAPABILITIES && (nntp_caps.list_active || nntp_caps.list_counts)) {
  814                             /* LIST ACTIVE or LIST COUNTS takes wildmats */
  815                             if (*buff && ((strlen(buff) + strlen(ptr)) < (NNTP_GRPLEN - 1))) { /* append group name */
  816                                 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", ptr);
  817                             } else {
  818                                 if (*buff) {
  819                                     put_server(buff);
  820                                     r++;
  821                                 }
  822                                 snprintf(buff, sizeof(buff), "LIST %s %s", nntp_caps.list_counts ? "COUNTS" : "ACTIVE", ptr);
  823                             }
  824                             continue;
  825                         } else
  826                             snprintf(buff, sizeof(buff), "LIST ACTIVE %s", ptr);
  827                         put_server(buff);
  828                         r++;
  829                         *buff = '\0';
  830                     }
  831                     if (*buff) {
  832                         put_server(buff);
  833                         r++;
  834                     }
  835                 } else {
  836                     do_group_cmds = TRUE;
  837                 }
  838                 fclose(fp);
  839 
  840                 if (j < PIPELINE_LIMIT) {
  841                     for (i = 0; i < r && !did_reconnect; i++) {
  842                         if ((j = get_only_respcode(buff, sizeof(buff))) != OK_GROUPS) {
  843                             /* TODO: add 483 (RFC 3977) code */
  844                             if (j == ERR_NOAUTH || j == NEED_AUTHINFO)
  845                                 need_auth = TRUE;
  846 #if 0 /* do we need something like this? */
  847                             if (j == ERR_CMDSYN)
  848                                 list_active = TRUE;
  849 #endif /* 0 */
  850                             continue;
  851                         } else {
  852                             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  853 #       ifdef DEBUG
  854                                 if (debug & DEBUG_NNTP)
  855                                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  856 #       endif /* DEBUG */
  857                                 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_counts) {
  858                                     if (!parse_count_line(ptr, &max, &min, &count, moderated))
  859                                         continue;
  860                                 } else {
  861                                     if (!parse_active_line(ptr, &max, &min, moderated))
  862                                         continue;
  863                                 }
  864 
  865                                 if ((grpptr = group_add(ptr)) == NULL) {
  866                                     if ((grpptr = group_find(ptr, FALSE)) == NULL)
  867                                         continue;
  868 
  869                                     if (max > grpptr->xmax) {
  870                                         grpptr->xmax = max;
  871                                         grpptr->count = count;
  872                                     }
  873                                     if (min > grpptr->xmin) {
  874                                         grpptr->xmin = min;
  875                                         grpptr->count = count;
  876                                     }
  877                                     continue;
  878                                 }
  879                                 active_add(grpptr, count, max, min, moderated);
  880                             }
  881                         }
  882                     }
  883                     if (need_auth) { /* retry after auth is overkill here, so just auth */
  884                         if (!authenticate(nntp_server, userid, FALSE))
  885                             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
  886                     }
  887                 }
  888                 did_reconnect = FALSE;
  889             }
  890         }
  891 #   endif /* !DISABLE_PIPELINING */
  892 #endif /* NNTP_ABLE */
  893         if (!nntp_caps.list_counts || do_group_cmds)
  894             read_newsrc_active_file();
  895     }
  896 
  897     (void) time(&active_timestamp);
  898     force_reread_active_file = FALSE;
  899 
  900     /*
  901      * check_for_any_new_groups() also does $AUTOSUBSCRIBE
  902      */
  903     if (check_for_new_newsgroups)
  904         newgrps = check_for_any_new_groups();
  905 
  906     /*
  907      * finally we have a list of all groups an can set the attributes
  908      */
  909     assign_attributes_to_groups();
  910 
  911     return newgrps;
  912 }
  913 
  914 
  915 /*
  916  * Open the active.times file locally or send the NEWGROUPS command
  917  * "NEWGROUPS yymmdd hhmmss"
  918  */
  919 static FILE *
  920 open_newgroups_fp(
  921     int idx)
  922 {
  923 #ifdef NNTP_ABLE
  924     char line[NNTP_STRLEN];
  925     struct tm *ngtm;
  926 
  927     if (read_news_via_nntp && !read_saved_news) {
  928         /*
  929          * not checking for caps_type == CAPABILITIES && reader as some
  930          * servers do not support it even if advertizing READER so we must
  931          * handle errors anyway and just issue the cmd.
  932          */
  933         if (idx == -1 || ((ngtm = localtime(&newnews[idx].time)) == NULL))
  934             return (FILE *) 0;
  935 
  936         /*
  937          * RFC 3077 states that we SHOULD use 4 digit year but some servers
  938          * still do not support it.
  939          */
  940         snprintf(line, sizeof(line), "NEWGROUPS %02d%02d%02d %02d%02d%02d",
  941             ngtm->tm_year % 100, ngtm->tm_mon + 1, ngtm->tm_mday,
  942             ngtm->tm_hour, ngtm->tm_min, ngtm->tm_sec);
  943 
  944         return (nntp_command(line, OK_NEWGROUPS, NULL, 0));
  945     }
  946 #endif /* NNTP_ABLE */
  947     return (fopen(active_times_file, "r"));
  948 }
  949 
  950 
  951 /*
  952  * Check for any newly created newsgroups.
  953  *
  954  * If reading news locally check the NEWSLIBDIR/active.times file.
  955  * Format:   Groupname Seconds Creator
  956  *
  957  * If reading news via NNTP issue a NEWGROUPS command.
  958  * Format:   (as active file) Groupname Maxart Minart moderated
  959  */
  960 static int
  961 check_for_any_new_groups(
  962     void)
  963 {
  964     FILE *fp;
  965     char *autosubscribe, *autounsubscribe;
  966     char *ptr, *line, buf[NNTP_STRLEN];
  967     char old_newnews_host[PATH_LEN];
  968     int newnews_index;
  969     int newgrps = 0;
  970     time_t old_newnews_time;
  971     time_t new_newnews_time;
  972 
  973     if (!batch_mode /* || verbose */)
  974         wait_message(0, _(txt_checking_new_groups));
  975 
  976     (void) time(&new_newnews_time);
  977 
  978     /*
  979      * find out if we have read news from here before otherwise -1
  980      */
  981     if ((newnews_index = find_newnews_index(nntp_server)) >= 0) {
  982         STRCPY(old_newnews_host, newnews[newnews_index].host);
  983         old_newnews_time = newnews[newnews_index].time;
  984     } else {
  985         STRCPY(old_newnews_host, "UNKNOWN");
  986         old_newnews_time = (time_t) 0;
  987     }
  988 
  989 #ifdef DEBUG
  990     if (debug & DEBUG_NNTP)
  991         debug_print_file("NNTP", "Newnews old=[%lu]  new=[%lu]", (unsigned long int) old_newnews_time, (unsigned long int) new_newnews_time);
  992 #endif /* DEBUG */
  993 
  994     if ((fp = open_newgroups_fp(newnews_index)) != NULL) {
  995         /*
  996          * Need these later. They list user-defined groups to be
  997          * automatically subscribed or unsubscribed.
  998          */
  999         autosubscribe = getenv("AUTOSUBSCRIBE");
 1000         autounsubscribe = getenv("AUTOUNSUBSCRIBE");
 1001 
 1002         while ((line = tin_fgets(fp, FALSE)) != NULL) {
 1003             /*
 1004              * Split the group name off and subscribe. If we're reading local,
 1005              * we must check the creation date manually
 1006              */
 1007             if ((ptr = strchr(line, ' ')) != NULL) {
 1008                 if (!read_news_via_nntp && ((time_t) atol(ptr) < old_newnews_time || old_newnews_time == (time_t) 0))
 1009                     continue;
 1010 
 1011                 *ptr = '\0';
 1012             }
 1013             subscribe_new_group(line, autosubscribe, autounsubscribe);
 1014             newgrps++;
 1015         }
 1016         TIN_FCLOSE(fp);
 1017 
 1018         if (tin_errno)
 1019             return 0;               /* Don't update the time if we quit */
 1020     }
 1021 
 1022     /*
 1023      * Update (if already existing) or create (if new) the in-memory
 1024      * 'last time newgroups checked' slot for this server. It will be written
 1025      * out as part of tinrc.
 1026      */
 1027     if (newnews_index >= 0)
 1028         newnews[newnews_index].time = new_newnews_time;
 1029     else {
 1030         snprintf(buf, sizeof(buf), "%s %lu", nntp_server, (unsigned long int) new_newnews_time);
 1031         load_newnews_info(buf);
 1032     }
 1033 
 1034     if (!batch_mode)
 1035         my_fputc('\n', stdout);
 1036 
 1037     return newgrps;
 1038 }
 1039 
 1040 
 1041 /*
 1042  * Subscribe to a new news group:
 1043  * Handle the AUTOSUBSCRIBE/AUTOUNSUBSCRIBE env vars
 1044  * They hold a wildcard list of groups that should be automatically
 1045  * (un)subscribed when a new group is found
 1046  * If a group is autounsubscribed, completely ignore it
 1047  * If a group is autosubscribed, subscribe to it
 1048  * Otherwise, mark it as New for inclusion in selection screen
 1049  */
 1050 static void
 1051 subscribe_new_group(
 1052     char *group,
 1053     char *autosubscribe,
 1054     char *autounsubscribe)
 1055 {
 1056     int idx;
 1057     struct t_group *ptr;
 1058 
 1059     /*
 1060      * If we explicitly don't auto subscribe to this group, then don't bother going on
 1061      */
 1062     if ((autounsubscribe != NULL) && match_group_list(group, autounsubscribe))
 1063         return;
 1064 
 1065     /*
 1066      * Try to add the group to our selection list. If this fails, we're
 1067      * probably using -n, so we fake an entry with no counts. The count will
 1068      * be properly updated when we enter the group. Otherwise there is some
 1069      * mismatch in the active.times data and we ignore the newgroup.
 1070      */
 1071     if ((idx = my_group_add(group, FALSE)) < 0) {
 1072         if (list_active) {
 1073 /*          my_fprintf(stderr, "subscribe_new_group: %s not in active[] && list_active\n", group); */
 1074             return;
 1075         }
 1076 
 1077         if ((ptr = group_add(group)) != NULL)
 1078             active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "y");
 1079 
 1080         if ((idx = my_group_add(group, FALSE)) < 0)
 1081             return;
 1082     }
 1083 
 1084     if (!no_write && (autosubscribe != NULL) && match_group_list(group, autosubscribe)) {
 1085         if (!batch_mode || verbose)
 1086             my_printf(_(txt_autosubscribed), group);
 1087 
 1088         /*
 1089          * as subscribe_new_group() is called from check_for_any_new_groups()
 1090          * which has pending data on the socket if reading via NNTP we are not
 1091          * allowed to issue any NNTP comands yet
 1092          */
 1093         subscribe(&active[my_group[idx]], SUBSCRIBED, bool_not(read_news_via_nntp));
 1094         /*
 1095          * Bad kluge to stop group later appearing in New newsgroups. This
 1096          * effectively loses the group, and it has now been subscribed to and
 1097          * so will be reread later by read_newsrc()
 1098          */
 1099         selmenu.max--;
 1100     } else
 1101         active[my_group[idx]].newgroup = TRUE;
 1102 }
 1103 
 1104 
 1105 /*
 1106  * See if group is a member of group_list, returning a boolean.
 1107  * group_list is a comma separated list of newsgroups, ! implies NOT
 1108  * The same degree of wildcarding as used elsewhere in tin is allowed
 1109  */
 1110 t_bool
 1111 match_group_list(
 1112     const char *group,
 1113     const char *group_list)
 1114 {
 1115     char *separator;
 1116     char pattern[HEADER_LEN];
 1117     size_t group_len, list_len;
 1118     t_bool negate, accept = FALSE;
 1119 
 1120     list_len = strlen(group_list);
 1121     /*
 1122      * walk through comma-separated entries in list
 1123      */
 1124     while (list_len != 0) {
 1125         /*
 1126          * find end/length of this entry
 1127          */
 1128         separator = strchr(group_list, ',');
 1129         group_len = MIN(((separator == NULL) ? list_len : (size_t) (separator - group_list)), sizeof(pattern) - 1);
 1130 
 1131         if ((negate = ('!' == *group_list))) {
 1132             /*
 1133              * a '!' before the pattern inverts sense of match
 1134              */
 1135             group_list++;
 1136             group_len--;
 1137             list_len--;
 1138         }
 1139         /*
 1140          * copy out the entry and terminate it properly
 1141          */
 1142         strncpy(pattern, group_list, group_len);
 1143         pattern[group_len] = '\0';
 1144         /*
 1145          * case-insensitive wildcard match
 1146          */
 1147         if (GROUP_MATCH(group, pattern, TRUE))
 1148             accept = bool_not(negate);  /* matched! */
 1149 
 1150         /*
 1151          * now examine next entry if any
 1152          */
 1153         if ((char) 0 != group_list[group_len])
 1154             group_len++;    /* skip the separator */
 1155 
 1156         group_list += group_len;
 1157         list_len -= group_len;
 1158     }
 1159     return accept;
 1160 }
 1161 
 1162 
 1163 /*
 1164  * Add or update an entry to the in-memory newnews[] array (The times newgroups
 1165  * were last checked for a particular news server)
 1166  * If this is first time we've been called, zero out the array.
 1167  *
 1168  * Side effects:
 1169  *   'info' is modified. Caller should not depend on it.
 1170  */
 1171 void
 1172 load_newnews_info(
 1173     char *info)
 1174 {
 1175     char *ptr;
 1176     int i;
 1177     time_t new_time;
 1178 
 1179     /*
 1180      * initialize newnews[] if no entries
 1181      */
 1182     if (!num_newnews) {
 1183         for (i = 0; i < max_newnews; i++) {
 1184             newnews[i].host = NULL;
 1185             newnews[i].time = (time_t) 0;
 1186         }
 1187     }
 1188 
 1189     /*
 1190      * Split 'info' into hostname and time
 1191      */
 1192     if ((ptr = strchr(info, ' ')) == NULL)
 1193         return;
 1194 
 1195     *ptr++ = '\0';
 1196     new_time = (time_t) atol(ptr);
 1197 
 1198     /*
 1199      * If this is a new host entry, set it up
 1200      */
 1201     if ((i = find_newnews_index(info)) == -1) {
 1202         i = num_newnews++;
 1203 
 1204         if (i >= max_newnews)
 1205             expand_newnews();
 1206         newnews[i].host = my_strdup(info);
 1207     }
 1208 
 1209     newnews[i].time = new_time;
 1210 
 1211 #ifdef DEBUG
 1212     if (debug & DEBUG_NNTP)
 1213         debug_print_file("NNTP", "ACTIVE host=[%s] time=[%lu]", newnews[i].host, (unsigned long int) newnews[i].time);
 1214 #endif /* DEBUG */
 1215 }
 1216 
 1217 
 1218 /*
 1219  * Return the index of cur_newnews_host in newnews[] or -1 if not found
 1220  */
 1221 int
 1222 find_newnews_index(
 1223     const char *cur_newnews_host)
 1224 {
 1225     int i;
 1226 
 1227     for (i = 0; i < num_newnews; i++) {
 1228         if (STRCMPEQ(cur_newnews_host, newnews[i].host))
 1229             return i;
 1230     }
 1231 
 1232     return -1;
 1233 }
 1234 
 1235 
 1236 /*
 1237  * Get a single status char from the moderated field. Used on selection screen
 1238  * and in header of group screen
 1239  */
 1240 char
 1241 group_flag(
 1242     char ch)
 1243 {
 1244     switch (ch) {
 1245         case 'm':
 1246             return 'M';
 1247 
 1248         case 'x':
 1249         case 'n':
 1250         case 'j':
 1251             return 'X';
 1252 
 1253         case '=':
 1254             return '=';
 1255 
 1256         default:
 1257             return ' ';
 1258     }
 1259 }
 1260 
 1261 
 1262 /* ex actived.c functions */
 1263 void
 1264 create_save_active_file(
 1265     void)
 1266 {
 1267     char *fb;
 1268     char group_path[PATH_LEN];
 1269     char local_save_active_file[PATH_LEN];
 1270 
 1271     joinpath(local_save_active_file, sizeof(local_save_active_file), rcdir, ACTIVE_SAVE_FILE);
 1272 
 1273     if (no_write && file_size(local_save_active_file) != -1L)
 1274         return;
 1275 
 1276     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, group_path, sizeof(group_path), NULL, FALSE)) {
 1277         wait_message(0, _(txt_creating_active));
 1278         print_active_head(local_save_active_file);
 1279 
 1280         while (strlen(group_path) && group_path[strlen(group_path) - 1] == '/')
 1281             group_path[strlen(group_path) - 1] = '\0';
 1282 
 1283         fb = my_strdup(group_path);
 1284         make_group_list(local_save_active_file, (cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, fb, group_path);
 1285         free(fb);
 1286     }
 1287 }
 1288 
 1289 
 1290 static void
 1291 make_group_list(
 1292     char *active_file,
 1293     char *base_dir,
 1294     char *fixed_base,
 1295     char *group_path)
 1296 {
 1297     DIR *dir;
 1298     DIR_BUF *direntry;
 1299     char *ptr;
 1300     char filename[PATH_LEN];
 1301     char path[PATH_LEN];
 1302     t_artnum art_max;
 1303     t_artnum art_min;
 1304     struct stat stat_info;
 1305     t_bool is_dir;
 1306 
 1307     if ((dir = opendir(group_path)) != NULL) {
 1308         is_dir = FALSE;
 1309         while ((direntry = readdir(dir)) != NULL) {
 1310             STRCPY(filename, direntry->d_name);
 1311             joinpath(path, sizeof(path), group_path, filename);
 1312             if (!(filename[0] == '.' && filename[1] == '\0') &&
 1313                 !(filename[0] == '.' && filename[1] == '.' && filename[2] == '\0')) {
 1314                 if (stat(path, &stat_info) != -1) {
 1315                     if (S_ISDIR(stat_info.st_mode))
 1316                         is_dir = TRUE;
 1317                 }
 1318             }
 1319             if (is_dir) {
 1320                 is_dir = FALSE;
 1321                 strcpy(group_path, path);
 1322 
 1323                 make_group_list(active_file, base_dir, fixed_base, group_path);
 1324                 find_art_max_min(group_path, &art_max, &art_min);
 1325                 append_group_line(active_file, group_path + strlen(fixed_base) + 1, art_max, art_min, fixed_base);
 1326                 if ((ptr = strrchr(group_path, '/')) != NULL) /* TODO: Unix'ism */
 1327                     *ptr = '\0';
 1328             }
 1329         }
 1330         CLOSEDIR(dir);
 1331     }
 1332 }
 1333 
 1334 
 1335 static void
 1336 append_group_line(
 1337     char *active_file,
 1338     char *group_path,
 1339     t_artnum art_max,
 1340     t_artnum art_min,
 1341     char *base_dir)
 1342 {
 1343     FILE *fp;
 1344     char *file_tmp;
 1345 
 1346     if (art_max == 0 && art_min == 1)
 1347         return;
 1348 
 1349     file_tmp = get_tmpfilename(active_file);
 1350 
 1351     if (!backup_file(active_file, file_tmp)) {
 1352         free(file_tmp);
 1353         return;
 1354     }
 1355 
 1356     if ((fp = fopen(active_file, "a+")) != NULL) {
 1357         char *ptr;
 1358         char *group_name;
 1359         int err;
 1360 
 1361         ptr = group_name = my_strdup(group_path);
 1362         ptr++;
 1363         while ((ptr = strchr(ptr, '/')) != NULL)
 1364             *ptr = '.';
 1365 
 1366         wait_message(0, "Appending=[%s %"T_ARTNUM_PFMT" %"T_ARTNUM_PFMT" %s]\n", group_name, art_max, art_min, base_dir);
 1367         print_group_line(fp, group_name, art_max, art_min, base_dir);
 1368         if ((err = ferror(fp)) || fclose(fp)) { /* TODO: issue warning? */
 1369             if (err) {
 1370                 clearerr(fp);
 1371                 fclose(fp);
 1372             }
 1373             err = rename(file_tmp, active_file);
 1374 #ifdef DEBUG
 1375             if ((debug & DEBUG_MISC) && err) /* TODO: is this the right debug-level? */
 1376                 perror_message(_(txt_rename_error), file_tmp, active_file);
 1377 #endif /* DEBUG */
 1378         }
 1379         free(group_name);
 1380     }
 1381     unlink(file_tmp);
 1382     free(file_tmp);
 1383 }