"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/active.c" (22 Dec 2021, 35853 Bytes) of package /linux/misc/tin-2.6.1.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.6.0_vs_2.6.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : active.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-02-16
    6  *  Updated   : 2021-07-23
    7  *  Notes     :
    8  *
    9  * Copyright (c) 1992-2022 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 #endif /* NNTP_ABLE */
  549 
  550     fclose(fp);
  551 
  552     /*
  553      * Exit if active file wasn't read correctly or is empty
  554      */
  555     if (tin_errno || !num_active) {
  556         if (newsrc_active && !num_active)
  557             tin_done(EXIT_FAILURE, _(txt_error_server_has_no_listed_groups), newsrc);
  558         else
  559             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));
  560     }
  561 
  562     if (!batch_mode || verbose)
  563         my_fputc('\n', stdout);
  564 }
  565 
  566 
  567 /*
  568  * Open the news active file locally or send the LIST command
  569  */
  570 static FILE *
  571 open_news_active_fp(
  572     void)
  573 {
  574 #ifdef NNTP_ABLE
  575     if (read_news_via_nntp && !read_saved_news)
  576         return (nntp_command("LIST", OK_GROUPS, NULL, 0));
  577 #endif /* NNTP_ABLE */
  578     return (fopen(news_active_file, "r"));
  579 }
  580 
  581 
  582 /*
  583  * Load the active file into active[]
  584  */
  585 static void
  586 read_active_file(
  587     void)
  588 {
  589     FILE *fp;
  590     char *ptr;
  591     char moderated[PATH_LEN];
  592     long processed = 0L;
  593     struct t_group *grpptr;
  594     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  595 
  596     if (!batch_mode || verbose)
  597         wait_message(0, _(txt_reading_news_active_file));
  598 
  599     if ((fp = open_news_active_fp()) == NULL) {
  600         if (cmd_line && !batch_mode)
  601             my_fputc('\n', stderr);
  602 
  603 #ifdef NNTP_ABLE
  604         if (read_news_via_nntp)
  605             tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  606 #   ifndef NNTP_ONLY
  607         else
  608             tin_done(EXIT_FAILURE, _(txt_cannot_open_active_file), news_active_file, tin_progname);
  609 #   endif /* !NNTP_ONLY */
  610 #else
  611         tin_done(EXIT_FAILURE, _(txt_cannot_open), news_active_file);
  612 #endif /* NNTP_ABLE */
  613     }
  614 
  615     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  616 #if defined(DEBUG) && defined(NNTP_ABLE)
  617         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  618             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  619 #endif /* DEBUG && NNTP_ABLE */
  620 
  621         if (++processed % MODULO_COUNT_NUM == 0)
  622             spin_cursor();
  623 
  624         if (!parse_active_line(ptr, &max, &min, moderated))
  625             continue;
  626 
  627         /*
  628          * Load group into group hash table
  629          * NULL means group already present, so we just fixup the counters
  630          * This call may implicitly ++num_active
  631          */
  632         if ((grpptr = group_add(ptr)) == NULL) {
  633             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  634                 continue;
  635 
  636             if (max > grpptr->xmax) {
  637                 grpptr->xmax = max;
  638                 grpptr->count = count;
  639             }
  640 
  641             if (min > grpptr->xmin) {
  642                 grpptr->xmin = min;
  643                 grpptr->count = count;
  644             }
  645 
  646             continue;
  647         }
  648 
  649         /*
  650          * Load the new group in active[]
  651          */
  652         active_add(grpptr, count, max, min, moderated);
  653     }
  654 
  655     TIN_FCLOSE(fp);
  656 
  657     /*
  658      * Exit if active file wasn't read correctly or is empty
  659      */
  660     if (tin_errno || !num_active)
  661         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));
  662 
  663     if (!batch_mode || verbose)
  664         my_fputc('\n', stdout);
  665 }
  666 
  667 
  668 #ifdef NNTP_ABLE
  669 /*
  670  * Load the active file into active[] via LIST COUNTS
  671  */
  672 static void
  673 read_active_counts(
  674     void)
  675 {
  676     FILE *fp;
  677     char *ptr;
  678     char moderated[PATH_LEN];
  679     long processed = 0L;
  680     struct t_group *grpptr;
  681     t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  682 
  683     if (!batch_mode || verbose)
  684         wait_message(0, _(txt_reading_news_active_file));
  685 
  686     if ((fp = nntp_command("LIST COUNTS", OK_GROUPS, NULL, 0)) == NULL) {
  687         if (cmd_line && !batch_mode)
  688             my_fputc('\n', stderr);
  689 
  690         tin_done(EXIT_FAILURE, _(txt_cannot_retrieve), ACTIVE_FILE);
  691     }
  692 
  693     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  694 #   ifdef DEBUG
  695         if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  696             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  697 #   endif /* DEBUG */
  698 
  699         if (++processed % MODULO_COUNT_NUM == 0)
  700             spin_cursor();
  701 
  702         if (!parse_count_line(ptr, &max, &min, &count, moderated))
  703             continue;
  704 
  705         /*
  706          * Load group into group hash table
  707          * NULL means group already present, so we just fixup the counters
  708          * This call may implicitly ++num_active
  709          */
  710         if ((grpptr = group_add(ptr)) == NULL) {
  711             if ((grpptr = group_find(ptr, FALSE)) == NULL)
  712                 continue;
  713 
  714             if (max > grpptr->xmax) {
  715                 grpptr->xmax = max;
  716                 grpptr->count = count;
  717             }
  718 
  719             if (min > grpptr->xmin) {
  720                 grpptr->xmin = min;
  721                 grpptr->count = count;
  722             }
  723 
  724             continue;
  725         }
  726 
  727         /*
  728          * Load the new group in active[]
  729          */
  730         active_add(grpptr, count, max, min, moderated);
  731     }
  732 
  733     /*
  734      * Exit if active file wasn't read correctly or is empty
  735      */
  736     if (tin_errno || !num_active)
  737         tin_done(EXIT_FAILURE, _(txt_active_file_is_empty), _(txt_servers_active));
  738 
  739     if (!batch_mode || verbose)
  740         my_fputc('\n', stdout);
  741 }
  742 #endif /* NNTP_ABLE */
  743 
  744 
  745 /*
  746  * Load the active file into active[]
  747  * Check and preload any new newgroups into my_group[]
  748  */
  749 int
  750 read_news_active_file(
  751     void)
  752 {
  753     FILE *fp;
  754     int newgrps = 0;
  755     t_bool do_group_cmds = !nntp_caps.list_counts;
  756 #if defined(NNTP_ABLE) && !defined(DISABLE_PIPELINING)
  757     t_bool did_list_cmd = FALSE;
  758 #endif /* NNTP_ABLE && !DISABLE_PIPELINING */
  759 
  760     /*
  761      * Ignore -n if no .newsrc can be found or .newsrc is empty
  762      */
  763     if (newsrc_active) {
  764         if ((fp = fopen(newsrc, "r")) == NULL) {
  765             list_active = TRUE;
  766             newsrc_active = FALSE;
  767         } else {
  768             fclose(fp);
  769             if (file_size(newsrc) <= 0L) {
  770                 list_active = TRUE;
  771                 newsrc_active = FALSE;
  772             }
  773         }
  774     }
  775 
  776     /* Read an active file if it is allowed */
  777     if (list_active) {
  778 #ifdef NNTP_ABLE
  779 #   ifndef DISABLE_PIPELINING
  780         did_list_cmd = TRUE;
  781 #   endif /* !DISABLE_PIPELINING */
  782         if (read_news_via_nntp && nntp_caps.list_counts)
  783             read_active_counts();
  784         else
  785 #endif /* NNTP_ABLE */
  786             read_active_file();
  787     }
  788 
  789     /* Read .newsrc and check each group */
  790     if (newsrc_active) {
  791 #ifdef NNTP_ABLE
  792 #   ifndef DISABLE_PIPELINING
  793         /*
  794          * prefer LIST COUNTS, otherwise use LIST ACTIVE (-l) or GROUP (-n)
  795          * or both (-ln); LIST COUNTS/ACTIVE grplist is used up to
  796          * PIPELINE_LIMIT groups in newsrc
  797          */
  798         if (read_news_via_nntp && (list_active || nntp_caps.list_counts) && !did_list_cmd) {
  799             char buff[NNTP_STRLEN];
  800             char *ptr, *q;
  801             char moderated[PATH_LEN];
  802             int r = 0, j = 0;
  803             int i;
  804             struct t_group *grpptr;
  805             t_artnum count = T_ARTNUM_CONST(-1), min = T_ARTNUM_CONST(1), max = T_ARTNUM_CONST(0);
  806             t_bool need_auth = FALSE;
  807 
  808             *buff = '\0';
  809             /* we can't use for_each_group(i) yet, so we have to parse the newsrc */
  810             if ((fp = fopen(newsrc, "r")) != NULL) {
  811                 while (tin_fgets(fp, FALSE) != NULL)
  812                     j++;
  813                 rewind(fp);
  814                 if (j < PIPELINE_LIMIT) {
  815                     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  816                         if (!(q = strpbrk(ptr, ":!")))
  817                             continue;
  818                         *q = '\0';
  819                         if (nntp_caps.type == CAPABILITIES && (nntp_caps.list_active || nntp_caps.list_counts)) {
  820                             /* LIST ACTIVE or LIST COUNTS takes wildmats */
  821                             if (*buff && ((strlen(buff) + strlen(ptr)) < (NNTP_GRPLEN - 1))) { /* append group name */
  822                                 snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", ptr);
  823                             } else {
  824                                 if (*buff) {
  825                                     put_server(buff);
  826                                     r++;
  827                                 }
  828                                 snprintf(buff, sizeof(buff), "LIST %s %s", nntp_caps.list_counts ? "COUNTS" : "ACTIVE", ptr);
  829                             }
  830                             continue;
  831                         } else
  832                             snprintf(buff, sizeof(buff), "LIST ACTIVE %s", ptr);
  833                         put_server(buff);
  834                         r++;
  835                         *buff = '\0';
  836                     }
  837                     if (*buff) {
  838                         put_server(buff);
  839                         r++;
  840                     }
  841                 } else
  842                     do_group_cmds = TRUE;
  843 
  844                 fclose(fp);
  845 
  846                 if (j < PIPELINE_LIMIT) {
  847                     for (i = 0; i < r && !did_reconnect; i++) {
  848                         if ((j = get_only_respcode(buff, sizeof(buff))) != OK_GROUPS) {
  849                             /* TODO: add 483 (RFC 3977) code */
  850                             if (j == ERR_NOAUTH || j == NEED_AUTHINFO)
  851                                 need_auth = TRUE;
  852 #       if 0 /* do we need something like this? */
  853                             if (j == ERR_CMDSYN)
  854                                 list_active = TRUE;
  855 #       endif /* 0 */
  856                             continue;
  857                         } else {
  858                             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  859 #       ifdef DEBUG
  860                                 if ((debug & DEBUG_NNTP) && verbose) /* long multiline response */
  861                                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  862 #       endif /* DEBUG */
  863                                 if (nntp_caps.type == CAPABILITIES && nntp_caps.list_counts) {
  864                                     if (!parse_count_line(ptr, &max, &min, &count, moderated))
  865                                         continue;
  866                                 } else {
  867                                     if (!parse_active_line(ptr, &max, &min, moderated))
  868                                         continue;
  869                                 }
  870 
  871                                 if ((grpptr = group_add(ptr)) == NULL) {
  872                                     if ((grpptr = group_find(ptr, FALSE)) == NULL)
  873                                         continue;
  874 
  875                                     if (max > grpptr->xmax) {
  876                                         grpptr->xmax = max;
  877                                         grpptr->count = count;
  878                                     }
  879                                     if (min > grpptr->xmin) {
  880                                         grpptr->xmin = min;
  881                                         grpptr->count = count;
  882                                     }
  883                                     continue;
  884                                 }
  885                                 active_add(grpptr, count, max, min, moderated);
  886                             }
  887                         }
  888                     }
  889                     if (need_auth) { /* retry after auth is overkill here, so just auth */
  890                         if (!authenticate(nntp_server, userid, FALSE))
  891                             tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
  892                     }
  893                 }
  894                 did_reconnect = FALSE;
  895             }
  896         }
  897 #   endif /* !DISABLE_PIPELINING */
  898 #endif /* NNTP_ABLE */
  899         if (!nntp_caps.list_counts || do_group_cmds)
  900             read_newsrc_active_file();
  901     }
  902 
  903     (void) time(&active_timestamp);
  904     force_reread_active_file = FALSE;
  905 
  906     /*
  907      * check_for_any_new_groups() also does $AUTOSUBSCRIBE
  908      */
  909     if (check_for_new_newsgroups)
  910         newgrps = check_for_any_new_groups();
  911 
  912     /*
  913      * finally we have a list of all groups and can set the attributes
  914      */
  915     assign_attributes_to_groups();
  916 
  917     return newgrps;
  918 }
  919 
  920 
  921 /*
  922  * Open the active.times file locally or send the NEWGROUPS command
  923  * "NEWGROUPS yymmdd hhmmss"
  924  */
  925 static FILE *
  926 open_newgroups_fp(
  927     int idx)
  928 {
  929 #ifdef NNTP_ABLE
  930     char line[NNTP_STRLEN];
  931     struct tm *ngtm;
  932 
  933     if (read_news_via_nntp && !read_saved_news) {
  934         /*
  935          * not checking for caps_type == CAPABILITIES && reader as some
  936          * servers do not support it even if advertising READER so we must
  937          * handle errors anyway and just issue the cmd.
  938          */
  939         if (idx == -1 || ((ngtm = localtime(&newnews[idx].time)) == NULL))
  940             return (FILE *) 0;
  941 
  942         /*
  943          * RFC 3077 states that we SHOULD use 4 digit year but some servers
  944          * still do not support it.
  945          */
  946         snprintf(line, sizeof(line), "NEWGROUPS %02d%02d%02d %02d%02d%02d",
  947             ngtm->tm_year % 100, ngtm->tm_mon + 1, ngtm->tm_mday,
  948             ngtm->tm_hour, ngtm->tm_min, ngtm->tm_sec);
  949 
  950         return (nntp_command(line, OK_NEWGROUPS, NULL, 0));
  951     }
  952 #endif /* NNTP_ABLE */
  953     return (fopen(active_times_file, "r"));
  954 }
  955 
  956 
  957 /*
  958  * Check for any newly created newsgroups.
  959  *
  960  * If reading news locally check the NEWSLIBDIR/active.times file.
  961  * Format:   Groupname Seconds Creator
  962  *
  963  * If reading news via NNTP issue a NEWGROUPS command.
  964  * Format:   (as active file) Groupname Maxart Minart moderated
  965  */
  966 static int
  967 check_for_any_new_groups(
  968     void)
  969 {
  970     FILE *fp;
  971     char *autosubscribe, *autounsubscribe;
  972     char *ptr, *line, buf[NNTP_STRLEN];
  973     char old_newnews_host[PATH_LEN];
  974     int newnews_index;
  975     int newgrps = 0;
  976     time_t old_newnews_time;
  977     time_t new_newnews_time;
  978 
  979     if (!batch_mode /* || verbose */)
  980         wait_message(0, _(txt_checking_new_groups));
  981 
  982     (void) time(&new_newnews_time);
  983 
  984     /*
  985      * find out if we have read news from here before otherwise -1
  986      */
  987     if ((newnews_index = find_newnews_index(nntp_server)) >= 0) {
  988         STRCPY(old_newnews_host, newnews[newnews_index].host);
  989         old_newnews_time = newnews[newnews_index].time;
  990     } else {
  991         STRCPY(old_newnews_host, "UNKNOWN");
  992         old_newnews_time = (time_t) 0;
  993     }
  994 
  995 #ifdef DEBUG
  996     if ((debug & DEBUG_NNTP) && verbose > 1)
  997         debug_print_file("NNTP", "Newnews old=[%lu]  new=[%lu]", (unsigned long int) old_newnews_time, (unsigned long int) new_newnews_time);
  998 #endif /* DEBUG */
  999 
 1000     if ((fp = open_newgroups_fp(newnews_index)) != NULL) {
 1001         /*
 1002          * Need these later. They list user-defined groups to be
 1003          * automatically subscribed or unsubscribed.
 1004          */
 1005         autosubscribe = getenv("AUTOSUBSCRIBE");
 1006         autounsubscribe = getenv("AUTOUNSUBSCRIBE");
 1007 
 1008         while ((line = tin_fgets(fp, FALSE)) != NULL) {
 1009             /*
 1010              * Split the group name off and subscribe. If we're reading local,
 1011              * we must check the creation date manually
 1012              */
 1013             if ((ptr = strchr(line, ' ')) != NULL) {
 1014                 if (!read_news_via_nntp && ((time_t) atol(ptr) < old_newnews_time || old_newnews_time == (time_t) 0))
 1015                     continue;
 1016 
 1017                 *ptr = '\0';
 1018             }
 1019             subscribe_new_group(line, autosubscribe, autounsubscribe);
 1020             newgrps++;
 1021         }
 1022         TIN_FCLOSE(fp);
 1023 
 1024         if (tin_errno)
 1025             return 0;               /* Don't update the time if we quit */
 1026     }
 1027 
 1028     /*
 1029      * Update (if already existing) or create (if new) the in-memory
 1030      * 'last time newgroups checked' slot for this server. It will be written
 1031      * out as part of tinrc.
 1032      */
 1033     if (newnews_index >= 0)
 1034         newnews[newnews_index].time = new_newnews_time;
 1035     else {
 1036         snprintf(buf, sizeof(buf), "%s %lu", nntp_server, (unsigned long int) new_newnews_time);
 1037         load_newnews_info(buf);
 1038     }
 1039 
 1040     if (!batch_mode)
 1041         my_fputc('\n', stdout);
 1042 
 1043     return newgrps;
 1044 }
 1045 
 1046 
 1047 /*
 1048  * Subscribe to a new news group:
 1049  * Handle the AUTOSUBSCRIBE/AUTOUNSUBSCRIBE env vars
 1050  * They hold a wildcard list of groups that should be automatically
 1051  * (un)subscribed when a new group is found
 1052  * If a group is autounsubscribed, completely ignore it
 1053  * If a group is autosubscribed, subscribe to it
 1054  * Otherwise, mark it as New for inclusion in selection screen
 1055  */
 1056 static void
 1057 subscribe_new_group(
 1058     char *group,
 1059     char *autosubscribe,
 1060     char *autounsubscribe)
 1061 {
 1062     int idx;
 1063     struct t_group *ptr;
 1064 
 1065     /*
 1066      * If we explicitly don't auto subscribe to this group, then don't bother going on
 1067      */
 1068     if ((autounsubscribe != NULL) && match_group_list(group, autounsubscribe))
 1069         return;
 1070 
 1071     /*
 1072      * Try to add the group to our selection list. If this fails, we're
 1073      * probably using -n, so we fake an entry with no counts. The count will
 1074      * be properly updated when we enter the group. Otherwise there is some
 1075      * mismatch in the active.times data and we ignore the newgroup.
 1076      */
 1077     if ((idx = my_group_add(group, FALSE)) < 0) {
 1078         if (list_active) {
 1079 /*          my_fprintf(stderr, "subscribe_new_group: %s not in active[] && list_active\n", group); */
 1080             return;
 1081         }
 1082 
 1083         if ((ptr = group_add(group)) != NULL)
 1084             active_add(ptr, T_ARTNUM_CONST(0), T_ARTNUM_CONST(1), T_ARTNUM_CONST(0), "y");
 1085 
 1086         if ((idx = my_group_add(group, FALSE)) < 0)
 1087             return;
 1088     }
 1089 
 1090     if (!no_write && (autosubscribe != NULL) && match_group_list(group, autosubscribe)) {
 1091         if (!batch_mode || verbose)
 1092             my_printf(_(txt_autosubscribed), group);
 1093 
 1094         /*
 1095          * as subscribe_new_group() is called from check_for_any_new_groups()
 1096          * which has pending data on the socket if reading via NNTP we are not
 1097          * allowed to issue any NNTP commands yet
 1098          */
 1099         subscribe(&active[my_group[idx]], SUBSCRIBED, bool_not(read_news_via_nntp));
 1100         /*
 1101          * Bad kluge to stop group later appearing in New newsgroups. This
 1102          * effectively loses the group, and it has now been subscribed to and
 1103          * so will be reread later by read_newsrc()
 1104          */
 1105         selmenu.max--;
 1106     } else
 1107         active[my_group[idx]].newgroup = TRUE;
 1108 }
 1109 
 1110 
 1111 /*
 1112  * See if group is a member of group_list, returning a boolean.
 1113  * group_list is a comma separated list of newsgroups, ! implies NOT
 1114  * The same degree of wildcarding as used elsewhere in tin is allowed
 1115  */
 1116 t_bool
 1117 match_group_list(
 1118     const char *group,
 1119     const char *group_list)
 1120 {
 1121     char *separator;
 1122     char pattern[HEADER_LEN];
 1123     size_t group_len, list_len;
 1124     t_bool negate, accept = FALSE;
 1125 
 1126     list_len = strlen(group_list);
 1127     /*
 1128      * walk through comma-separated entries in list
 1129      */
 1130     while (list_len != 0) {
 1131         /*
 1132          * find end/length of this entry
 1133          */
 1134         separator = strchr(group_list, ',');
 1135         group_len = MIN(((separator == NULL) ? list_len : (size_t) (separator - group_list)), sizeof(pattern) - 1);
 1136 
 1137         if ((negate = (*group_list == '!'))) {
 1138             /*
 1139              * a '!' before the pattern inverts sense of match
 1140              */
 1141             group_list++;
 1142             group_len--;
 1143             list_len--;
 1144         }
 1145         /*
 1146          * copy out the entry and terminate it properly
 1147          */
 1148         strncpy(pattern, group_list, group_len);
 1149         pattern[group_len] = '\0';
 1150         /*
 1151          * case-insensitive wildcard match
 1152          */
 1153         if (GROUP_MATCH(group, pattern, TRUE))
 1154             accept = bool_not(negate);  /* matched! */
 1155 
 1156         /*
 1157          * now examine next entry if any
 1158          */
 1159         if (group_list[group_len] != '\0')
 1160             group_len++;    /* skip the separator */
 1161 
 1162         group_list += group_len;
 1163         list_len -= group_len;
 1164     }
 1165     return accept;
 1166 }
 1167 
 1168 
 1169 /*
 1170  * Add or update an entry to the in-memory newnews[] array (The times newgroups
 1171  * were last checked for a particular news server)
 1172  * If this is first time we've been called, zero out the array.
 1173  *
 1174  * Side effects:
 1175  *   'info' is modified. Caller should not depend on it.
 1176  */
 1177 void
 1178 load_newnews_info(
 1179     char *info)
 1180 {
 1181     char *ptr;
 1182     int i;
 1183     time_t new_time;
 1184 
 1185     /*
 1186      * initialize newnews[] if no entries
 1187      */
 1188     if (!num_newnews) {
 1189         for (i = 0; i < max_newnews; i++) {
 1190             newnews[i].host = NULL;
 1191             newnews[i].time = (time_t) 0;
 1192         }
 1193     }
 1194 
 1195     /*
 1196      * Split 'info' into hostname and time
 1197      */
 1198     if ((ptr = strchr(info, ' ')) == NULL)
 1199         return;
 1200 
 1201     *ptr++ = '\0';
 1202     new_time = (time_t) atol(ptr);
 1203 
 1204     /*
 1205      * If this is a new host entry, set it up
 1206      */
 1207     if ((i = find_newnews_index(info)) == -1) {
 1208         i = num_newnews++;
 1209 
 1210         if (i >= max_newnews)
 1211             expand_newnews();
 1212         newnews[i].host = my_strdup(info);
 1213     }
 1214 
 1215     newnews[i].time = new_time;
 1216 
 1217 #ifdef DEBUG
 1218     if ((debug & DEBUG_NNTP) && verbose > 1)
 1219         debug_print_file("NNTP", "ACTIVE host=[%s] time=[%lu]", newnews[i].host, (unsigned long int) newnews[i].time);
 1220 #endif /* DEBUG */
 1221 }
 1222 
 1223 
 1224 /*
 1225  * Return the index of cur_newnews_host in newnews[] or -1 if not found
 1226  */
 1227 int
 1228 find_newnews_index(
 1229     const char *cur_newnews_host)
 1230 {
 1231     int i;
 1232 
 1233     for (i = 0; i < num_newnews; i++) {
 1234         if (STRCMPEQ(cur_newnews_host, newnews[i].host))
 1235             return i;
 1236     }
 1237 
 1238     return -1;
 1239 }
 1240 
 1241 
 1242 /*
 1243  * Get a single status char from the moderated field. Used on selection screen
 1244  * and in header of group screen
 1245  */
 1246 char
 1247 group_flag(
 1248     char ch)
 1249 {
 1250     switch (ch) {
 1251         case 'm':
 1252             return 'M';
 1253 
 1254         case 'x':
 1255         case 'n':
 1256         case 'j':
 1257             return 'X';
 1258 
 1259         case '=':
 1260             return '=';
 1261 
 1262         default:
 1263             return ' ';
 1264     }
 1265 }
 1266 
 1267 
 1268 /* ex actived.c functions */
 1269 void
 1270 create_save_active_file(
 1271     void)
 1272 {
 1273     char *fb;
 1274     char group_path[PATH_LEN];
 1275     char local_save_active_file[PATH_LEN];
 1276 
 1277     joinpath(local_save_active_file, sizeof(local_save_active_file), rcdir, ACTIVE_SAVE_FILE);
 1278 
 1279     if (no_write && file_size(local_save_active_file) != -1L)
 1280         return;
 1281 
 1282     if (strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, group_path, sizeof(group_path), NULL, FALSE)) {
 1283         wait_message(0, _(txt_creating_active));
 1284         print_active_head(local_save_active_file);
 1285 
 1286         while (strlen(group_path) && group_path[strlen(group_path) - 1] == '/')
 1287             group_path[strlen(group_path) - 1] = '\0';
 1288 
 1289         fb = my_strdup(group_path);
 1290         make_group_list(local_save_active_file, (cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : tinrc.savedir, fb, group_path);
 1291         free(fb);
 1292     }
 1293 }
 1294 
 1295 
 1296 static void
 1297 make_group_list(
 1298     char *active_file,
 1299     char *base_dir,
 1300     char *fixed_base,
 1301     char *group_path)
 1302 {
 1303     DIR *dir;
 1304     DIR_BUF *direntry;
 1305     char *ptr;
 1306     char filename[PATH_LEN];
 1307     char path[PATH_LEN];
 1308     t_artnum art_max;
 1309     t_artnum art_min;
 1310     struct stat stat_info;
 1311     t_bool is_dir;
 1312 
 1313     if ((dir = opendir(group_path)) != NULL) {
 1314         is_dir = FALSE;
 1315         while ((direntry = readdir(dir)) != NULL) {
 1316             STRCPY(filename, direntry->d_name);
 1317             joinpath(path, sizeof(path), group_path, filename);
 1318             if (!(filename[0] == '.' && filename[1] == '\0') &&
 1319                 !(filename[0] == '.' && filename[1] == '.' && filename[2] == '\0')) {
 1320                 if (stat(path, &stat_info) != -1) {
 1321                     if (S_ISDIR(stat_info.st_mode))
 1322                         is_dir = TRUE;
 1323                 }
 1324             }
 1325             if (is_dir) {
 1326                 is_dir = FALSE;
 1327                 strcpy(group_path, path);
 1328 
 1329                 make_group_list(active_file, base_dir, fixed_base, group_path);
 1330                 find_art_max_min(group_path, &art_max, &art_min);
 1331                 append_group_line(active_file, group_path + strlen(fixed_base) + 1, art_max, art_min, fixed_base);
 1332                 if ((ptr = strrchr(group_path, '/')) != NULL)
 1333                     *ptr = '\0';
 1334             }
 1335         }
 1336         CLOSEDIR(dir);
 1337     }
 1338 }
 1339 
 1340 
 1341 static void
 1342 append_group_line(
 1343     char *active_file,
 1344     char *group_path,
 1345     t_artnum art_max,
 1346     t_artnum art_min,
 1347     char *base_dir)
 1348 {
 1349     FILE *fp;
 1350     char *file_tmp;
 1351 
 1352     if (art_max == 0 && art_min == 1)
 1353         return;
 1354 
 1355     file_tmp = get_tmpfilename(active_file);
 1356 
 1357     if (!backup_file(active_file, file_tmp)) {
 1358         free(file_tmp);
 1359         return;
 1360     }
 1361 
 1362     if ((fp = fopen(active_file, "a+")) != NULL) {
 1363         char *ptr;
 1364         char *group_name;
 1365         int err;
 1366 
 1367         ptr = group_name = my_strdup(group_path);
 1368         ptr++;
 1369         while ((ptr = strchr(ptr, '/')) != NULL)
 1370             *ptr = '.';
 1371 
 1372         wait_message(0, "Appending=[%s %"T_ARTNUM_PFMT" %"T_ARTNUM_PFMT" %s]\n", group_name, art_max, art_min, base_dir);
 1373         print_group_line(fp, group_name, art_max, art_min, base_dir);
 1374         if ((err = ferror(fp)) || fclose(fp)) { /* TODO: issue warning? */
 1375             if (err) {
 1376                 clearerr(fp);
 1377                 fclose(fp);
 1378             }
 1379             err = rename(file_tmp, active_file);
 1380 #ifdef DEBUG
 1381             if ((debug & DEBUG_MISC) && err) /* TODO: is this the right debug-level? */
 1382                 perror_message(_(txt_rename_error), file_tmp, active_file);
 1383 #endif /* DEBUG */
 1384         }
 1385         free(group_name);
 1386     }
 1387     unlink(file_tmp);
 1388     free(file_tmp);
 1389 }