"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/mail.c" (9 Dec 2022, 17179 Bytes) of package /linux/misc/tin-2.6.2.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 "mail.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.6.1_vs_2.6.2.

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