"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/active.c" (20 Nov 2019, 35853 Bytes) of package /linux/misc/tin-2.4.4.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.3_vs_2.4.4.

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