"Fossies" - the Fresh Open Source Software Archive

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


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

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : mail.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-10-02
    6  *  Updated   : 2016-07-29
    7  *  Notes     : Mail handling routines for creating pseudo newsgroups
    8  *
    9  * Copyright (c) 1992-2017 Iain Lea <iain@bricbrac.de>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. The name of the author may not be used to endorse or promote
   21  *    products derived from this software without specific prior written
   22  *    permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
   25  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
   28  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   30  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  */
   36 
   37 
   38 #ifndef TIN_H
   39 #   include "tin.h"
   40 #endif /* !TIN_H */
   41 #ifndef TCURSES_H
   42 #   include "tcurses.h"
   43 #endif /* !TCURSES_H */
   44 #ifdef NNTP_ABLE
   45 #   ifndef TNNTP_H
   46 #       include "tnntp.h"
   47 #   endif /* !TNNTP_H */
   48 #endif /* NNTP_ABLE */
   49 /*
   50  * local prototypes
   51  */
   52 static FILE *open_newsgroups_fp(void);
   53 static void read_groups_descriptions(FILE *fp, FILE *fp_save);
   54 static void read_newsgroups_file(t_bool verb);
   55 #ifdef HAVE_MH_MAIL_HANDLING
   56     static FILE *open_mail_active_fp(const char *mode);
   57     static FILE *open_mailgroups_fp(void);
   58     static void read_mailgroups_file(t_bool verb);
   59 #endif /* HAVE_MH_MAIL_HANDLING */
   60 
   61 
   62 #ifdef HAVE_MH_MAIL_HANDLING
   63 /*
   64  * Open the mail active file locally
   65  */
   66 static FILE *
   67 open_mail_active_fp(
   68     const char *mode)
   69 {
   70     return fopen(mail_active_file, mode);
   71 }
   72 
   73 
   74 /*
   75  * Open mail groups description file locally
   76  */
   77 static FILE *
   78 open_mailgroups_fp(
   79     void)
   80 {
   81     return fopen(mailgroups_file, "r");
   82 }
   83 
   84 
   85 /*
   86  * Load the mail active file into active[]
   87  */
   88 void
   89 read_mail_active_file(
   90     void)
   91 {
   92     FILE *fp;
   93     char buf[LEN];
   94     char my_spooldir[PATH_LEN];
   95     char buf2[PATH_LEN];
   96     t_artnum min, max;
   97     struct t_group *ptr;
   98 
   99     if (!batch_mode)
  100         wait_message(0, _(txt_reading_mail_active_file));
  101 
  102     /*
  103      * Open the mail active file
  104      */
  105     if ((fp = open_mail_active_fp("r")) == NULL) {
  106         if (cmd_line)
  107             my_fputc('\n', stderr);
  108 
  109         if (!created_rcdir) /* no error on first start */
  110             error_message(2, _(txt_cannot_open), mail_active_file);
  111         /*
  112          * TODO: do an autoscan of maildir, create & reopen?
  113          */
  114         write_mail_active_file();
  115         return;
  116     }
  117 
  118     while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
  119         if (!parse_active_line(buf, &max, &min, my_spooldir) || *buf == '\0')
  120             continue;
  121 
  122         /*
  123          * Update mailgroup info
  124          */
  125         if ((ptr = group_find(buf, FALSE)) != NULL) {
  126             if (strcmp(ptr->spooldir, my_spooldir) != 0) {
  127                 free(ptr->spooldir);
  128                 strfpath(my_spooldir, buf2, sizeof(buf2) - 1, ptr, FALSE);
  129                 ptr->spooldir = my_strdup(buf2);
  130             }
  131             ptr->xmax = max;
  132             ptr->xmin = min;
  133             continue;
  134         }
  135 
  136         /*
  137          * Load mailgroup into group hash table
  138          */
  139         if ((ptr = group_add(buf)) == NULL)
  140             continue;
  141 
  142         /*
  143          * Load group info. TODO: integrate with active_add()
  144          */
  145         strfpath(my_spooldir, buf2, sizeof(buf2) - 1, ptr, FALSE);
  146         ptr->spooldir = my_strdup(buf2);
  147         group_get_art_info(ptr->spooldir, buf, GROUP_TYPE_MAIL, &ptr->count, &ptr->xmax, &ptr->xmin);
  148         ptr->aliasedto = NULL;
  149         ptr->description = NULL;
  150         ptr->moderated = 'y';
  151         ptr->type = GROUP_TYPE_MAIL;
  152         ptr->inrange = FALSE;
  153         ptr->read_during_session = FALSE;
  154         ptr->art_was_posted = FALSE;
  155         ptr->subscribed = FALSE;        /* not in my_group[] yet */
  156         ptr->newgroup = FALSE;
  157         ptr->bogus = FALSE;
  158         ptr->next = -1;         /* hash chaining */
  159         ptr->newsrc.xbitmap = (t_bitmap *) 0;
  160         ptr->attribute = (struct t_attribute *) 0;
  161         ptr->glob_filter = &glob_filter;
  162         set_default_bitmap(ptr);
  163     }
  164     fclose(fp);
  165 
  166     if (!batch_mode)
  167         my_fputs("\n", stdout);
  168 }
  169 
  170 
  171 /*
  172  * Write out mailgroups from active[] to ~/.tin/active.mail
  173  */
  174 void
  175 write_mail_active_file(
  176     void)
  177 {
  178     FILE *fp;
  179     char *file_tmp;
  180     char group_path[PATH_LEN];
  181     int i;
  182     struct t_group *group;
  183 
  184     if (no_write && file_size(mail_active_file) != -1L)
  185         return;
  186 
  187     /* generate tmp-filename */
  188     file_tmp = get_tmpfilename(mail_active_file);
  189 
  190     if (!backup_file(mail_active_file, file_tmp)) {
  191         error_message(2, _(txt_filesystem_full_backup), mail_active_file);
  192         /* free memory for tmp-filename */
  193         free(file_tmp);
  194         return;
  195     }
  196 
  197     print_active_head(mail_active_file);
  198 
  199     if ((fp = open_mail_active_fp("a+")) != NULL) {
  200         for_each_group(i) {
  201             group = &active[i];
  202             if (group->type == GROUP_TYPE_MAIL) {
  203                 make_base_group_path(group->spooldir, group->name, group_path, sizeof(group_path));
  204                 find_art_max_min(group_path, &group->xmax, &group->xmin);
  205                 print_group_line(fp, group->name, group->xmax, group->xmin, group->spooldir);
  206             }
  207         }
  208         if ((i = ferror(fp)) || fclose(fp)) {
  209             error_message(2, _(txt_filesystem_full), mail_active_file);
  210             if (i) {
  211                 clearerr(fp);
  212                 fclose(fp);
  213             }
  214             i = rename(file_tmp, mail_active_file);
  215 #ifdef DEBUG
  216             if ((debug & DEBUG_MISC) && i) /* TODO: is this the right debug-level? */
  217                 perror_message(_(txt_rename_error), file_tmp, mail_active_file);
  218 #endif /* DEBUG */
  219         } else
  220             unlink(file_tmp);
  221     }
  222 
  223     /* free memory for tmp-filename */
  224     free(file_tmp);
  225 }
  226 
  227 
  228 /*
  229  * Load the text description from ~/.tin/mailgroups for each mail group into
  230  * the active[] array.
  231  */
  232 static void
  233 read_mailgroups_file(
  234     t_bool verb)
  235 {
  236     FILE *fp;
  237 
  238     if ((fp = open_mailgroups_fp()) != NULL) {
  239         if (!batch_mode && verb)
  240             wait_message(0, _(txt_reading_mailgroups_file));
  241 
  242         read_groups_descriptions(fp, (FILE *) 0);
  243 
  244         fclose(fp);
  245 
  246         if (!batch_mode && verb) {
  247             my_fputs("\n", stdout);
  248             cursoroff();
  249         }
  250     }
  251 }
  252 #endif /* HAVE_MH_MAIL_HANDLING */
  253 
  254 
  255 /*
  256  * If reading via NNTP the newsgroups file will be saved to
  257  * ~/.tin/$NNTPSERVER/newsgroups so that any subsequent rereads on the
  258  * active file will not have to waste net bandwidth and the local copy
  259  * of the newsgroups file can be accessed.
  260  *
  261  * in the newsrc_active case (-n cmd-line switch) we use "LIST NEWSGROUPS grp"
  262  * instead of "LIST NEWSGROUPS" if we have just a few groups in the newsrc,
  263  * due to pipelining the code is a bit complex.
  264  */
  265 static FILE *
  266 open_newsgroups_fp(
  267     void)
  268 {
  269 #ifdef NNTP_ABLE
  270     FILE *result;
  271     static int no_more_wildmat = 0;
  272 
  273     if (read_news_via_nntp && !read_saved_news) {
  274         if (read_local_newsgroups_file) {
  275             if ((result = fopen(local_newsgroups_file, "r")) != NULL) {
  276                 struct stat buf;
  277 
  278 #   ifdef DEBUG
  279                     if (debug & DEBUG_NNTP)
  280                         debug_print_file("NNTP", "open_newsgroups_fp Using local copy of newsgroups file");
  281 #   endif /* DEBUG */
  282                 if (!fstat(fileno(result), &buf)) {
  283                     if (buf.st_size > 0)
  284                         return result;
  285                 }
  286                 fclose(result);
  287                 unlink(local_newsgroups_file);
  288             }
  289         }
  290         /*
  291          * TODO: test me, find a useful limit,
  292          *       optimize more than n groups (e.g. 5) of the same
  293          *       subhierarchy to a wildmat?
  294          */
  295         if (((nntp_caps.type == CAPABILITIES && nntp_caps.list_newsgroups) || nntp_caps.type != CAPABILITIES) && newsrc_active && !list_active && !no_more_wildmat && (PIPELINE_LIMIT > MAX(1, num_active))) {
  296             char *ptr;
  297             char buff[NNTP_STRLEN];
  298             char line[NNTP_STRLEN];
  299             char file[PATH_LEN];
  300             char serverdir[PATH_LEN];
  301             struct t_group *group;
  302             int resp, i, j = 0;
  303 
  304             if (nntp_tcp_port != IPPORT_NNTP)
  305                 snprintf(file, sizeof(file), "%s:%u", nntp_server, nntp_tcp_port);
  306             else
  307                 STRCPY(file, quote_space_to_dash(nntp_server));
  308 
  309             joinpath(serverdir, sizeof(serverdir), rcdir, file);
  310             joinpath(file, sizeof(file), serverdir, NEWSGROUPS_FILE".tmp");
  311             *buff = '\0';
  312             if ((result = fopen(file, "w")) != NULL) {
  313                 for_each_group(i) {
  314                     if ((group = group_find(active[i].name, FALSE)) != NULL) {
  315                         if (group->type == GROUP_TYPE_NEWS) {
  316                             if (nntp_caps.type == CAPABILITIES && nntp_caps.list_newsgroups) {
  317                                 if (*buff) {
  318                                     if (strlen(buff) + strlen(active[i].name) + 1 < NNTP_GRPLEN) {
  319                                         snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), ",%s", active[i].name);
  320                                         continue;
  321                                     } else {
  322                                         put_server(buff);
  323                                         *buff = '\0';
  324                                         j++;
  325                                     }
  326                                 }
  327                                 if (!*buff) {
  328                                     snprintf(buff, sizeof(buff), "LIST NEWSGROUPS %s", active[i].name);
  329                                     continue;
  330                                 }
  331                             } else
  332                                 snprintf(buff, sizeof(buff), "LIST NEWSGROUPS %s", active[i].name);
  333 #       ifdef DISABLE_PIPELINING
  334                             if ((resp = new_nntp_command(buff, OK_GROUPS, line, sizeof(line))) != OK_GROUPS) {
  335                                 no_more_wildmat = resp;
  336                                 *buff = '\0';
  337                                 break;
  338                             }
  339                             while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  340 #           ifdef DEBUG
  341                                 if (debug & DEBUG_NNTP)
  342                                     debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  343 #           endif /* DEBUG */
  344                                 fprintf(result, "%s\n", str_trim(ptr));
  345                             }
  346 #       else
  347                             put_server(buff);
  348                             *buff = '\0';
  349                             j++;
  350 #       endif /* DISABLE_PIPELINING */
  351                         }
  352                     }
  353                 }
  354                 if (*buff) {
  355                     put_server(buff);
  356                     j++;
  357                 }
  358 #       ifndef DISABLE_PIPELINING
  359                 while (j--) {
  360                     if ((resp = get_only_respcode(line, sizeof(line))) != OK_GROUPS) {
  361                         if (!no_more_wildmat)
  362                             no_more_wildmat = resp;
  363                         continue;
  364                     }
  365                     while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
  366 #           ifdef DEBUG
  367                         if (debug & DEBUG_NNTP)
  368                             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  369 #           endif /* DEBUG */
  370                         fprintf(result, "%s\n", str_trim(ptr));
  371                     }
  372                 }
  373                 /* TODO: add 483 (RFC 3977) support */
  374                 if (no_more_wildmat == ERR_NOAUTH || no_more_wildmat == NEED_AUTHINFO) {
  375                     if (!authenticate(nntp_server, userid, FALSE))
  376                         tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);
  377                 }
  378 #       endif /* !DISABLE_PIPELINING */
  379                 fclose(result);
  380                 result = fopen(file, "r");
  381                 unlink(file); /* unlink on close */
  382             }
  383 
  384             if (result != NULL) {
  385                 if (!no_more_wildmat)
  386                     return result;
  387                 else /* AUTH request while pipeling or some error */
  388                     fclose(result);
  389             }
  390         }
  391         return (nntp_command("LIST NEWSGROUPS", OK_GROUPS, NULL, 0));
  392     }
  393 #endif /* NNTP_ABLE */
  394     return fopen(newsgroups_file, "r");
  395 }
  396 
  397 
  398 /*
  399  * Load the text description from NEWSLIBDIR/newsgroups for each group into the
  400  * active[] array. Save a copy locally if reading via NNTP to save bandwidth.
  401  */
  402 static void
  403 read_newsgroups_file(
  404     t_bool verb)
  405 {
  406     FILE *fp;
  407     FILE *fp_save = (FILE *) 0;
  408 
  409     if ((fp = open_newsgroups_fp()) != NULL) {
  410         if (!batch_mode && verb)
  411             wait_message(0, _(txt_reading_newsgroups_file));
  412 
  413         if (read_news_via_nntp && !no_write && !read_local_newsgroups_file)
  414             fp_save = fopen(local_newsgroups_file, "w");
  415 
  416         read_groups_descriptions(fp, fp_save);
  417 
  418         if (fp_save != NULL) {
  419             fclose(fp_save);
  420             read_local_newsgroups_file = TRUE;
  421         }
  422 
  423         TIN_FCLOSE(fp);
  424 
  425         if (!batch_mode && verb) {
  426             my_fputs("\n", stdout);
  427             cursoroff();
  428         }
  429     }
  430 }
  431 
  432 
  433 /*
  434  * read group descriptions for news (and mailgroups)
  435  */
  436 void
  437 read_descriptions(
  438     t_bool verb)
  439 {
  440 #ifdef HAVE_MH_MAIL_HANDLING
  441     read_mailgroups_file(verb);
  442 #endif /* HAVE_MH_MAIL_HANDLING */
  443     read_newsgroups_file(verb);
  444 }
  445 
  446 
  447 /*
  448  * Read groups descriptions from opened file & make local backup copy
  449  * of all groups if reading groups of type GROUP_TYPE_NEWS.
  450  * Aborting this early won't have any adverse affects, just some missing
  451  * descriptions.
  452  */
  453 static void
  454 read_groups_descriptions(
  455     FILE *fp,
  456     FILE *fp_save)
  457 {
  458     char *p, *q, *ptr;
  459     char *groupname = NULL;
  460     int count = 0;
  461     size_t space = 0;
  462     struct t_group *group;
  463 
  464     while ((ptr = tin_fgets(fp, FALSE)) != NULL) {
  465         if (*ptr == '#' || *ptr == '\0')
  466             continue;
  467 
  468         /*
  469          * This was moved from below and simplified. I can't test here for the
  470          * type of group being read, because that requires having found the
  471          * group in the active file, and that truncates the local copy of the
  472          * newsgroups file to only subscribed-to groups when tin is called
  473          * with the "-q" option.
  474          */
  475         if ((fp_save != NULL) && read_news_via_nntp)
  476             fprintf(fp_save, "%s\n", str_trim(ptr));
  477 
  478         if (!space) { /* initial malloc */
  479             space = strlen(ptr) + 1;
  480             groupname = my_malloc(space);
  481         } else {
  482             while (space < strlen(ptr) + 1) { /* realloc needed? */
  483                 space <<= 1; /* double size */
  484                 groupname = my_realloc(groupname, space);
  485             }
  486         }
  487 
  488         for (p = ptr, q = groupname; *p && *p != ' ' && *p != '\t'; p++, q++)
  489             *q = *p;
  490 
  491         *q = '\0';
  492 
  493         while (*p == '\t' || *p == ' ')
  494             p++;
  495 
  496         group = group_find(groupname, FALSE);
  497 
  498         if (group != NULL && group->description == NULL) {
  499             char *r;
  500             size_t r_len;
  501 
  502             q = p;
  503             while ((q = strchr(q, '\t')) != NULL)
  504                 *q = ' ';
  505 
  506             r = my_strdup(p);
  507             r_len = strlen(r);
  508             /*
  509              * Protect against invalid character sequences.
  510              */
  511             process_charsets(&r, &r_len, "UTF-8", tinrc.mm_local_charset, FALSE);
  512             group->description = convert_to_printable(r, FALSE);
  513         }
  514 
  515         if (++count % 100 == 0)
  516             spin_cursor();
  517     }
  518     FreeIfNeeded(groupname);
  519 }
  520 
  521 
  522 void
  523 print_active_head(
  524     const char *active_file)
  525 {
  526     FILE *fp;
  527 
  528     if (no_write && file_size(active_file) != -1L)
  529         return;
  530 
  531     if ((fp = fopen(active_file, "w")) != NULL) {
  532         fprintf(fp, "%s", _(txt_mail_save_active_head));
  533         fclose(fp);
  534     }
  535 }
  536 
  537 
  538 void
  539 find_art_max_min(
  540     const char *group_path,
  541     t_artnum *art_max,
  542     t_artnum *art_min)
  543 {
  544     DIR *dir;
  545     DIR_BUF *direntry;
  546     t_artnum art_num;
  547 
  548     *art_min = *art_max = T_ARTNUM_CONST(0);
  549 
  550     if ((dir = opendir(group_path)) != NULL) {
  551         while ((direntry = readdir(dir)) != NULL) {
  552             art_num = atoartnum(direntry->d_name);
  553             if (art_num >= T_ARTNUM_CONST(1)) {
  554                 if (art_num > *art_max) {
  555                     *art_max = art_num;
  556                     if (*art_min == T_ARTNUM_CONST(0))
  557                         *art_min = art_num;
  558                 } else if (art_num < *art_min)
  559                     *art_min = art_num;
  560             }
  561         }
  562         CLOSEDIR(dir);
  563     }
  564     if (*art_min == T_ARTNUM_CONST(0))
  565         *art_min = T_ARTNUM_CONST(1);
  566 }
  567 
  568 
  569 void
  570 print_group_line(
  571     FILE *fp,
  572     const char *group_name,
  573     t_artnum art_max,
  574     t_artnum art_min,
  575     const char *base_dir)
  576 {
  577     fprintf(fp, "%s %05"T_ARTNUM_PFMT" %05"T_ARTNUM_PFMT" %s\n",
  578         group_name, art_max, art_min, base_dir);
  579 }
  580 
  581 
  582 void
  583 grp_del_mail_art(
  584     struct t_article *article)
  585 {
  586 
  587     if (article->delete_it)
  588         info_message(_(txt_art_undeleted));
  589     else
  590         info_message(_(txt_art_deleted));
  591 
  592     article->delete_it = bool_not(article->delete_it);
  593 }
  594 
  595 
  596 void
  597 grp_del_mail_arts(
  598     struct t_group *group)
  599 {
  600     char article_filename[PATH_LEN];
  601     char group_path[PATH_LEN];
  602     char artnum[LEN];
  603     int i;
  604     struct t_article *article;
  605 
  606     if (group->type == GROUP_TYPE_MAIL || group->type == GROUP_TYPE_SAVE) {
  607         /*
  608          * at least for GROUP_TYPE_SAVE a wait is annoying - nuke the message?
  609          */
  610         wait_message(0, (group->type == GROUP_TYPE_MAIL) ? _(txt_processing_mail_arts) : _(txt_processing_saved_arts));
  611         cursoroff();
  612         make_base_group_path(group->spooldir, group->name, group_path, sizeof(group_path));
  613         for_each_art(i) {
  614             article = &arts[i];
  615             if (article->delete_it) {
  616                 snprintf(artnum, sizeof(artnum), "%"T_ARTNUM_PFMT, article->artnum);
  617                 joinpath(article_filename, sizeof(article_filename), group_path, artnum);
  618                 unlink(article_filename);
  619                 article->thread = ART_EXPIRED;
  620             }
  621         }
  622 
  623         /*
  624          * current tin's build_references() is changed to free msgid and
  625          * refs, therefore we cannot call write_overview after it. NovFile
  626          * will update at next time.
  627          */
  628     }
  629 }
  630 
  631 
  632 t_bool
  633 art_edit(
  634     struct t_group *group,
  635     struct t_article *article)
  636 {
  637     char article_filename[PATH_LEN];
  638     char temp_filename[PATH_LEN];
  639     char buf[PATH_LEN];
  640 
  641     /*
  642      * Check if news / mail group
  643      */
  644     if (group->type != GROUP_TYPE_MAIL)
  645         return FALSE;
  646 
  647     make_base_group_path(group->spooldir, group->name, temp_filename, sizeof(temp_filename));
  648     snprintf(buf, sizeof(buf), "%"T_ARTNUM_PFMT, article->artnum);
  649     joinpath(article_filename, sizeof(article_filename), temp_filename, buf);
  650     snprintf(buf, sizeof(buf), "%ld.art", (long) process_id);
  651     joinpath(temp_filename, sizeof(temp_filename), TMPDIR, buf);
  652 
  653     if (!backup_file(article_filename, temp_filename))
  654         return FALSE;
  655 
  656     if (!invoke_editor(temp_filename, 1, group)) {
  657         unlink(temp_filename);
  658         return FALSE;
  659     }
  660 
  661     rename_file(temp_filename, article_filename);
  662     return TRUE;
  663 }