"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.3/src/mail.c" (23 Nov 2018, 17071 Bytes) of package /linux/misc/tin-2.4.3.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.4.2_vs_2.4.3.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : mail.c
    4  *  Author    : I. Lea
    5  *  Created   : 1992-10-02
    6  *  Updated   : 2018-02-15
    7  *  Notes     : Mail handling routines for creating pseudo newsgroups
    8  *
    9  * Copyright (c) 1992-2019 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;
   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 ((buf = tin_fgets(fp, FALSE))) {
  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) && verbose > 1)
  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 && verbose)
  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 && verbose)
  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 pipelining 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 #ifdef DEBUG
  466         if ((debug & DEBUG_NNTP) && fp == FAKE_NNTP_FP && verbose)
  467             debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
  468 #endif /* DEBUG */
  469         if (*ptr == '#' || *ptr == '\0')
  470             continue;
  471 
  472         /*
  473          * This was moved from below and simplified. I can't test here for the
  474          * type of group being read, because that requires having found the
  475          * group in the active file, and that truncates the local copy of the
  476          * newsgroups file to only subscribed-to groups when tin is called
  477          * with the "-q" option.
  478          */
  479         if ((fp_save != NULL) && read_news_via_nntp)
  480             fprintf(fp_save, "%s\n", str_trim(ptr));
  481 
  482         if (!space) { /* initial malloc */
  483             space = strlen(ptr) + 1;
  484             groupname = my_malloc(space);
  485         } else {
  486             while (space < strlen(ptr) + 1) { /* realloc needed? */
  487                 space <<= 1; /* double size */
  488                 groupname = my_realloc(groupname, space);
  489             }
  490         }
  491 
  492         for (p = ptr, q = groupname; *p && *p != ' ' && *p != '\t'; p++, q++)
  493             *q = *p;
  494 
  495         *q = '\0';
  496 
  497         while (*p == '\t' || *p == ' ')
  498             p++;
  499 
  500         group = group_find(groupname, FALSE);
  501 
  502         if (group != NULL && group->description == NULL) {
  503             char *r;
  504             size_t r_len;
  505 
  506             q = p;
  507             while ((q = strchr(q, '\t')) != NULL)
  508                 *q = ' ';
  509 
  510             r = my_strdup(p);
  511             r_len = strlen(r);
  512             /*
  513              * Protect against invalid character sequences.
  514              */
  515             process_charsets(&r, &r_len, "UTF-8", tinrc.mm_local_charset, FALSE);
  516             group->description = convert_to_printable(r, FALSE);
  517         }
  518 
  519         if (++count % 100 == 0)
  520             spin_cursor();
  521     }
  522     FreeIfNeeded(groupname);
  523 }
  524 
  525 
  526 void
  527 print_active_head(
  528     const char *active_file)
  529 {
  530     FILE *fp;
  531 
  532     if (no_write && file_size(active_file) != -1L)
  533         return;
  534 
  535     if ((fp = fopen(active_file, "w")) != NULL) {
  536         fprintf(fp, "%s", _(txt_mail_save_active_head));
  537         fclose(fp);
  538     }
  539 }
  540 
  541 
  542 void
  543 find_art_max_min(
  544     const char *group_path,
  545     t_artnum *art_max,
  546     t_artnum *art_min)
  547 {
  548     DIR *dir;
  549     DIR_BUF *direntry;
  550     t_artnum art_num;
  551 
  552     *art_min = *art_max = T_ARTNUM_CONST(0);
  553 
  554     if ((dir = opendir(group_path)) != NULL) {
  555         while ((direntry = readdir(dir)) != NULL) {
  556             art_num = atoartnum(direntry->d_name);
  557             if (art_num >= T_ARTNUM_CONST(1)) {
  558                 if (art_num > *art_max) {
  559                     *art_max = art_num;
  560                     if (*art_min == T_ARTNUM_CONST(0))
  561                         *art_min = art_num;
  562                 } else if (art_num < *art_min)
  563                     *art_min = art_num;
  564             }
  565         }
  566         CLOSEDIR(dir);
  567     }
  568     if (*art_min == T_ARTNUM_CONST(0))
  569         *art_min = T_ARTNUM_CONST(1);
  570 }
  571 
  572 
  573 void
  574 print_group_line(
  575     FILE *fp,
  576     const char *group_name,
  577     t_artnum art_max,
  578     t_artnum art_min,
  579     const char *base_dir)
  580 {
  581     fprintf(fp, "%s %05"T_ARTNUM_PFMT" %05"T_ARTNUM_PFMT" %s\n",
  582         group_name, art_max, art_min, base_dir);
  583 }
  584 
  585 
  586 void
  587 grp_del_mail_art(
  588     struct t_article *article)
  589 {
  590     if (article->delete_it)
  591         info_message(_(txt_art_undeleted));
  592     else
  593         info_message(_(txt_art_deleted));
  594 
  595     article->delete_it = bool_not(article->delete_it);
  596 }
  597 
  598 
  599 void
  600 grp_del_mail_arts(
  601     struct t_group *group)
  602 {
  603     char article_filename[PATH_LEN];
  604     char group_path[PATH_LEN];
  605     char artnum[LEN];
  606     int i;
  607     struct t_article *article;
  608 
  609     if (group->type == GROUP_TYPE_MAIL || group->type == GROUP_TYPE_SAVE) {
  610         /*
  611          * at least for GROUP_TYPE_SAVE a wait is annoying - nuke the message?
  612          */
  613         wait_message(0, (group->type == GROUP_TYPE_MAIL) ? _(txt_processing_mail_arts) : _(txt_processing_saved_arts));
  614         cursoroff();
  615         make_base_group_path(group->spooldir, group->name, group_path, sizeof(group_path));
  616         for_each_art(i) {
  617             article = &arts[i];
  618             if (article->delete_it) {
  619                 snprintf(artnum, sizeof(artnum), "%"T_ARTNUM_PFMT, article->artnum);
  620                 joinpath(article_filename, sizeof(article_filename), group_path, artnum);
  621                 unlink(article_filename);
  622                 article->thread = ART_EXPIRED;
  623             }
  624         }
  625 
  626         /*
  627          * current tin's build_references() is changed to free msgid and
  628          * refs, therefore we cannot call write_overview after it. NovFile
  629          * will update at next time.
  630          */
  631     }
  632 }
  633 
  634 
  635 t_bool
  636 art_edit(
  637     struct t_group *group,
  638     struct t_article *article)
  639 {
  640     char article_filename[PATH_LEN];
  641     char temp_filename[PATH_LEN];
  642     char buf[PATH_LEN];
  643 
  644     /*
  645      * Check if news / mail group
  646      */
  647     if (group->type != GROUP_TYPE_MAIL)
  648         return FALSE;
  649 
  650     make_base_group_path(group->spooldir, group->name, temp_filename, sizeof(temp_filename));
  651     snprintf(buf, sizeof(buf), "%"T_ARTNUM_PFMT, article->artnum);
  652     joinpath(article_filename, sizeof(article_filename), temp_filename, buf);
  653     snprintf(buf, sizeof(buf), "%ld.art", (long) process_id);
  654     joinpath(temp_filename, sizeof(temp_filename), TMPDIR, buf);
  655 
  656     if (!backup_file(article_filename, temp_filename))
  657         return FALSE;
  658 
  659     if (!invoke_editor(temp_filename, 1, group)) {
  660         unlink(temp_filename);
  661         return FALSE;
  662     }
  663 
  664     rename_file(temp_filename, article_filename);
  665     return TRUE;
  666 }