Coverage Report

Created: 2025-04-22 06:17

/src/neomutt/send/send.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Prepare and send an email
4
 *
5
 * @authors
6
 * Copyright (C) 2016-2024 Richard Russon <rich@flatcap.org>
7
 * Copyright (C) 2017-2023 Pietro Cerutti <gahr@gahr.ch>
8
 * Copyright (C) 2020 Jakub Jindra <jakub.jindra@socialbakers.com>
9
 * Copyright (C) 2020 Thomas Sanchez <thomas.sanchz@gmail.com>
10
 * Copyright (C) 2021 Ashish Panigrahi <ashish.panigrahi@protonmail.com>
11
 * Copyright (C) 2021 Charalampos Kardaris <ckardaris@outlook.com>
12
 * Copyright (C) 2021 Thomas Bracht Laumann Jespersen <t@laumann.xyz>
13
 * Copyright (C) 2021 Viktor Cheburkin <victor.cheburkin@gmail.com>
14
 * Copyright (C) 2022 David Purton <dcpurton@marshwiggle.net>
15
 * Copyright (C) 2023 Dennis Schön <mail@dennis-schoen.de>
16
 * Copyright (C) 2023 Whitney Cumber
17
 * Copyright (C) 2023 наб <nabijaczleweli@nabijaczleweli.xyz>
18
 * Copyright (C) 2024 Alejandro Colomar <alx@kernel.org>
19
 * Copyright (C) 2023-2024 Tóth János <gomba007@gmail.com>
20
 *
21
 * @copyright
22
 * This program is free software: you can redistribute it and/or modify it under
23
 * the terms of the GNU General Public License as published by the Free Software
24
 * Foundation, either version 2 of the License, or (at your option) any later
25
 * version.
26
 *
27
 * This program is distributed in the hope that it will be useful, but WITHOUT
28
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
29
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
30
 * details.
31
 *
32
 * You should have received a copy of the GNU General Public License along with
33
 * this program.  If not, see <http://www.gnu.org/licenses/>.
34
 */
35
36
/**
37
 * @page send_send Prepare and send an email
38
 *
39
 * Prepare and send an email
40
 */
41
42
#include "config.h"
43
#include <errno.h>
44
#include <locale.h>
45
#include <stdbool.h>
46
#include <stdio.h>
47
#include <string.h>
48
#include <sys/stat.h>
49
#include <sys/types.h>
50
#include <unistd.h>
51
#include "mutt/lib.h"
52
#include "address/lib.h"
53
#include "config/lib.h"
54
#include "email/lib.h"
55
#include "core/lib.h"
56
#include "alias/lib.h"
57
#include "gui/lib.h"
58
#include "mutt.h"
59
#include "send.h"
60
#include "attach/lib.h"
61
#include "browser/lib.h"
62
#include "compose/lib.h"
63
#include "editor/lib.h"
64
#include "expando/lib.h"
65
#include "history/lib.h"
66
#include "imap/lib.h"
67
#include "index/lib.h"
68
#include "ncrypt/lib.h"
69
#include "pager/lib.h"
70
#include "parse/lib.h"
71
#include "pattern/lib.h"
72
#include "postpone/lib.h"
73
#include "question/lib.h"
74
#include "body.h"
75
#include "copy.h"
76
#include "expando.h"
77
#include "globals.h"
78
#include "handler.h"
79
#include "header.h"
80
#include "hook.h"
81
#include "maillist.h"
82
#include "multipart.h"
83
#include "mutt_body.h"
84
#include "mutt_header.h"
85
#include "mutt_logging.h"
86
#include "muttlib.h"
87
#include "mx.h"
88
#include "nntp/mdata.h"
89
#include "protos.h"
90
#include "rfc3676.h"
91
#include "sendlib.h"
92
#include "sendmail.h"
93
#include "smtp.h"
94
#ifdef USE_NOTMUCH
95
#include "notmuch/lib.h"
96
#endif
97
#ifdef USE_AUTOCRYPT
98
#include "autocrypt/lib.h"
99
#endif
100
101
/**
102
 * append_signature - Append a signature to an email
103
 * @param fp  File to write to
104
 * @param sub Config Subset
105
 */
106
static void append_signature(FILE *fp, struct ConfigSubset *sub)
107
0
{
108
0
  const char *const c_signature = cs_subset_path(sub, "signature");
109
0
  if (!c_signature)
110
0
    return;
111
112
  // If the user hasn't set $signature, don't warn them if it doesn't exist
113
0
  struct Buffer *def_sig = buf_pool_get();
114
0
  cs_str_initial_get(sub->cs, "signature", def_sig);
115
0
  mutt_path_canon(def_sig, NeoMutt->home_dir, false);
116
0
  bool notify_missing = !mutt_str_equal(c_signature, buf_string(def_sig));
117
0
  buf_pool_release(&def_sig);
118
119
0
  pid_t pid = 0;
120
0
  FILE *fp_tmp = mutt_open_read(c_signature, &pid);
121
0
  if (!fp_tmp)
122
0
  {
123
0
    if (notify_missing)
124
0
      mutt_perror("%s", c_signature);
125
0
    return;
126
0
  }
127
128
0
  const bool c_sig_dashes = cs_subset_bool(sub, "sig_dashes");
129
0
  if (c_sig_dashes)
130
0
    fputs("\n-- \n", fp);
131
0
  mutt_file_copy_stream(fp_tmp, fp);
132
0
  mutt_file_fclose(&fp_tmp);
133
0
  if (pid != -1)
134
0
    filter_wait(pid);
135
0
}
136
137
/**
138
 * remove_user - Remove any address which matches the current user
139
 * @param al         List of addresses
140
 * @param leave_only If set, don't remove the user's address if it it the only
141
 *                   one in the list
142
 */
143
static void remove_user(struct AddressList *al, bool leave_only)
144
0
{
145
0
  struct Address *a = NULL, *tmp = NULL;
146
0
  TAILQ_FOREACH_SAFE(a, al, entries, tmp)
147
0
  {
148
0
    if (mutt_addr_is_user(a) && (!leave_only || TAILQ_NEXT(a, entries)))
149
0
    {
150
0
      TAILQ_REMOVE(al, a, entries);
151
0
      mutt_addr_free(&a);
152
0
    }
153
0
  }
154
0
}
155
156
/**
157
 * add_mailing_lists - Search Address lists for mailing lists
158
 * @param out Address list where to append matching mailing lists
159
 * @param t   'To' Address list
160
 * @param c   'Cc' Address list
161
 */
162
static void add_mailing_lists(struct AddressList *out, const struct AddressList *t,
163
                              const struct AddressList *c)
164
0
{
165
0
  const struct AddressList *const als[] = { t, c };
166
167
0
  for (size_t i = 0; i < mutt_array_size(als); i++)
168
0
  {
169
0
    const struct AddressList *al = als[i];
170
0
    struct Address *a = NULL;
171
0
    TAILQ_FOREACH(a, al, entries)
172
0
    {
173
0
      if (!a->group && mutt_is_mail_list(a))
174
0
      {
175
0
        mutt_addrlist_append(out, mutt_addr_copy(a));
176
0
      }
177
0
    }
178
0
  }
179
0
}
180
181
/**
182
 * mutt_edit_address - Edit an email address
183
 * @param[in,out] al          AddressList to edit
184
 * @param[in]  field          Prompt for user
185
 * @param[in]  expand_aliases If true, expand Address aliases
186
 * @retval  0 Success
187
 * @retval -1 Failure
188
 */
189
int mutt_edit_address(struct AddressList *al, const char *field, bool expand_aliases)
190
0
{
191
0
  int rc = 0;
192
0
  struct Buffer *buf = buf_pool_get();
193
0
  buf_alloc(buf, 8192);
194
0
  char *err = NULL;
195
0
  int idna_ok = 0;
196
197
0
  do
198
0
  {
199
0
    mutt_addrlist_to_local(al);
200
0
    buf_reset(buf);
201
0
    mutt_addrlist_write(al, buf, false);
202
0
    if (!buf_is_empty(buf))
203
0
      buf_addstr(buf, ", ");
204
205
0
    if (mw_get_field(field, buf, MUTT_COMP_NO_FLAGS, HC_ALIAS, &CompleteAliasOps, NULL) != 0)
206
0
    {
207
0
      rc = -1;
208
0
      goto done;
209
0
    }
210
0
    mutt_addrlist_clear(al);
211
0
    mutt_addrlist_parse2(al, buf_string(buf));
212
0
    if (expand_aliases)
213
0
      mutt_expand_aliases(al);
214
0
    idna_ok = mutt_addrlist_to_intl(al, &err);
215
0
    if (idna_ok != 0)
216
0
    {
217
0
      mutt_error(_("Bad IDN: '%s'"), err);
218
0
      FREE(&err);
219
0
    }
220
0
  } while (idna_ok != 0);
221
222
0
done:
223
0
  buf_pool_release(&buf);
224
0
  return rc;
225
0
}
226
227
/**
228
 * edit_envelope - Edit Envelope fields
229
 * @param en    Envelope to edit
230
 * @param flags Flags, see #SendFlags
231
 * @param sub   Config Subset
232
 * @retval  0 Success
233
 * @retval -1 Failure
234
 */
235
static int edit_envelope(struct Envelope *en, SendFlags flags, struct ConfigSubset *sub)
236
0
{
237
0
  int rc = -1;
238
0
  struct Buffer *buf = buf_pool_get();
239
0
  buf_alloc(buf, 8192);
240
241
0
  if (OptNewsSend)
242
0
  {
243
0
    if (en->newsgroups)
244
0
      buf_strcpy(buf, en->newsgroups);
245
0
    else
246
0
      buf_reset(buf);
247
248
0
    if (mw_get_field("Newsgroups: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) != 0)
249
0
    {
250
0
      goto done;
251
0
    }
252
0
    mutt_str_replace(&en->newsgroups, buf_string(buf));
253
254
0
    if (en->followup_to)
255
0
      buf_strcpy(buf, en->followup_to);
256
0
    else
257
0
      buf_reset(buf);
258
259
0
    const bool c_ask_followup_to = cs_subset_bool(sub, "ask_followup_to");
260
0
    if (c_ask_followup_to && (mw_get_field("Followup-To: ", buf, MUTT_COMP_NO_FLAGS,
261
0
                                           HC_OTHER, NULL, NULL) != 0))
262
0
    {
263
0
      goto done;
264
0
    }
265
0
    mutt_str_replace(&en->followup_to, buf_string(buf));
266
267
0
    if (en->x_comment_to)
268
0
      buf_strcpy(buf, en->x_comment_to);
269
0
    else
270
0
      buf_reset(buf);
271
272
0
    const bool c_x_comment_to = cs_subset_bool(sub, "x_comment_to");
273
0
    const bool c_ask_x_comment_to = cs_subset_bool(sub, "ask_x_comment_to");
274
0
    if (c_x_comment_to && c_ask_x_comment_to &&
275
0
        (mw_get_field("X-Comment-To: ", buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) != 0))
276
0
    {
277
0
      goto done;
278
0
    }
279
0
    mutt_str_replace(&en->x_comment_to, buf_string(buf));
280
0
  }
281
0
  else
282
0
  {
283
0
    const bool c_fast_reply = cs_subset_bool(sub, "fast_reply");
284
0
    if (TAILQ_EMPTY(&en->to) || !c_fast_reply || (flags & SEND_REVIEW_TO))
285
0
    {
286
0
      if ((mutt_edit_address(&en->to, _("To: "), true) == -1))
287
0
        goto done;
288
0
    }
289
290
0
    const bool c_ask_cc = cs_subset_bool(sub, "ask_cc");
291
0
    if (TAILQ_EMPTY(&en->cc) || !c_fast_reply)
292
0
    {
293
0
      if (c_ask_cc && (mutt_edit_address(&en->cc, _("Cc: "), true) == -1))
294
0
        goto done;
295
0
    }
296
297
0
    const bool c_ask_bcc = cs_subset_bool(sub, "ask_bcc");
298
0
    if (TAILQ_EMPTY(&en->bcc) || !c_fast_reply)
299
0
    {
300
0
      if (c_ask_bcc && (mutt_edit_address(&en->bcc, _("Bcc: "), true) == -1))
301
0
        goto done;
302
0
    }
303
304
0
    if (TAILQ_EMPTY(&en->to) && TAILQ_EMPTY(&en->cc) && TAILQ_EMPTY(&en->bcc))
305
0
    {
306
0
      mutt_warning(_("No recipients specified"));
307
0
      goto done;
308
0
    }
309
310
0
    const bool c_reply_with_xorig = cs_subset_bool(sub, "reply_with_xorig");
311
0
    if (c_reply_with_xorig && (flags & (SEND_REPLY | SEND_LIST_REPLY | SEND_GROUP_REPLY)) &&
312
0
        (mutt_edit_address(&en->from, "From: ", true) == -1))
313
0
    {
314
0
      goto done;
315
0
    }
316
0
  }
317
318
0
  if (en->subject)
319
0
  {
320
0
    const bool c_fast_reply = cs_subset_bool(sub, "fast_reply");
321
0
    if (c_fast_reply)
322
0
    {
323
0
      rc = 0;
324
0
      goto done;
325
0
    }
326
0
    buf_strcpy(buf, en->subject);
327
0
  }
328
0
  else
329
0
  {
330
0
    const char *p = NULL;
331
332
0
    buf_reset(buf);
333
0
    struct ListNode *uh = NULL;
334
0
    STAILQ_FOREACH(uh, &UserHeader, entries)
335
0
    {
336
0
      size_t plen = mutt_istr_startswith(uh->data, "subject:");
337
0
      if (plen)
338
0
      {
339
0
        p = mutt_str_skip_email_wsp(uh->data + plen);
340
0
        buf_strcpy(buf, p);
341
0
      }
342
0
    }
343
0
  }
344
345
0
  if ((mw_get_field(_("Subject: "), buf, MUTT_COMP_NO_FLAGS, HC_OTHER, NULL, NULL) != 0) ||
346
0
      (buf_is_empty(buf) &&
347
0
       (query_quadoption(_("No subject, abort?"), sub, "abort_nosubject") != MUTT_NO)))
348
0
  {
349
0
    mutt_message(_("No subject, aborting"));
350
0
    goto done;
351
0
  }
352
0
  mutt_env_set_subject(en, buf_string(buf));
353
0
  rc = 0;
354
355
0
done:
356
0
  buf_pool_release(&buf);
357
0
  return rc;
358
0
}
359
360
/**
361
 * nntp_get_header - Get the trimmed header
362
 * @param s Header line with leading whitespace
363
 * @retval ptr Copy of string
364
 *
365
 * @note The caller should free the returned string.
366
 */
367
static char *nntp_get_header(const char *s)
368
0
{
369
0
  SKIPWS(s);
370
0
  return mutt_str_dup(s);
371
0
}
372
373
/**
374
 * process_user_recips - Process the user headers
375
 * @param env Envelope to populate
376
 */
377
static void process_user_recips(struct Envelope *env)
378
0
{
379
0
  struct ListNode *uh = NULL;
380
0
  STAILQ_FOREACH(uh, &UserHeader, entries)
381
0
  {
382
0
    size_t plen;
383
0
    if ((plen = mutt_istr_startswith(uh->data, "to:")))
384
0
      mutt_addrlist_parse(&env->to, uh->data + plen);
385
0
    else if ((plen = mutt_istr_startswith(uh->data, "cc:")))
386
0
      mutt_addrlist_parse(&env->cc, uh->data + plen);
387
0
    else if ((plen = mutt_istr_startswith(uh->data, "bcc:")))
388
0
      mutt_addrlist_parse(&env->bcc, uh->data + plen);
389
0
    else if ((plen = mutt_istr_startswith(uh->data, "newsgroups:")))
390
0
      env->newsgroups = nntp_get_header(uh->data + plen);
391
0
    else if ((plen = mutt_istr_startswith(uh->data, "followup-to:")))
392
0
      env->followup_to = nntp_get_header(uh->data + plen);
393
0
    else if ((plen = mutt_istr_startswith(uh->data, "x-comment-to:")))
394
0
      env->x_comment_to = nntp_get_header(uh->data + plen);
395
0
  }
396
0
}
397
398
/**
399
 * process_user_header - Process the user headers
400
 * @param env Envelope to populate
401
 */
402
static void process_user_header(struct Envelope *env)
403
0
{
404
0
  struct ListNode *uh = NULL;
405
0
  STAILQ_FOREACH(uh, &UserHeader, entries)
406
0
  {
407
0
    size_t plen;
408
0
    if ((plen = mutt_istr_startswith(uh->data, "from:")))
409
0
    {
410
      /* User has specified a default From: address.  Remove default address */
411
0
      mutt_addrlist_clear(&env->from);
412
0
      mutt_addrlist_parse(&env->from, uh->data + plen);
413
0
    }
414
0
    else if ((plen = mutt_istr_startswith(uh->data, "reply-to:")))
415
0
    {
416
0
      mutt_addrlist_clear(&env->reply_to);
417
0
      mutt_addrlist_parse(&env->reply_to, uh->data + plen);
418
0
    }
419
0
    else if ((plen = mutt_istr_startswith(uh->data, "message-id:")))
420
0
    {
421
0
      char *tmp = mutt_extract_message_id(uh->data + plen, NULL);
422
0
      if (mutt_addr_valid_msgid(tmp))
423
0
      {
424
0
        FREE(&env->message_id);
425
0
        env->message_id = tmp;
426
0
      }
427
0
      else
428
0
      {
429
0
        FREE(&tmp);
430
0
      }
431
0
    }
432
0
    else if (!mutt_istr_startswith(uh->data, "to:") &&
433
0
             !mutt_istr_startswith(uh->data, "cc:") &&
434
0
             !mutt_istr_startswith(uh->data, "bcc:") &&
435
0
             !mutt_istr_startswith(uh->data, "newsgroups:") &&
436
0
             !mutt_istr_startswith(uh->data, "followup-to:") &&
437
0
             !mutt_istr_startswith(uh->data, "x-comment-to:") &&
438
0
             !mutt_istr_startswith(uh->data, "supersedes:") &&
439
0
             !mutt_istr_startswith(uh->data, "subject:") &&
440
0
             !mutt_istr_startswith(uh->data, "return-path:"))
441
0
    {
442
0
      mutt_list_insert_tail(&env->userhdrs, mutt_str_dup(uh->data));
443
0
    }
444
0
  }
445
0
}
446
447
/**
448
 * mutt_forward_intro - Add the "start of forwarded message" text
449
 * @param e   Email
450
 * @param sub Config Subset
451
 * @param fp  File to write to
452
 */
453
void mutt_forward_intro(struct Email *e, FILE *fp, struct ConfigSubset *sub)
454
0
{
455
0
  const struct Expando *c_forward_attribution_intro = cs_subset_expando(sub, "forward_attribution_intro");
456
0
  if (!c_forward_attribution_intro || !fp)
457
0
    return;
458
459
0
  const char *const c_attribution_locale = cs_subset_string(sub, "attribution_locale");
460
461
0
  struct Buffer *buf = buf_pool_get();
462
0
  setlocale(LC_TIME, NONULL(c_attribution_locale));
463
0
  mutt_make_string(buf, -1, c_forward_attribution_intro, NULL, -1, e,
464
0
                   MUTT_FORMAT_NO_FLAGS, NULL);
465
0
  setlocale(LC_TIME, "");
466
0
  fputs(buf_string(buf), fp);
467
0
  fputs("\n\n", fp);
468
0
  buf_pool_release(&buf);
469
0
}
470
471
/**
472
 * mutt_forward_trailer - Add a "end of forwarded message" text
473
 * @param e   Email
474
 * @param sub Config Subset
475
 * @param fp  File to write to
476
 */
477
void mutt_forward_trailer(struct Email *e, FILE *fp, struct ConfigSubset *sub)
478
0
{
479
0
  const struct Expando *c_forward_attribution_trailer = cs_subset_expando(sub, "forward_attribution_trailer");
480
0
  if (!c_forward_attribution_trailer || !fp)
481
0
    return;
482
483
0
  const char *const c_attribution_locale = cs_subset_string(sub, "attribution_locale");
484
485
0
  struct Buffer *buf = buf_pool_get();
486
0
  setlocale(LC_TIME, NONULL(c_attribution_locale));
487
0
  mutt_make_string(buf, -1, c_forward_attribution_trailer, NULL, -1, e,
488
0
                   MUTT_FORMAT_NO_FLAGS, NULL);
489
0
  setlocale(LC_TIME, "");
490
0
  fputc('\n', fp);
491
0
  fputs(buf_string(buf), fp);
492
0
  fputc('\n', fp);
493
0
  buf_pool_release(&buf);
494
0
}
495
496
/**
497
 * include_forward - Write out a forwarded message
498
 * @param m      Mailbox
499
 * @param e      Email
500
 * @param fp_out File to write to
501
 * @param sub    Config Subset
502
 * @retval  0 Success
503
 * @retval -1 Failure
504
 */
505
static int include_forward(struct Mailbox *m, struct Email *e, FILE *fp_out,
506
                           struct ConfigSubset *sub)
507
0
{
508
0
  CopyHeaderFlags chflags = CH_DECODE;
509
0
  CopyMessageFlags cmflags = MUTT_CM_NO_FLAGS;
510
511
0
  struct Message *msg = mx_msg_open(m, e);
512
0
  if (!msg)
513
0
  {
514
0
    return -1;
515
0
  }
516
0
  mutt_parse_mime_message(e, msg->fp);
517
0
  mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
518
519
0
  const bool c_forward_decode = cs_subset_bool(sub, "forward_decode");
520
0
  if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT) && c_forward_decode)
521
0
  {
522
    /* make sure we have the user's passphrase before proceeding... */
523
0
    if (!crypt_valid_passphrase(e->security))
524
0
    {
525
0
      mx_msg_close(m, &msg);
526
0
      return -1;
527
0
    }
528
0
  }
529
530
0
  mutt_forward_intro(e, fp_out, sub);
531
532
0
  if (c_forward_decode)
533
0
  {
534
0
    cmflags |= MUTT_CM_DECODE | MUTT_CM_CHARCONV;
535
536
0
    const bool c_weed = cs_subset_bool(sub, "weed");
537
0
    if (c_weed)
538
0
    {
539
0
      chflags |= CH_WEED | CH_REORDER;
540
0
      cmflags |= MUTT_CM_WEED;
541
0
    }
542
0
  }
543
544
0
  const bool c_forward_quote = cs_subset_bool(sub, "forward_quote");
545
0
  if (c_forward_quote)
546
0
    cmflags |= MUTT_CM_PREFIX;
547
548
0
  mutt_copy_message(fp_out, e, msg, cmflags, chflags, 0);
549
0
  mx_msg_close(m, &msg);
550
0
  mutt_forward_trailer(e, fp_out, sub);
551
0
  return 0;
552
0
}
553
554
/**
555
 * inline_forward_attachments - Add attachments to an email, inline
556
 * @param[in]  m        Mailbox
557
 * @param[in]  e        Current Email
558
 * @param[out] plast    Pointer to the last Attachment
559
 * @param[out] forwardq Result of asking the user to forward the attachments, e.g. #MUTT_YES
560
 * @param[in]  sub      Config Subset
561
 * @retval  0 Success
562
 * @retval -1 Error
563
 */
564
static int inline_forward_attachments(struct Mailbox *m, struct Email *e,
565
                                      struct Body ***plast, enum QuadOption *forwardq,
566
                                      struct ConfigSubset *sub)
567
0
{
568
0
  struct Body **last = *plast;
569
0
  struct Body *body = NULL;
570
0
  struct AttachCtx *actx = NULL;
571
0
  int rc = 0, i;
572
573
0
  struct Message *msg = mx_msg_open(m, e);
574
0
  if (!msg)
575
0
  {
576
0
    return -1;
577
0
  }
578
579
0
  mutt_parse_mime_message(e, msg->fp);
580
0
  mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
581
582
0
  actx = MUTT_MEM_CALLOC(1, struct AttachCtx);
583
0
  actx->email = e;
584
0
  actx->fp_root = msg->fp;
585
586
0
  mutt_generate_recvattach_list(actx, actx->email, actx->email->body,
587
0
                                actx->fp_root, -1, 0, 0);
588
589
0
  for (i = 0; i < actx->idxlen; i++)
590
0
  {
591
0
    body = actx->idx[i]->body;
592
0
    if ((body->type != TYPE_MULTIPART) && mutt_prefer_as_attachment(body) &&
593
0
        !((body->type == TYPE_APPLICATION) &&
594
0
          (mutt_istr_equal(body->subtype, "pgp-signature") ||
595
0
           mutt_istr_equal(body->subtype, "x-pkcs7-signature") ||
596
0
           mutt_istr_equal(body->subtype, "pkcs7-signature"))))
597
0
    {
598
      /* Ask the quadoption only once */
599
0
      if (*forwardq == MUTT_ABORT)
600
0
      {
601
        /* L10N: This is the prompt for $forward_attachments.
602
           When inline forwarding ($mime_forward answered "no"), this prompts
603
           whether to add non-decodable attachments from the original email.
604
           Text/plain parts and the like will already be included in the
605
           message contents, but other attachment, such as PDF files, will also
606
           be added as attachments to the new mail, if this is answered yes.  */
607
0
        *forwardq = query_quadoption(_("Forward attachments?"), sub, "forward_attachments");
608
0
        if (*forwardq != MUTT_YES)
609
0
        {
610
0
          if (*forwardq == -1)
611
0
            rc = -1;
612
0
          goto cleanup;
613
0
        }
614
0
      }
615
0
      if (mutt_body_copy(actx->idx[i]->fp, last, body) == -1)
616
0
      {
617
0
        rc = -1;
618
0
        goto cleanup;
619
0
      }
620
0
      last = &((*last)->next);
621
0
    }
622
0
  }
623
624
0
cleanup:
625
0
  *plast = last;
626
0
  mx_msg_close(m, &msg);
627
0
  mutt_actx_free(&actx);
628
0
  return rc;
629
0
}
630
631
/**
632
 * format_attribution - Format an attribution prefix/suffix
633
 * @param exp    Expando to format
634
 * @param e      Email
635
 * @param fp_out File to write to
636
 * @param sub    Config Subset
637
 */
638
static void format_attribution(const struct Expando *exp, struct Email *e,
639
                               FILE *fp_out, struct ConfigSubset *sub)
640
0
{
641
0
  if (!exp || !fp_out)
642
0
    return;
643
644
0
  const char *const c_attribution_locale = cs_subset_string(sub, "attribution_locale");
645
646
0
  struct Buffer *buf = buf_pool_get();
647
0
  setlocale(LC_TIME, NONULL(c_attribution_locale));
648
0
  mutt_make_string(buf, -1, exp, NULL, -1, e, MUTT_FORMAT_NO_FLAGS, NULL);
649
0
  setlocale(LC_TIME, "");
650
0
  fputs(buf_string(buf), fp_out);
651
0
  fputc('\n', fp_out);
652
0
  buf_pool_release(&buf);
653
0
}
654
655
/**
656
 * mutt_make_attribution_intro - Add "on DATE, PERSON wrote" header
657
 * @param e      Email
658
 * @param fp_out File to write to
659
 * @param sub    Config Subset
660
 */
661
void mutt_make_attribution_intro(struct Email *e, FILE *fp_out, struct ConfigSubset *sub)
662
0
{
663
0
  format_attribution(cs_subset_expando(sub, "attribution_intro"), e, fp_out, sub);
664
0
}
665
666
/**
667
 * mutt_make_attribution_trailer - Add suffix to replied email text
668
 * @param e      Email
669
 * @param fp_out File to write to
670
 * @param sub    Config Subset
671
 */
672
void mutt_make_attribution_trailer(struct Email *e, FILE *fp_out, struct ConfigSubset *sub)
673
0
{
674
0
  format_attribution(cs_subset_expando(sub, "attribution_trailer"), e, fp_out, sub);
675
0
}
676
677
/**
678
 * mutt_make_greeting - Add greetings string
679
 * @param e      Email
680
 * @param fp_out File to write to
681
 * @param sub    Config Subset
682
 *
683
 * @sa $greeting
684
 */
685
static void mutt_make_greeting(struct Email *e, FILE *fp_out, struct ConfigSubset *sub)
686
0
{
687
0
  const struct Expando *c_greeting = cs_subset_expando(sub, "greeting");
688
0
  if (!c_greeting || !fp_out)
689
0
    return;
690
691
0
  struct Buffer *buf = buf_pool_get();
692
693
0
  expando_filter(c_greeting, GreetingRenderCallbacks, e, TOKEN_NO_FLAGS,
694
0
                 buf->dsize, NeoMutt->env, buf);
695
696
0
  fputs(buf_string(buf), fp_out);
697
0
  fputc('\n', fp_out);
698
0
  buf_pool_release(&buf);
699
0
}
700
701
/**
702
 * include_reply - Generate the reply text for an email
703
 * @param m      Mailbox
704
 * @param e      Email
705
 * @param fp_out File to write to
706
 * @param sub    Config Subset
707
 * @retval  0 Success
708
 * @retval -1 Failure
709
 */
710
static int include_reply(struct Mailbox *m, struct Email *e, FILE *fp_out,
711
                         struct ConfigSubset *sub)
712
0
{
713
0
  CopyMessageFlags cmflags = MUTT_CM_PREFIX | MUTT_CM_DECODE | MUTT_CM_CHARCONV | MUTT_CM_REPLYING;
714
0
  CopyHeaderFlags chflags = CH_DECODE;
715
716
0
  if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT))
717
0
  {
718
    /* make sure we have the user's passphrase before proceeding... */
719
0
    if (!crypt_valid_passphrase(e->security))
720
0
      return -1;
721
0
  }
722
723
0
  struct Message *msg = mx_msg_open(m, e);
724
0
  if (!msg)
725
0
  {
726
0
    return -1;
727
0
  }
728
0
  mutt_parse_mime_message(e, msg->fp);
729
0
  mutt_message_hook(m, e, MUTT_MESSAGE_HOOK);
730
731
0
  mutt_make_attribution_intro(e, fp_out, sub);
732
733
0
  const bool c_header = cs_subset_bool(sub, "header");
734
0
  if (!c_header)
735
0
    cmflags |= MUTT_CM_NOHEADER;
736
737
0
  const bool c_weed = cs_subset_bool(sub, "weed");
738
0
  if (c_weed)
739
0
  {
740
0
    chflags |= CH_WEED | CH_REORDER;
741
0
    cmflags |= MUTT_CM_WEED;
742
0
  }
743
744
0
  mutt_copy_message(fp_out, e, msg, cmflags, chflags, 0);
745
0
  mx_msg_close(m, &msg);
746
747
0
  mutt_make_attribution_trailer(e, fp_out, sub);
748
749
0
  return 0;
750
0
}
751
752
/**
753
 * choose_default_to - Pick the best 'to:' value
754
 * @param from From Address
755
 * @param env  Envelope
756
 * @param sub  Config Subset
757
 * @retval ptr Addresses to use
758
 */
759
static const struct AddressList *choose_default_to(const struct Address *from,
760
                                                   const struct Envelope *env,
761
                                                   struct ConfigSubset *sub)
762
0
{
763
0
  const bool c_reply_self = cs_subset_bool(sub, "reply_self");
764
0
  if (!c_reply_self && mutt_addr_is_user(from))
765
0
  {
766
    /* mail is from the user, assume replying to recipients */
767
0
    return &env->to;
768
0
  }
769
0
  else
770
0
  {
771
0
    return &env->from;
772
0
  }
773
0
}
774
775
/**
776
 * default_to - Generate default email addresses
777
 * @param[in,out] to      'To' address
778
 * @param[in]     env     Envelope to populate
779
 * @param[in]     flags   Flags, see #SendFlags
780
 * @param[in]     hmfupto If true, add 'followup-to' address to 'to' address
781
 * @param[in]     sub     Config Subset
782
 * @retval  0 Success
783
 * @retval -1 Aborted
784
 */
785
static int default_to(struct AddressList *to, struct Envelope *env,
786
                      SendFlags flags, int hmfupto, struct ConfigSubset *sub)
787
0
{
788
0
  const struct Address *from = TAILQ_FIRST(&env->from);
789
0
  const struct Address *reply_to = TAILQ_FIRST(&env->reply_to);
790
791
0
  if (flags && !TAILQ_EMPTY(&env->mail_followup_to) && (hmfupto == MUTT_YES))
792
0
  {
793
0
    mutt_addrlist_copy(to, &env->mail_followup_to, true);
794
0
    return 0;
795
0
  }
796
797
  /* Exit now if we're setting up the default Cc list for list-reply
798
   * (only set if Mail-Followup-To is present and honoured).  */
799
0
  if (flags & SEND_LIST_REPLY)
800
0
    return 0;
801
802
0
  const struct AddressList *default_to = choose_default_to(from, env, sub);
803
804
0
  if (reply_to)
805
0
  {
806
0
    const bool from_is_reply_to = mutt_addr_cmp(from, reply_to);
807
0
    const bool multiple_reply_to = reply_to &&
808
0
                                   TAILQ_NEXT(TAILQ_FIRST(&env->reply_to), entries);
809
810
0
    const bool c_ignore_list_reply_to = cs_subset_bool(sub, "ignore_list_reply_to");
811
0
    const enum QuadOption c_reply_to = cs_subset_quad(sub, "reply_to");
812
0
    if ((from_is_reply_to && !multiple_reply_to && !reply_to->personal) ||
813
0
        (c_ignore_list_reply_to && mutt_is_mail_list(reply_to) &&
814
0
         (mutt_addrlist_search(&env->to, reply_to) || mutt_addrlist_search(&env->cc, reply_to))))
815
0
    {
816
      /* If the Reply-To: address is a mailing list, assume that it was
817
       * put there by the mailing list, and use the From: address
818
       *
819
       * We also take the from header if our correspondent has a reply-to
820
       * header which is identical to the electronic mail address given
821
       * in his From header, and the reply-to has no display-name.  */
822
0
      mutt_addrlist_copy(to, &env->from, false);
823
0
    }
824
0
    else if (!(from_is_reply_to && !multiple_reply_to) && (c_reply_to != MUTT_YES))
825
0
    {
826
0
      char prompt[256] = { 0 };
827
      /* There are quite a few mailing lists which set the Reply-To:
828
       * header field to the list address, which makes it quite impossible
829
       * to send a message to only the sender of the message.  This
830
       * provides a way to do that.  */
831
      /* L10N: Asks whether the user respects the reply-to header.
832
         If she says no, neomutt will reply to the from header's address instead. */
833
0
      snprintf(prompt, sizeof(prompt), _("Reply to %s%s?"),
834
0
               buf_string(reply_to->mailbox), multiple_reply_to ? ",..." : "");
835
0
      switch (query_quadoption(prompt, sub, "reply_to"))
836
0
      {
837
0
        case MUTT_YES:
838
0
          mutt_addrlist_copy(to, &env->reply_to, false);
839
0
          break;
840
841
0
        case MUTT_NO:
842
0
          mutt_addrlist_copy(to, default_to, false);
843
0
          break;
844
845
0
        default:
846
0
          return -1; /* abort */
847
0
      }
848
0
    }
849
0
    else
850
0
    {
851
0
      mutt_addrlist_copy(to, &env->reply_to, false);
852
0
    }
853
0
  }
854
0
  else
855
0
  {
856
0
    mutt_addrlist_copy(to, default_to, false);
857
0
  }
858
859
0
  return 0;
860
0
}
861
862
/**
863
 * mutt_fetch_recips - Generate recpients for a reply email
864
 * @param out   Envelope to populate
865
 * @param in    Envelope of source email
866
 * @param flags Flags, see #SendFlags
867
 * @param sub   Config Subset
868
 * @retval  0 Success
869
 * @retval -1 Failure
870
 */
871
int mutt_fetch_recips(struct Envelope *out, struct Envelope *in,
872
                      SendFlags flags, struct ConfigSubset *sub)
873
0
{
874
0
  enum QuadOption hmfupto = MUTT_ABORT;
875
0
  const struct Address *followup_to = TAILQ_FIRST(&in->mail_followup_to);
876
877
0
  if ((flags & (SEND_LIST_REPLY | SEND_GROUP_REPLY | SEND_GROUP_CHAT_REPLY)) && followup_to)
878
0
  {
879
0
    char prompt[256] = { 0 };
880
0
    snprintf(prompt, sizeof(prompt), _("Follow-up to %s%s?"),
881
0
             buf_string(followup_to->mailbox),
882
0
             TAILQ_NEXT(TAILQ_FIRST(&in->mail_followup_to), entries) ? ",..." : "");
883
884
0
    hmfupto = query_quadoption(prompt, sub, "honor_followup_to");
885
0
    if (hmfupto == MUTT_ABORT)
886
0
      return -1;
887
0
  }
888
889
0
  if (flags & SEND_LIST_REPLY)
890
0
  {
891
0
    add_mailing_lists(&out->to, &in->to, &in->cc);
892
893
0
    if (followup_to && (hmfupto == MUTT_YES) &&
894
0
        (default_to(&out->cc, in, flags & SEND_LIST_REPLY, (hmfupto == MUTT_YES), sub) == MUTT_ABORT))
895
0
    {
896
0
      return -1; /* abort */
897
0
    }
898
0
  }
899
0
  else if (flags & SEND_TO_SENDER)
900
0
  {
901
0
    mutt_addrlist_copy(&out->to, &in->from, false);
902
0
  }
903
0
  else
904
0
  {
905
0
    if (default_to(&out->to, in, flags & (SEND_GROUP_REPLY | SEND_GROUP_CHAT_REPLY),
906
0
                   (hmfupto == MUTT_YES), sub) == -1)
907
0
    {
908
0
      return -1; /* abort */
909
0
    }
910
911
0
    if ((flags & (SEND_GROUP_REPLY | SEND_GROUP_CHAT_REPLY)) &&
912
0
        (!followup_to || (hmfupto != MUTT_YES)))
913
0
    {
914
      /* if(!mutt_addr_is_user(in->to)) */
915
0
      if (flags & SEND_GROUP_REPLY)
916
0
        mutt_addrlist_copy(&out->cc, &in->to, true);
917
0
      else
918
0
        mutt_addrlist_copy(&out->to, &in->to, true);
919
0
      mutt_addrlist_copy(&out->cc, &in->cc, true);
920
0
    }
921
0
  }
922
0
  return 0;
923
0
}
924
925
/**
926
 * add_references - Add the email's references to a list
927
 * @param head List of references
928
 * @param env    Envelope of message
929
 */
930
static void add_references(struct ListHead *head, struct Envelope *env)
931
0
{
932
0
  struct ListHead *src = STAILQ_EMPTY(&env->references) ? &env->in_reply_to : &env->references;
933
0
  mutt_list_copy_tail(head, src);
934
0
}
935
936
/**
937
 * add_message_id - Add the email's message ID to a list
938
 * @param head List of message IDs
939
 * @param env  Envelope of message
940
 */
941
static void add_message_id(struct ListHead *head, struct Envelope *env)
942
0
{
943
0
  if (env->message_id)
944
0
  {
945
0
    mutt_list_insert_head(head, mutt_str_dup(env->message_id));
946
0
  }
947
0
}
948
949
/**
950
 * mutt_fix_reply_recipients - Remove duplicate recipients
951
 * @param env Envelope to fix
952
 * @param sub Config Subset
953
 */
954
void mutt_fix_reply_recipients(struct Envelope *env, struct ConfigSubset *sub)
955
0
{
956
0
  const bool c_me_too = cs_subset_bool(sub, "me_too");
957
0
  if (!c_me_too)
958
0
  {
959
0
    const bool c_reply_self = cs_subset_bool(sub, "reply_self");
960
961
    /* the order is important here.  do the CC: first so that if the
962
     * the user is the only recipient, it ends up on the TO: field */
963
0
    remove_user(&env->cc, TAILQ_EMPTY(&env->to));
964
0
    remove_user(&env->to, TAILQ_EMPTY(&env->cc) || c_reply_self);
965
0
  }
966
967
  /* the CC field can get cluttered, especially with lists */
968
0
  mutt_addrlist_dedupe(&env->to);
969
0
  mutt_addrlist_dedupe(&env->cc);
970
0
  mutt_addrlist_remove_xrefs(&env->to, &env->cc);
971
972
0
  if (!TAILQ_EMPTY(&env->cc) && TAILQ_EMPTY(&env->to))
973
0
  {
974
0
    TAILQ_SWAP(&env->to, &env->cc, Address, entries);
975
0
  }
976
0
}
977
978
/**
979
 * mutt_make_forward_subject - Create a subject for a forwarded email
980
 * @param env Envelope for result
981
 * @param e   Email
982
 * @param sub Config Subset
983
 */
984
void mutt_make_forward_subject(struct Envelope *env, struct Email *e, struct ConfigSubset *sub)
985
0
{
986
0
  if (!env)
987
0
    return;
988
989
0
  const struct Expando *c_forward_format = cs_subset_expando(sub, "forward_format");
990
991
0
  struct Buffer *buf = buf_pool_get();
992
  /* set the default subject for the message. */
993
0
  mutt_make_string(buf, -1, c_forward_format, NULL, -1, e, MUTT_FORMAT_NO_FLAGS, NULL);
994
0
  mutt_env_set_subject(env, buf_string(buf));
995
0
  buf_pool_release(&buf);
996
0
}
997
998
/**
999
 * mutt_make_misc_reply_headers - Set subject for a reply
1000
 * @param env    Envelope for result
1001
 * @param env_cur Envelope of source email
1002
 * @param sub    Config Subset
1003
 */
1004
void mutt_make_misc_reply_headers(struct Envelope *env, struct Envelope *env_cur,
1005
                                  struct ConfigSubset *sub)
1006
0
{
1007
0
  if (!env || !env_cur)
1008
0
    return;
1009
1010
  /* This takes precedence over a subject that might have
1011
   * been taken from a List-Post header.  Is that correct?  */
1012
0
  if (env_cur->real_subj)
1013
0
  {
1014
0
    char *subj = NULL;
1015
0
    mutt_str_asprintf(&subj, "Re: %s", env_cur->real_subj);
1016
0
    mutt_env_set_subject(env, subj);
1017
0
    FREE(&subj);
1018
0
  }
1019
0
  else if (!env->subject)
1020
0
  {
1021
0
    const char *const c_empty_subject = cs_subset_string(sub, "empty_subject");
1022
0
    mutt_env_set_subject(env, c_empty_subject);
1023
0
  }
1024
0
}
1025
1026
/**
1027
 * mutt_add_to_reference_headers - Generate references for a reply email
1028
 * @param env     Envelope for result
1029
 * @param env_cur Envelope of source email
1030
 * @param sub     Config Subset
1031
 */
1032
void mutt_add_to_reference_headers(struct Envelope *env, struct Envelope *env_cur,
1033
                                   struct ConfigSubset *sub)
1034
0
{
1035
0
  add_references(&env->references, env_cur);
1036
0
  add_message_id(&env->references, env_cur);
1037
0
  add_message_id(&env->in_reply_to, env_cur);
1038
1039
0
  const bool c_x_comment_to = cs_subset_bool(sub, "x_comment_to");
1040
0
  if (OptNewsSend && c_x_comment_to && !TAILQ_EMPTY(&env_cur->from))
1041
0
    env->x_comment_to = mutt_str_dup(mutt_get_name(TAILQ_FIRST(&env_cur->from)));
1042
0
}
1043
1044
/**
1045
 * make_reference_headers - Generate reference headers for an email
1046
 * @param ea  Array of source Emails
1047
 * @param env Envelope for result
1048
 * @param sub Config Subset
1049
 */
1050
static void make_reference_headers(struct EmailArray *ea, struct Envelope *env,
1051
                                   struct ConfigSubset *sub)
1052
0
{
1053
0
  if (!ea || !env || ARRAY_EMPTY(ea))
1054
0
    return;
1055
1056
0
  struct Email **ep = NULL;
1057
0
  ARRAY_FOREACH(ep, ea)
1058
0
  {
1059
0
    struct Email *e = *ep;
1060
0
    mutt_add_to_reference_headers(env, e->env, sub);
1061
0
  }
1062
1063
  /* if there's more than entry in In-Reply-To (i.e. message has multiple
1064
   * parents), don't generate a References: header as it's discouraged by
1065
   * RFC2822, sect. 3.6.4 */
1066
0
  if ((ARRAY_SIZE(ea) > 1) && !STAILQ_EMPTY(&env->in_reply_to) &&
1067
0
      STAILQ_NEXT(STAILQ_FIRST(&env->in_reply_to), entries))
1068
0
  {
1069
0
    mutt_list_free(&env->references);
1070
0
  }
1071
0
}
1072
1073
/**
1074
 * envelope_defaults - Fill in some defaults for a new email
1075
 * @param env   Envelope for result
1076
 * @param ea    Array of Emails to use
1077
 * @param flags Flags, see #SendFlags
1078
 * @param sub   Config Subset
1079
 * @retval  0 Success
1080
 * @retval -1 Failure
1081
 */
1082
static int envelope_defaults(struct Envelope *env, struct EmailArray *ea,
1083
                             SendFlags flags, struct ConfigSubset *sub)
1084
0
{
1085
0
  if (!ea || ARRAY_EMPTY(ea))
1086
0
    return -1;
1087
1088
0
  struct Email *e_cur = *ARRAY_GET(ea, 0);
1089
0
  bool single = (ARRAY_SIZE(ea) == 1);
1090
1091
0
  struct Envelope *env_cur = e_cur->env;
1092
0
  if (!env_cur)
1093
0
    return -1;
1094
1095
0
  if (flags & (SEND_REPLY | SEND_TO_SENDER))
1096
0
  {
1097
0
    if ((flags & SEND_NEWS))
1098
0
    {
1099
      /* in case followup set Newsgroups: with Followup-To: if it present */
1100
0
      if (!env->newsgroups && !mutt_istr_equal(env_cur->followup_to, "poster"))
1101
0
      {
1102
0
        env->newsgroups = mutt_str_dup(env_cur->followup_to);
1103
0
      }
1104
0
    }
1105
0
    else if (!single)
1106
0
    {
1107
0
      struct Email **ep = NULL;
1108
0
      ARRAY_FOREACH(ep, ea)
1109
0
      {
1110
0
        struct Email *e = *ep;
1111
0
        if (mutt_fetch_recips(env, e->env, flags, sub) == -1)
1112
0
          return -1;
1113
0
      }
1114
0
    }
1115
0
    else if (mutt_fetch_recips(env, env_cur, flags, sub) == -1)
1116
0
    {
1117
0
      return -1;
1118
0
    }
1119
1120
0
    if ((flags & SEND_LIST_REPLY) && TAILQ_EMPTY(&env->to))
1121
0
    {
1122
0
      mutt_error(_("No mailing lists found"));
1123
0
      return -1;
1124
0
    }
1125
1126
0
    if (flags & SEND_REPLY)
1127
0
    {
1128
0
      mutt_make_misc_reply_headers(env, env_cur, sub);
1129
0
      make_reference_headers(ea, env, sub);
1130
0
    }
1131
0
  }
1132
0
  else if (flags & SEND_FORWARD)
1133
0
  {
1134
0
    mutt_make_forward_subject(env, e_cur, sub);
1135
1136
0
    const bool c_forward_references = cs_subset_bool(sub, "forward_references");
1137
0
    if (c_forward_references)
1138
0
      make_reference_headers(ea, env, sub);
1139
0
  }
1140
1141
0
  return 0;
1142
0
}
1143
1144
/**
1145
 * generate_body - Create a new email body
1146
 * @param fp_tmp Stream for outgoing message
1147
 * @param e      Email for outgoing message
1148
 * @param flags  Compose mode, see #SendFlags
1149
 * @param m      Mailbox
1150
 * @param ea     Array of Emails to use
1151
 * @param sub    Config Subset
1152
 * @retval  0 Success
1153
 * @retval -1 Error
1154
 */
1155
static int generate_body(FILE *fp_tmp, struct Email *e, SendFlags flags,
1156
                         struct Mailbox *m, struct EmailArray *ea, struct ConfigSubset *sub)
1157
0
{
1158
  /* An EmailList is required for replying and forwarding */
1159
0
  if (!ea && (flags & (SEND_REPLY | SEND_FORWARD)))
1160
0
    return -1;
1161
1162
0
  if (flags & SEND_REPLY)
1163
0
  {
1164
0
    enum QuadOption ans = query_quadoption(_("Include message in reply?"), sub, "include");
1165
0
    if (ans == MUTT_ABORT)
1166
0
      return -1;
1167
1168
0
    if (ans == MUTT_YES)
1169
0
    {
1170
0
      mutt_message(_("Including quoted message..."));
1171
0
      struct Email **ep = NULL;
1172
0
      size_t count = ARRAY_SIZE(ea) - 1;
1173
0
      ARRAY_FOREACH(ep, ea)
1174
0
      {
1175
0
        if (include_reply(m, *ep, fp_tmp, sub) == -1)
1176
0
        {
1177
0
          mutt_error(_("Could not include all requested messages"));
1178
0
          return -1;
1179
0
        }
1180
0
        if (ARRAY_FOREACH_IDX_ep < count)
1181
0
        {
1182
0
          fputc('\n', fp_tmp);
1183
0
        }
1184
0
      }
1185
0
    }
1186
0
  }
1187
0
  else if (flags & SEND_FORWARD)
1188
0
  {
1189
0
    enum QuadOption ans = query_quadoption(_("Forward as attachment?"), sub, "mime_forward");
1190
0
    if (ans == MUTT_YES)
1191
0
    {
1192
0
      struct Body *last = e->body;
1193
1194
0
      mutt_message(_("Preparing forwarded message..."));
1195
1196
0
      while (last && last->next)
1197
0
        last = last->next;
1198
1199
0
      struct Email **ep = NULL;
1200
0
      ARRAY_FOREACH(ep, ea)
1201
0
      {
1202
0
        struct Body *tmp = mutt_make_message_attach(m, *ep, false, sub);
1203
0
        if (last)
1204
0
        {
1205
0
          last->next = tmp;
1206
0
          last = tmp;
1207
0
        }
1208
0
        else
1209
0
        {
1210
0
          last = tmp;
1211
0
          e->body = tmp;
1212
0
        }
1213
0
      }
1214
0
    }
1215
0
    else if (ans != MUTT_ABORT)
1216
0
    {
1217
0
      enum QuadOption forwardq = MUTT_ABORT;
1218
0
      struct Body **last = NULL;
1219
1220
0
      const bool c_forward_decode = cs_subset_bool(sub, "forward_decode");
1221
0
      const enum QuadOption c_forward_attachments = cs_subset_quad(sub, "forward_attachments");
1222
0
      if (c_forward_decode && (c_forward_attachments != MUTT_NO))
1223
0
      {
1224
0
        last = &e->body;
1225
0
        while (*last)
1226
0
          last = &((*last)->next);
1227
0
      }
1228
1229
0
      struct Email **ep = NULL;
1230
0
      ARRAY_FOREACH(ep, ea)
1231
0
      {
1232
0
        struct Email *e_cur = *ep;
1233
0
        include_forward(m, e_cur, fp_tmp, sub);
1234
0
        if (c_forward_decode && (c_forward_attachments != MUTT_NO))
1235
0
        {
1236
0
          if (inline_forward_attachments(m, e_cur, &last, &forwardq, sub) != 0)
1237
0
            return -1;
1238
0
        }
1239
0
      }
1240
0
    }
1241
0
    else
1242
0
    {
1243
0
      return -1;
1244
0
    }
1245
0
  }
1246
0
  else if (((WithCrypto & APPLICATION_PGP) != 0) && (flags & SEND_KEY))
1247
0
  {
1248
0
    struct Body *b = NULL;
1249
1250
0
    if (((WithCrypto & APPLICATION_PGP) != 0) && !(b = crypt_pgp_make_key_attachment()))
1251
0
    {
1252
0
      return -1;
1253
0
    }
1254
1255
0
    b->next = e->body;
1256
0
    e->body = b;
1257
0
  }
1258
1259
0
  mutt_clear_error();
1260
1261
0
  return 0;
1262
0
}
1263
1264
/**
1265
 * mutt_set_followup_to - Set followup-to field
1266
 * @param env Envelope to modify
1267
 * @param sub Config Subset
1268
 */
1269
void mutt_set_followup_to(struct Envelope *env, struct ConfigSubset *sub)
1270
0
{
1271
  /* Only generate the Mail-Followup-To if the user has requested it, and
1272
   * it hasn't already been set */
1273
1274
0
  const bool c_followup_to = cs_subset_bool(sub, "followup_to");
1275
0
  if (!c_followup_to)
1276
0
    return;
1277
0
  if (OptNewsSend)
1278
0
  {
1279
0
    if (!env->followup_to && env->newsgroups && (strrchr(env->newsgroups, ',')))
1280
0
      env->followup_to = mutt_str_dup(env->newsgroups);
1281
0
    return;
1282
0
  }
1283
1284
0
  if (TAILQ_EMPTY(&env->mail_followup_to))
1285
0
  {
1286
0
    if (mutt_is_list_recipient(false, env))
1287
0
    {
1288
      /* this message goes to known mailing lists, so create a proper
1289
       * mail-followup-to header */
1290
1291
0
      mutt_addrlist_copy(&env->mail_followup_to, &env->to, false);
1292
0
      mutt_addrlist_copy(&env->mail_followup_to, &env->cc, true);
1293
0
    }
1294
1295
    /* remove ourselves from the mail-followup-to header */
1296
0
    remove_user(&env->mail_followup_to, false);
1297
1298
    /* If we are not subscribed to any of the lists in question, re-add
1299
     * ourselves to the mail-followup-to header.  The mail-followup-to header
1300
     * generated is a no-op with group-reply, but makes sure list-reply has the
1301
     * desired effect.  */
1302
1303
0
    if (!TAILQ_EMPTY(&env->mail_followup_to) &&
1304
0
        !mutt_is_subscribed_list_recipient(false, env))
1305
0
    {
1306
0
      struct AddressList *al = NULL;
1307
0
      if (!TAILQ_EMPTY(&env->reply_to))
1308
0
        al = &env->reply_to;
1309
0
      else if (!TAILQ_EMPTY(&env->from))
1310
0
        al = &env->from;
1311
1312
0
      if (al)
1313
0
      {
1314
0
        struct Address *a = NULL;
1315
0
        TAILQ_FOREACH_REVERSE(a, al, AddressList, entries)
1316
0
        {
1317
0
          mutt_addrlist_prepend(&env->mail_followup_to, mutt_addr_copy(a));
1318
0
        }
1319
0
      }
1320
0
      else
1321
0
      {
1322
0
        mutt_addrlist_prepend(&env->mail_followup_to, mutt_default_from(sub));
1323
0
      }
1324
0
    }
1325
1326
0
    mutt_addrlist_dedupe(&env->mail_followup_to);
1327
0
  }
1328
0
}
1329
1330
/**
1331
 * set_reverse_name - Try to set the 'from' field from the recipients
1332
 * @param al  AddressList to prepend the found address
1333
 * @param env Envelope to use
1334
 * @param sub Config Subset
1335
 *
1336
 * Look through the recipients of the message we are replying to, and if we
1337
 * find an address that matches $alternates, we use that as the default from
1338
 * field
1339
 */
1340
static void set_reverse_name(struct AddressList *al, struct Envelope *env,
1341
                             struct ConfigSubset *sub)
1342
0
{
1343
0
  struct Address *a = NULL;
1344
0
  if (TAILQ_EMPTY(al))
1345
0
  {
1346
0
    TAILQ_FOREACH(a, &env->to, entries)
1347
0
    {
1348
0
      if (mutt_addr_is_user(a))
1349
0
      {
1350
0
        mutt_addrlist_append(al, mutt_addr_copy(a));
1351
0
        break;
1352
0
      }
1353
0
    }
1354
0
  }
1355
1356
0
  if (TAILQ_EMPTY(al))
1357
0
  {
1358
0
    TAILQ_FOREACH(a, &env->cc, entries)
1359
0
    {
1360
0
      if (mutt_addr_is_user(a))
1361
0
      {
1362
0
        mutt_addrlist_append(al, mutt_addr_copy(a));
1363
0
        break;
1364
0
      }
1365
0
    }
1366
0
  }
1367
1368
0
  if (TAILQ_EMPTY(al))
1369
0
  {
1370
0
    struct Address *from = TAILQ_FIRST(&env->from);
1371
0
    if (from && mutt_addr_is_user(from))
1372
0
    {
1373
0
      mutt_addrlist_append(al, mutt_addr_copy(from));
1374
0
    }
1375
0
  }
1376
1377
0
  if (!TAILQ_EMPTY(al))
1378
0
  {
1379
    /* when $reverse_real_name is not set, clear the personal name so that it
1380
     * may be set via a reply- or send-hook.  */
1381
1382
0
    const bool c_reverse_real_name = cs_subset_bool(sub, "reverse_real_name");
1383
0
    if (!c_reverse_real_name)
1384
0
      FREE(&TAILQ_FIRST(al)->personal);
1385
0
  }
1386
0
}
1387
1388
/**
1389
 * mutt_default_from - Get a default 'from' Address
1390
 * @param sub Config Subset
1391
 * @retval ptr Newly allocated Address
1392
 */
1393
struct Address *mutt_default_from(struct ConfigSubset *sub)
1394
0
{
1395
  /* Note: We let $from override $real_name here.
1396
   *       Is this the right thing to do?
1397
   */
1398
1399
0
  const struct Address *c_from = cs_subset_address(sub, "from");
1400
0
  if (c_from)
1401
0
  {
1402
0
    return mutt_addr_copy(c_from);
1403
0
  }
1404
1405
0
  char domain[1024] = { 0 };
1406
0
  const char *mailbox = NeoMutt->username;
1407
0
  const bool c_use_domain = cs_subset_bool(sub, "use_domain");
1408
0
  if (c_use_domain)
1409
0
  {
1410
0
    snprintf(domain, sizeof(domain), "%s@%s", NONULL(NeoMutt->username),
1411
0
             NONULL(mutt_fqdn(true, sub)));
1412
0
    mailbox = domain;
1413
0
  }
1414
1415
0
  return mutt_addr_create(NULL, mailbox);
1416
0
}
1417
1418
/**
1419
 * invoke_mta - Send an email
1420
 * @param m   Mailbox
1421
 * @param e   Email
1422
 * @param sub Config Subset
1423
 * @retval  0 Success
1424
 * @retval -1 Failure
1425
 */
1426
static int invoke_mta(struct Mailbox *m, struct Email *e, struct ConfigSubset *sub)
1427
0
{
1428
0
  struct Buffer *tempfile = NULL;
1429
0
  int rc = -1;
1430
1431
  /* Write out the message in MIME form. */
1432
0
  tempfile = buf_pool_get();
1433
0
  buf_mktemp(tempfile);
1434
0
  FILE *fp_tmp = mutt_file_fopen(buf_string(tempfile), "w");
1435
0
  if (!fp_tmp)
1436
0
    goto cleanup;
1437
1438
0
  const bool c_write_bcc = cs_subset_bool(sub, "write_bcc");
1439
0
  const char *const c_smtp_url = cs_subset_string(sub, "smtp_url");
1440
0
  if (c_smtp_url)
1441
0
    cs_subset_str_native_set(sub, "write_bcc", false, NULL);
1442
1443
0
  mutt_rfc822_write_header(fp_tmp, e->env, e->body, MUTT_WRITE_HEADER_NORMAL,
1444
0
                           false, mutt_should_hide_protected_subject(e), sub);
1445
1446
0
  cs_subset_str_native_set(sub, "write_bcc", c_write_bcc, NULL);
1447
1448
0
  fputc('\n', fp_tmp); /* tie off the header. */
1449
1450
0
  if ((mutt_write_mime_body(e->body, fp_tmp, sub) == -1))
1451
0
    goto cleanup;
1452
1453
0
  if (mutt_file_fclose(&fp_tmp) != 0)
1454
0
  {
1455
0
    mutt_perror("%s", buf_string(tempfile));
1456
0
    unlink(buf_string(tempfile));
1457
0
    goto cleanup;
1458
0
  }
1459
1460
0
  if (OptNewsSend)
1461
0
    goto sendmail;
1462
1463
0
  if (c_smtp_url)
1464
0
  {
1465
0
    rc = mutt_smtp_send(&e->env->from, &e->env->to, &e->env->cc, &e->env->bcc,
1466
0
                        buf_string(tempfile), (e->body->encoding == ENC_8BIT), sub);
1467
0
    goto cleanup;
1468
0
  }
1469
1470
0
sendmail:
1471
0
  rc = mutt_invoke_sendmail(m, &e->env->from, &e->env->to, &e->env->cc, &e->env->bcc,
1472
0
                            buf_string(tempfile), (e->body->encoding == ENC_8BIT), sub);
1473
0
cleanup:
1474
0
  if (fp_tmp)
1475
0
  {
1476
0
    mutt_file_fclose(&fp_tmp);
1477
0
    unlink(buf_string(tempfile));
1478
0
  }
1479
0
  buf_pool_release(&tempfile);
1480
0
  return rc;
1481
0
}
1482
1483
/**
1484
 * mutt_encode_descriptions - RFC2047 encode the content-descriptions
1485
 * @param b       Body of email
1486
 * @param recurse If true, encode children parts
1487
 * @param sub     Config Subset
1488
 */
1489
void mutt_encode_descriptions(struct Body *b, bool recurse, struct ConfigSubset *sub)
1490
0
{
1491
0
  const struct Slist *const c_send_charset = cs_subset_slist(sub, "send_charset");
1492
0
  for (struct Body *t = b; t; t = t->next)
1493
0
  {
1494
0
    if (t->description)
1495
0
    {
1496
0
      rfc2047_encode(&t->description, NULL, sizeof("Content-Description:"), c_send_charset);
1497
0
    }
1498
0
    if (recurse && t->parts)
1499
0
      mutt_encode_descriptions(t->parts, recurse, sub);
1500
0
  }
1501
0
}
1502
1503
/**
1504
 * decode_descriptions - RFC2047 decode them in case of an error
1505
 * @param b MIME parts to decode
1506
 */
1507
static void decode_descriptions(struct Body *b)
1508
0
{
1509
0
  for (struct Body *t = b; t; t = t->next)
1510
0
  {
1511
0
    if (t->description)
1512
0
    {
1513
0
      rfc2047_decode(&t->description);
1514
0
    }
1515
0
    if (t->parts)
1516
0
      decode_descriptions(t->parts);
1517
0
  }
1518
0
}
1519
1520
/**
1521
 * fix_end_of_file - Ensure a file ends with a linefeed
1522
 * @param data Name of file to fix
1523
 */
1524
static void fix_end_of_file(const char *data)
1525
0
{
1526
0
  FILE *fp = mutt_file_fopen(data, "a+");
1527
0
  if (!fp)
1528
0
    return;
1529
1530
0
  if ((mutt_file_get_size_fp(fp) > 0) && mutt_file_seek(fp, -1, SEEK_END))
1531
0
  {
1532
0
    int c = fgetc(fp);
1533
0
    if (c != '\n')
1534
0
      fputc('\n', fp);
1535
0
  }
1536
0
  mutt_file_fclose(&fp);
1537
0
}
1538
1539
/**
1540
 * mutt_resend_message - Resend an email
1541
 * @param fp    File containing email
1542
 * @param m     Mailbox
1543
 * @param e_cur Email to resend
1544
 * @param sub   Config Subset
1545
 * @retval  0 Message was successfully sent
1546
 * @retval -1 Message was aborted or an error occurred
1547
 * @retval  1 Message was postponed
1548
 */
1549
int mutt_resend_message(FILE *fp, struct Mailbox *m, struct Email *e_cur,
1550
                        struct ConfigSubset *sub)
1551
0
{
1552
0
  struct Email *e_new = email_new();
1553
1554
0
  if (mutt_prepare_template(fp, m, e_new, e_cur, true) < 0)
1555
0
  {
1556
0
    email_free(&e_new);
1557
0
    return -1;
1558
0
  }
1559
1560
0
  if (WithCrypto)
1561
0
  {
1562
    /* mutt_prepare_template doesn't always flip on an application bit.
1563
     * so fix that here */
1564
0
    if (!(e_new->security & (APPLICATION_SMIME | APPLICATION_PGP)))
1565
0
    {
1566
0
      const bool c_smime_is_default = cs_subset_bool(sub, "smime_is_default");
1567
0
      if (((WithCrypto & APPLICATION_SMIME) != 0) && c_smime_is_default)
1568
0
        e_new->security |= APPLICATION_SMIME;
1569
0
      else if (WithCrypto & APPLICATION_PGP)
1570
0
        e_new->security |= APPLICATION_PGP;
1571
0
      else
1572
0
        e_new->security |= APPLICATION_SMIME;
1573
0
    }
1574
1575
0
    const bool c_crypt_opportunistic_encrypt = cs_subset_bool(sub, "crypt_opportunistic_encrypt");
1576
0
    if (c_crypt_opportunistic_encrypt)
1577
0
    {
1578
0
      e_new->security |= SEC_OPPENCRYPT;
1579
0
      crypt_opportunistic_encrypt(e_new);
1580
0
    }
1581
0
  }
1582
1583
0
  struct EmailArray ea = ARRAY_HEAD_INITIALIZER;
1584
0
  ARRAY_ADD(&ea, e_cur);
1585
0
  int rc = mutt_send_message(SEND_RESEND, e_new, NULL, m, &ea, sub);
1586
0
  ARRAY_FREE(&ea);
1587
1588
0
  return rc;
1589
0
}
1590
1591
/**
1592
 * is_reply - Is one email a reply to another?
1593
 * @param reply Email to test
1594
 * @param orig  Original email
1595
 * @retval true  It is a reply
1596
 * @retval false It is not a reply
1597
 */
1598
static bool is_reply(struct Email *reply, struct Email *orig)
1599
0
{
1600
0
  if (!reply || !reply->env || !orig || !orig->env)
1601
0
    return false;
1602
0
  return mutt_list_find(&orig->env->references, reply->env->message_id) ||
1603
0
         mutt_list_find(&orig->env->in_reply_to, reply->env->message_id);
1604
0
}
1605
1606
/**
1607
 * search_attach_keyword - Search an email for 'attachment' keywords
1608
 * @param filename Filename
1609
 * @param sub      Config Subset
1610
 * @retval true The regex matches in the email
1611
 *
1612
 * Search an email for the regex in $abort_noattach_regex.
1613
 * A match might indicate that the user should have attached something.
1614
 *
1615
 * @note Quoted lines (as defined by $quote_regex) are ignored
1616
 */
1617
static bool search_attach_keyword(char *filename, struct ConfigSubset *sub)
1618
0
{
1619
0
  const struct Regex *c_abort_noattach_regex = cs_subset_regex(sub, "abort_noattach_regex");
1620
0
  const struct Regex *c_quote_regex = cs_subset_regex(sub, "quote_regex");
1621
1622
  /* Search for the regex in `$abort_noattach_regex` within a file */
1623
0
  if (!c_abort_noattach_regex || !c_abort_noattach_regex->regex ||
1624
0
      !c_quote_regex || !c_quote_regex->regex)
1625
0
  {
1626
0
    return false;
1627
0
  }
1628
1629
0
  FILE *fp_att = mutt_file_fopen(filename, "r");
1630
0
  if (!fp_att)
1631
0
    return false;
1632
1633
0
  char *inputline = MUTT_MEM_MALLOC(1024, char);
1634
0
  bool found = false;
1635
0
  while (!feof(fp_att) && fgets(inputline, 1024, fp_att))
1636
0
  {
1637
0
    if (!mutt_is_quote_line(inputline, NULL) &&
1638
0
        mutt_regex_match(c_abort_noattach_regex, inputline))
1639
0
    {
1640
0
      found = true;
1641
0
      break;
1642
0
    }
1643
0
  }
1644
0
  FREE(&inputline);
1645
0
  mutt_file_fclose(&fp_att);
1646
0
  return found;
1647
0
}
1648
1649
/**
1650
 * save_fcc - Save an Email to a 'sent mail' folder
1651
 * @param[in]  m             Current Mailbox
1652
 * @param[in]  e             Email to save
1653
 * @param[in]  fcc           Folder to save to (can be comma-separated list)
1654
 * @param[in]  clear_content Cleartext content of Email
1655
 * @param[in]  pgpkeylist    List of pgp keys
1656
 * @param[in]  flags         Send mode, see #SendFlags
1657
 * @param[out] finalpath     Path of final folder
1658
 * @param[in]  sub           Config Subset
1659
 * @retval  0 Success
1660
 * @retval -1 Error
1661
 */
1662
static int save_fcc(struct Mailbox *m, struct Email *e, struct Buffer *fcc,
1663
                    struct Body *clear_content, char *pgpkeylist,
1664
                    SendFlags flags, char **finalpath, struct ConfigSubset *sub)
1665
0
{
1666
0
  int rc = 0;
1667
0
  struct Body *save_content = NULL;
1668
1669
0
  buf_expand_path(fcc);
1670
1671
  /* Don't save a copy when we are in batch-mode, and the FCC
1672
   * folder is on an IMAP server: This would involve possibly lots
1673
   * of user interaction, which is not available in batch mode.
1674
   *
1675
   * Note: A patch to fix the problems with the use of IMAP servers
1676
   * from non-curses mode is available from Brendan Cully.  However,
1677
   * I'd like to think a bit more about this before including it.  */
1678
1679
0
  if ((flags & SEND_BATCH) && !buf_is_empty(fcc) &&
1680
0
      (imap_path_probe(buf_string(fcc), NULL) == MUTT_IMAP))
1681
0
  {
1682
0
    mutt_error(_("Warning: Fcc to an IMAP mailbox is not supported in batch mode"));
1683
    /* L10N: Printed after the "Fcc to an IMAP mailbox is not supported" message.
1684
       To make it clearer that the message doesn't mean NeoMutt is aborting
1685
       sending the mail too.
1686
       %s is the full mailbox URL, including imap(s)://
1687
    */
1688
0
    mutt_error(_("Skipping Fcc to %s"), buf_string(fcc));
1689
0
    buf_reset(fcc);
1690
0
    return rc;
1691
0
  }
1692
1693
0
  if (buf_is_empty(fcc) || mutt_str_equal("/dev/null", buf_string(fcc)))
1694
0
    return rc;
1695
1696
0
  struct Body *tmpbody = e->body;
1697
0
  struct Body *save_sig = NULL;
1698
0
  struct Body *save_parts = NULL;
1699
1700
0
  const bool c_fcc_before_send = cs_subset_bool(sub, "fcc_before_send");
1701
  /* Before sending, we don't allow message manipulation because it
1702
   * will break message signatures.  This is especially complicated by
1703
   * Protected Headers. */
1704
0
  if (!c_fcc_before_send)
1705
0
  {
1706
0
    const bool c_fcc_clear = cs_subset_bool(sub, "fcc_clear");
1707
0
    if ((WithCrypto != 0) &&
1708
0
        (e->security & (SEC_ENCRYPT | SEC_SIGN | SEC_AUTOCRYPT)) && c_fcc_clear)
1709
0
    {
1710
0
      e->body = clear_content;
1711
0
      e->security &= ~(SEC_ENCRYPT | SEC_SIGN | SEC_AUTOCRYPT);
1712
0
      mutt_env_free(&e->body->mime_headers);
1713
0
      mutt_param_delete(&e->body->parameter, "protected-headers");
1714
0
    }
1715
1716
0
    const enum QuadOption c_fcc_attach = cs_subset_quad(sub, "fcc_attach");
1717
1718
    /* check to see if the user wants copies of all attachments */
1719
0
    bool save_atts = true;
1720
0
    if (e->body->type == TYPE_MULTIPART)
1721
0
    {
1722
      /* In batch mode, save attachments if the quadoption is yes or ask-yes */
1723
0
      if (flags & SEND_BATCH)
1724
0
      {
1725
0
        if ((c_fcc_attach == MUTT_NO) || (c_fcc_attach == MUTT_ASKNO))
1726
0
          save_atts = false;
1727
0
      }
1728
0
      else if (query_quadoption(_("Save attachments in Fcc?"), sub, "fcc_attach") != MUTT_YES)
1729
0
      {
1730
0
        save_atts = false;
1731
0
      }
1732
0
    }
1733
0
    if (!save_atts)
1734
0
    {
1735
0
      if ((WithCrypto != 0) && (e->security & (SEC_ENCRYPT | SEC_SIGN | SEC_AUTOCRYPT)) &&
1736
0
          (mutt_str_equal(e->body->subtype, "encrypted") ||
1737
0
           mutt_str_equal(e->body->subtype, "signed")))
1738
0
      {
1739
0
        if ((clear_content->type == TYPE_MULTIPART) &&
1740
0
            (query_quadoption(_("Save attachments in Fcc?"), sub, "fcc_attach") != MUTT_YES))
1741
0
        {
1742
0
          if (!(e->security & SEC_ENCRYPT) && (e->security & SEC_SIGN))
1743
0
          {
1744
            /* save initial signature and attachments */
1745
0
            save_sig = e->body->parts->next;
1746
0
            save_parts = clear_content->parts->next;
1747
0
          }
1748
1749
          /* this means writing only the main part */
1750
0
          e->body = clear_content->parts;
1751
1752
0
          if (mutt_protect(e, pgpkeylist, false) == -1)
1753
0
          {
1754
            /* we can't do much about it at this point, so
1755
           * fallback to saving the whole thing to fcc */
1756
0
            e->body = tmpbody;
1757
0
            save_sig = NULL;
1758
0
            goto full_fcc;
1759
0
          }
1760
1761
0
          save_content = e->body;
1762
0
        }
1763
0
      }
1764
0
      else
1765
0
      {
1766
0
        if (query_quadoption(_("Save attachments in Fcc?"), sub, "fcc_attach") != MUTT_YES)
1767
0
          e->body = e->body->parts;
1768
0
      }
1769
0
    }
1770
0
  }
1771
1772
0
full_fcc:
1773
0
  if (e->body)
1774
0
  {
1775
    /* update received time so that when storing to a mbox-style folder
1776
     * the From_ line contains the current time instead of when the
1777
     * message was first postponed.  */
1778
0
    e->received = mutt_date_now();
1779
0
    rc = mutt_write_multiple_fcc(buf_string(fcc), e, NULL, false, NULL, finalpath, sub);
1780
0
    while (rc && !(flags & SEND_BATCH))
1781
0
    {
1782
0
      mutt_clear_error();
1783
0
      int choice = mw_multi_choice(
1784
          /* L10N: Called when saving to $record or Fcc failed after sending.
1785
             (r)etry tries the same mailbox again.
1786
             alternate (m)ailbox prompts for a different mailbox to try.
1787
             (s)kip aborts saving.  */
1788
0
          _("Fcc failed. (r)etry, alternate (m)ailbox, or (s)kip?"),
1789
          /* L10N: These correspond to the "Fcc failed" multi-choice prompt
1790
             (r)etry, alternate (m)ailbox, or (s)kip.
1791
             Any similarity to famous leaders of the FSF is coincidental.  */
1792
0
          _("rms"));
1793
0
      switch (choice)
1794
0
      {
1795
0
        case 2: /* alternate (m)ailbox */
1796
          /* L10N: This is the prompt to enter an "alternate (m)ailbox" when the
1797
             initial Fcc fails.  */
1798
0
          rc = mw_enter_fname(_("Fcc mailbox"), fcc, true, m, false, NULL, NULL,
1799
0
                              MUTT_SEL_NO_FLAGS);
1800
0
          if ((rc == -1) || buf_is_empty(fcc))
1801
0
          {
1802
0
            rc = 0;
1803
0
            break;
1804
0
          }
1805
0
          FALLTHROUGH;
1806
1807
0
        case 1: /* (r)etry */
1808
0
          rc = mutt_write_multiple_fcc(buf_string(fcc), e, NULL, false, NULL, finalpath, sub);
1809
0
          break;
1810
1811
0
        case -1: /* abort */
1812
0
        case 3:  /* (s)kip */
1813
0
          rc = 0;
1814
0
          break;
1815
0
      }
1816
0
    }
1817
0
  }
1818
1819
0
  if (!c_fcc_before_send)
1820
0
  {
1821
0
    e->body = tmpbody;
1822
1823
0
    if ((WithCrypto != 0) && save_sig)
1824
0
    {
1825
      /* cleanup the second signature structures */
1826
0
      if (save_content->parts)
1827
0
      {
1828
0
        mutt_body_free(&save_content->parts->next);
1829
0
        save_content->parts = NULL;
1830
0
      }
1831
0
      mutt_body_free(&save_content);
1832
1833
      /* restore old signature and attachments */
1834
0
      e->body->parts->next = save_sig;
1835
0
      e->body->parts->parts->next = save_parts;
1836
0
    }
1837
0
    else if ((WithCrypto != 0) && save_content)
1838
0
    {
1839
      /* destroy the new encrypted body. */
1840
0
      mutt_body_free(&save_content);
1841
0
    }
1842
0
  }
1843
1844
0
  return 0;
1845
0
}
1846
1847
/**
1848
 * postpone_message - Save an Email for another day
1849
 * @param e_post Email to postpone
1850
 * @param e_cur  Current Email in the index
1851
 * @param fcc    Folder for 'sent mail'
1852
 * @param flags  Send mode, see #SendFlags
1853
 * @param sub    Config Subset
1854
 * @retval  0 Success
1855
 * @retval -1 Error
1856
 */
1857
static int postpone_message(struct Email *e_post, struct Email *e_cur,
1858
                            const char *fcc, SendFlags flags, struct ConfigSubset *sub)
1859
0
{
1860
0
  char *pgpkeylist = NULL;
1861
0
  const char *encrypt_as = NULL;
1862
0
  struct Body *clear_content = NULL;
1863
1864
0
  const char *const c_postponed = cs_subset_string(sub, "postponed");
1865
0
  if (!c_postponed)
1866
0
  {
1867
0
    mutt_error(_("Can't postpone.  $postponed is unset"));
1868
0
    return -1;
1869
0
  }
1870
1871
0
  if (e_post->body->next)
1872
0
    e_post->body = mutt_make_multipart(e_post->body);
1873
1874
0
  mutt_encode_descriptions(e_post->body, true, sub);
1875
1876
0
  const bool c_postpone_encrypt = cs_subset_bool(sub, "postpone_encrypt");
1877
0
  if ((WithCrypto != 0) && c_postpone_encrypt &&
1878
0
      (e_post->security & (SEC_ENCRYPT | SEC_AUTOCRYPT)))
1879
0
  {
1880
0
    if (((WithCrypto & APPLICATION_PGP) != 0) && (e_post->security & APPLICATION_PGP))
1881
0
    {
1882
0
      const char *const c_pgp_default_key = cs_subset_string(sub, "pgp_default_key");
1883
0
      encrypt_as = c_pgp_default_key;
1884
0
    }
1885
0
    else if (((WithCrypto & APPLICATION_SMIME) != 0) && (e_post->security & APPLICATION_SMIME))
1886
0
    {
1887
0
      const char *const c_smime_default_key = cs_subset_string(sub, "smime_default_key");
1888
0
      encrypt_as = c_smime_default_key;
1889
0
    }
1890
0
    if (!encrypt_as)
1891
0
    {
1892
0
      const char *const c_postpone_encrypt_as = cs_subset_string(sub, "postpone_encrypt_as");
1893
0
      encrypt_as = c_postpone_encrypt_as;
1894
0
    }
1895
1896
#ifdef USE_AUTOCRYPT
1897
    if (e_post->security & SEC_AUTOCRYPT)
1898
    {
1899
      if (mutt_autocrypt_set_sign_as_default_key(e_post))
1900
      {
1901
        if (mutt_istr_equal(e_post->body->subtype, "mixed"))
1902
          e_post->body = mutt_remove_multipart(e_post->body);
1903
        decode_descriptions(e_post->body);
1904
        mutt_error(_("Error encrypting message. Check your crypt settings."));
1905
        return -1;
1906
      }
1907
      encrypt_as = AutocryptDefaultKey;
1908
    }
1909
#endif
1910
1911
0
    if (encrypt_as)
1912
0
    {
1913
0
      pgpkeylist = mutt_str_dup(encrypt_as);
1914
0
      clear_content = e_post->body;
1915
0
      if (mutt_protect(e_post, pgpkeylist, true) == -1)
1916
0
      {
1917
0
        FREE(&pgpkeylist);
1918
0
        if (mutt_istr_equal(e_post->body->subtype, "mixed"))
1919
0
          e_post->body = mutt_remove_multipart(e_post->body);
1920
0
        decode_descriptions(e_post->body);
1921
0
        mutt_error(_("Error encrypting message. Check your crypt settings."));
1922
0
        return -1;
1923
0
      }
1924
1925
0
      FREE(&pgpkeylist);
1926
1927
0
      mutt_encode_descriptions(e_post->body, false, sub);
1928
0
    }
1929
0
  }
1930
1931
  /* make sure the message is written to the right part of a maildir
1932
   * postponed folder.  */
1933
0
  e_post->read = false;
1934
0
  e_post->old = false;
1935
1936
0
  mutt_prepare_envelope(e_post->env, false, sub);
1937
0
  mutt_env_to_intl(e_post->env, NULL, NULL); /* Handle bad IDNAs the next time. */
1938
1939
0
  if (mutt_write_fcc(NONULL(c_postponed), e_post,
1940
0
                     (e_cur && (flags & SEND_REPLY)) ? e_cur->env->message_id : NULL,
1941
0
                     true, fcc, NULL, sub) < 0)
1942
0
  {
1943
0
    if (clear_content)
1944
0
    {
1945
0
      mutt_body_free(&e_post->body);
1946
0
      e_post->body = clear_content;
1947
0
    }
1948
0
    mutt_env_free(&e_post->body->mime_headers); /* protected headers */
1949
0
    mutt_param_delete(&e_post->body->parameter, "protected-headers");
1950
0
    if (mutt_istr_equal(e_post->body->subtype, "mixed"))
1951
0
      e_post->body = mutt_remove_multipart(e_post->body);
1952
0
    decode_descriptions(e_post->body);
1953
0
    mutt_unprepare_envelope(e_post->env);
1954
0
    return -1;
1955
0
  }
1956
1957
0
  mutt_update_num_postponed();
1958
1959
0
  if (clear_content)
1960
0
    mutt_body_free(&clear_content);
1961
1962
0
  return 0;
1963
0
}
1964
1965
/**
1966
 * is_text_plain - Is a Body a text/plain MIME part?
1967
 * @param b Body to check
1968
 * @retval true  Body is text/plain
1969
 * @retval false Body is not
1970
 */
1971
static bool is_text_plain(const struct Body *b)
1972
0
{
1973
0
  return (b->type == TYPE_TEXT) && mutt_istr_equal(b->subtype, "plain");
1974
0
}
1975
1976
/**
1977
 * abort_for_missing_attachments - Should we abort sending because of missing attachments?
1978
 * @param b Body
1979
 * @param sub Config Subset
1980
 * @retval true Abort because of missing attachments
1981
 */
1982
static bool abort_for_missing_attachments(const struct Body *b, struct ConfigSubset *sub)
1983
0
{
1984
0
  const enum QuadOption c_abort_noattach = cs_subset_quad(sub, "abort_noattach");
1985
1986
0
  if (c_abort_noattach == MUTT_NO)
1987
0
    return false;
1988
1989
0
  if (b->next)
1990
0
    return false;
1991
1992
0
  bool has_keyword = false;
1993
1994
  /* search text/plain parts, whether they are main or alternative parts */
1995
0
  if (is_text_plain(b))
1996
0
  {
1997
0
    has_keyword |= search_attach_keyword(b->filename, sub);
1998
0
  }
1999
0
  else
2000
0
  {
2001
0
    for (b = b->parts; b; b = b->next)
2002
0
    {
2003
0
      if (is_text_plain(b))
2004
0
      {
2005
0
        has_keyword |= search_attach_keyword(b->filename, sub);
2006
0
      }
2007
0
    }
2008
0
  }
2009
2010
0
  if (!has_keyword)
2011
0
    return false;
2012
2013
0
  if (c_abort_noattach == MUTT_YES)
2014
0
  {
2015
0
    mutt_error(_("Message contains text matching \"$abort_noattach_regex\". Not sending."));
2016
0
    return true;
2017
0
  }
2018
2019
0
  return query_quadoption(_("No attachments, cancel sending?"), sub, "abort_noattach") != MUTT_NO;
2020
0
}
2021
2022
/**
2023
 * mutt_send_message - Send an email
2024
 * @param flags    Send mode, see #SendFlags
2025
 * @param e_templ  Template to use for new message
2026
 * @param tempfile File specified by -i or -H
2027
 * @param m        Current mailbox
2028
 * @param ea       Array of Emails to send
2029
 * @param sub      Config Subset
2030
 * @retval  0 Message was successfully sent
2031
 * @retval -1 Message was aborted or an error occurred
2032
 * @retval  1 Message was postponed
2033
 */
2034
int mutt_send_message(SendFlags flags, struct Email *e_templ, const char *tempfile,
2035
                      struct Mailbox *m, struct EmailArray *ea, struct ConfigSubset *sub)
2036
0
{
2037
0
  struct Buffer *fcc = buf_pool_get(); /* where to copy this message */
2038
0
  FILE *fp_tmp = NULL;
2039
0
  struct Body *pbody = NULL;
2040
0
  int i;
2041
0
  bool free_clear_content = false;
2042
2043
0
  struct Body *clear_content = NULL;
2044
0
  char *pgpkeylist = NULL;
2045
  /* save current value of "pgp_sign_as"  and "smime_default_key" */
2046
0
  char *pgp_sign_as = NULL;
2047
0
  char *smime_sign_as = NULL;
2048
0
  const char *tag = NULL;
2049
0
  char *err = NULL;
2050
0
  const char *ctype = NULL;
2051
0
  char *finalpath = NULL;
2052
0
  struct Email *e_cur = NULL;
2053
2054
0
  if (ea && (ARRAY_SIZE(ea) == 1))
2055
0
    e_cur = *ARRAY_GET(ea, 0);
2056
2057
0
  int rc = -1;
2058
2059
0
  if (flags & SEND_NEWS)
2060
0
    OptNewsSend = true;
2061
0
  else
2062
0
    OptNewsSend = false;
2063
2064
0
  const enum QuadOption c_recall = cs_subset_quad(sub, "recall");
2065
2066
0
  if (!flags && !e_templ && (c_recall != MUTT_NO) && mutt_num_postponed(m, true))
2067
0
  {
2068
    /* If the user is composing a new message, check to see if there
2069
     * are any postponed messages first.  */
2070
0
    enum QuadOption ans = query_quadoption(_("Recall postponed message?"), sub, "recall");
2071
0
    if (ans == MUTT_ABORT)
2072
0
      return rc;
2073
2074
0
    if (ans == MUTT_YES)
2075
0
      flags |= SEND_POSTPONED;
2076
0
  }
2077
2078
0
  if (flags & SEND_POSTPONED)
2079
0
  {
2080
0
    if (WithCrypto & APPLICATION_PGP)
2081
0
    {
2082
0
      const char *const c_pgp_sign_as = cs_subset_string(sub, "pgp_sign_as");
2083
0
      pgp_sign_as = mutt_str_dup(c_pgp_sign_as);
2084
0
    }
2085
0
    if (WithCrypto & APPLICATION_SMIME)
2086
0
    {
2087
0
      const char *const c_smime_sign_as = cs_subset_string(sub, "smime_sign_as");
2088
0
      smime_sign_as = mutt_str_dup(c_smime_sign_as);
2089
0
    }
2090
0
  }
2091
2092
  /* Delay expansion of aliases until absolutely necessary--shouldn't
2093
   * be necessary unless we are prompting the user or about to execute a
2094
   * send-hook.  */
2095
2096
0
  if (!e_templ)
2097
0
  {
2098
0
    e_templ = email_new();
2099
2100
0
    if (flags == SEND_POSTPONED)
2101
0
    {
2102
0
      rc = mutt_get_postponed(m, e_templ, &e_cur, fcc);
2103
0
      if (rc < 0)
2104
0
      {
2105
0
        flags = SEND_POSTPONED;
2106
0
        goto cleanup;
2107
0
      }
2108
0
      flags = rc;
2109
      /* If postponed message is a news article, it have
2110
       * a "Newsgroups:" header line, then set appropriate flag.  */
2111
0
      if (e_templ->env->newsgroups)
2112
0
      {
2113
0
        flags |= SEND_NEWS;
2114
0
        OptNewsSend = true;
2115
0
      }
2116
0
      else
2117
0
      {
2118
0
        flags &= ~SEND_NEWS;
2119
0
        OptNewsSend = false;
2120
0
      }
2121
0
    }
2122
2123
0
    if (flags & (SEND_POSTPONED | SEND_RESEND))
2124
0
    {
2125
0
      struct Body *b = e_templ->body;
2126
0
      while (b->parts)
2127
0
        b = b->parts;
2128
0
      fp_tmp = mutt_file_fopen(b->filename, "a+");
2129
0
      if (!fp_tmp)
2130
0
      {
2131
0
        mutt_perror("%s", b->filename);
2132
0
        goto cleanup;
2133
0
      }
2134
0
    }
2135
2136
0
    if (!e_templ->env)
2137
0
      e_templ->env = mutt_env_new();
2138
0
  }
2139
2140
  /* Parse and use an eventual list-post header */
2141
0
  if ((flags & SEND_LIST_REPLY) && e_cur && e_cur->env && e_cur->env->list_post)
2142
0
  {
2143
    /* Use any list-post header as a template */
2144
0
    mutt_parse_mailto(e_templ->env, NULL, e_cur->env->list_post);
2145
    /* We don't let them set the sender's address. */
2146
0
    mutt_addrlist_clear(&e_templ->env->from);
2147
0
  }
2148
2149
0
  if (!(flags & (SEND_KEY | SEND_POSTPONED | SEND_RESEND)))
2150
0
  {
2151
    /* When SEND_DRAFT_FILE is set, the caller has already
2152
     * created the "parent" body structure.  */
2153
0
    if (!(flags & SEND_DRAFT_FILE))
2154
0
    {
2155
0
      pbody = mutt_body_new();
2156
0
      pbody->next = e_templ->body; /* don't kill command-line attachments */
2157
0
      e_templ->body = pbody;
2158
2159
0
      const char *const c_content_type = cs_subset_string(sub, "content_type");
2160
0
      ctype = c_content_type;
2161
0
      if (!ctype)
2162
0
        ctype = "text/plain";
2163
0
      mutt_parse_content_type(ctype, e_templ->body);
2164
0
      e_templ->body->unlink = true;
2165
0
      e_templ->body->use_disp = false;
2166
0
      e_templ->body->disposition = DISP_INLINE;
2167
2168
0
      if (tempfile)
2169
0
      {
2170
0
        fp_tmp = mutt_file_fopen(tempfile, "a+");
2171
0
        e_templ->body->filename = mutt_str_dup(tempfile);
2172
0
        if (flags & SEND_NO_FREE_HEADER)
2173
0
          e_templ->body->unlink = false;
2174
0
      }
2175
0
      else
2176
0
      {
2177
0
        struct Buffer *buf = buf_pool_get();
2178
0
        buf_mktemp(buf);
2179
0
        fp_tmp = mutt_file_fopen(buf_string(buf), "w+");
2180
0
        e_templ->body->filename = buf_strdup(buf);
2181
0
        buf_pool_release(&buf);
2182
0
      }
2183
0
    }
2184
0
    else
2185
0
    {
2186
0
      struct Body *b = e_templ->body;
2187
0
      while (b->parts)
2188
0
        b = b->parts;
2189
0
      fp_tmp = mutt_file_fopen(b->filename, "a+");
2190
0
    }
2191
2192
0
    if (!fp_tmp)
2193
0
    {
2194
0
      mutt_debug(LL_DEBUG1, "can't create tempfile %s (errno=%d)\n",
2195
0
                 e_templ->body->filename, errno);
2196
0
      mutt_perror("%s", e_templ->body->filename);
2197
0
      goto cleanup;
2198
0
    }
2199
0
  }
2200
2201
0
  const bool c_reverse_name = cs_subset_bool(sub, "reverse_name");
2202
  /* this is handled here so that the user can match ~f in send-hook */
2203
0
  if (e_cur && c_reverse_name && !(flags & (SEND_POSTPONED | SEND_RESEND)))
2204
0
  {
2205
    /* We shouldn't have to worry about alias expansion here since we are
2206
     * either replying to a real or postponed message, therefore no aliases
2207
     * should exist since the user has not had the opportunity to add
2208
     * addresses to the list.  We just have to ensure the postponed messages
2209
     * have their aliases expanded.  */
2210
2211
0
    if (!TAILQ_EMPTY(&e_templ->env->from))
2212
0
    {
2213
0
      mutt_debug(LL_DEBUG5, "e_templ->env->from before set_reverse_name: %s\n",
2214
0
                 buf_string(TAILQ_FIRST(&e_templ->env->from)->mailbox));
2215
0
      mutt_addrlist_clear(&e_templ->env->from);
2216
0
    }
2217
0
    set_reverse_name(&e_templ->env->from, e_cur->env, sub);
2218
0
  }
2219
2220
0
  const bool c_reply_with_xorig = cs_subset_bool(sub, "reply_with_xorig");
2221
0
  if (e_cur && c_reply_with_xorig && !(flags & (SEND_POSTPONED | SEND_RESEND | SEND_FORWARD)))
2222
0
  {
2223
    /* We shouldn't have to worry about freeing 'e_templ->env->from' before
2224
     * setting it here since this code will only execute when doing some
2225
     * sort of reply. The pointer will only be set when using the -H command
2226
     * line option.
2227
     *
2228
     * If there is already a from address recorded in 'e_templ->env->from',
2229
     * then it theoretically comes from `$reverse_name` handling, and we don't use
2230
     * the 'X-Original-To header'.  */
2231
0
    if (!TAILQ_EMPTY(&e_cur->env->x_original_to) && TAILQ_EMPTY(&e_templ->env->from))
2232
0
    {
2233
0
      mutt_addrlist_copy(&e_templ->env->from, &e_cur->env->x_original_to, false);
2234
0
      mutt_debug(LL_DEBUG5, "e_templ->env->from extracted from X-Original-To: header: %s\n",
2235
0
                 buf_string(TAILQ_FIRST(&e_templ->env->from)->mailbox));
2236
0
    }
2237
0
  }
2238
2239
0
  if (!e_templ->env->message_id)
2240
0
    e_templ->env->message_id = mutt_gen_msgid();
2241
2242
0
  const bool c_resume_draft_files = cs_subset_bool(sub, "resume_draft_files");
2243
0
  if (!(flags & (SEND_POSTPONED | SEND_RESEND)) &&
2244
0
      !((flags & SEND_DRAFT_FILE) && c_resume_draft_files))
2245
0
  {
2246
0
    if ((flags & (SEND_REPLY | SEND_FORWARD | SEND_TO_SENDER)) &&
2247
0
        (envelope_defaults(e_templ->env, ea, flags, sub) == -1))
2248
0
    {
2249
0
      goto cleanup;
2250
0
    }
2251
2252
0
    const bool c_hdrs = cs_subset_bool(sub, "hdrs");
2253
0
    if (c_hdrs)
2254
0
      process_user_recips(e_templ->env);
2255
2256
    /* Expand aliases and remove duplicates/crossrefs */
2257
0
    mutt_expand_aliases_env(e_templ->env);
2258
2259
0
    if (flags & SEND_REPLY)
2260
0
      mutt_fix_reply_recipients(e_templ->env, sub);
2261
2262
0
    if ((flags & SEND_NEWS) && (m && m->type == MUTT_NNTP) && !e_templ->env->newsgroups)
2263
0
    {
2264
0
      e_templ->env->newsgroups = mutt_str_dup(((struct NntpMboxData *) m->mdata)->group);
2265
0
    }
2266
2267
0
    const bool c_auto_edit = cs_subset_bool(sub, "auto_edit");
2268
0
    const bool c_edit_headers = cs_subset_bool(sub, "edit_headers");
2269
0
    const bool c_fast_reply = cs_subset_bool(sub, "fast_reply");
2270
0
    if (!(flags & SEND_BATCH) && !(c_auto_edit && c_edit_headers) &&
2271
0
        !((flags & SEND_REPLY) && c_fast_reply))
2272
0
    {
2273
0
      if (edit_envelope(e_templ->env, flags, sub) == -1)
2274
0
        goto cleanup;
2275
0
    }
2276
2277
    /* the from address must be set here regardless of whether or not
2278
     * $use_from is set so that the '~P' (from you) operator in send-hook
2279
     * patterns will work.  if $use_from is unset, the from address is killed
2280
     * after send-hooks are evaluated */
2281
2282
0
    const bool killfrom = TAILQ_EMPTY(&e_templ->env->from);
2283
0
    if (killfrom)
2284
0
    {
2285
0
      mutt_addrlist_append(&e_templ->env->from, mutt_default_from(sub));
2286
0
    }
2287
2288
0
    if ((flags & SEND_REPLY) && e_cur)
2289
0
    {
2290
      /* change setting based upon message we are replying to */
2291
0
      mutt_message_hook(m, e_cur, MUTT_REPLY_HOOK);
2292
2293
      /* set the replied flag for the message we are generating so that the
2294
       * user can use ~Q in a send-hook to know when reply-hook's are also
2295
       * being used.  */
2296
0
      e_templ->replied = true;
2297
0
    }
2298
2299
    /* change settings based upon recipients */
2300
2301
0
    mutt_message_hook(NULL, e_templ, MUTT_SEND_HOOK);
2302
2303
    /* Unset the replied flag from the message we are composing since it is
2304
     * no longer required.  This is done here because the FCC'd copy of
2305
     * this message was erroneously get the 'R'eplied flag when stored in
2306
     * a maildir-style mailbox.  */
2307
0
    e_templ->replied = false;
2308
2309
    /* $use_from and/or $from might have changed in a send-hook */
2310
0
    if (killfrom)
2311
0
    {
2312
0
      mutt_addrlist_clear(&e_templ->env->from);
2313
2314
0
      const bool c_use_from = cs_subset_bool(sub, "use_from");
2315
0
      if (c_use_from && !(flags & (SEND_POSTPONED | SEND_RESEND)))
2316
0
        mutt_addrlist_append(&e_templ->env->from, mutt_default_from(sub));
2317
0
    }
2318
2319
0
    if (c_hdrs)
2320
0
      process_user_header(e_templ->env);
2321
2322
0
    if ((flags & SEND_BATCH) && !(flags & SEND_CONSUMED_STDIN))
2323
0
    {
2324
0
      if (mutt_file_copy_stream(stdin, fp_tmp) < 0)
2325
0
      {
2326
0
        mutt_error(_("Error sending message"));
2327
0
        goto cleanup;
2328
0
      }
2329
0
    }
2330
2331
0
    if (!(flags & SEND_BATCH))
2332
0
      mutt_make_greeting(e_templ, fp_tmp, sub);
2333
2334
0
    const bool c_sig_on_top = cs_subset_bool(sub, "sig_on_top");
2335
0
    const char *const c_editor = cs_subset_string(sub, "editor");
2336
0
    if (c_sig_on_top && !(flags & (SEND_KEY | SEND_BATCH)) && c_editor)
2337
0
    {
2338
0
      append_signature(fp_tmp, sub);
2339
0
    }
2340
2341
    /* include replies/forwarded messages, unless we are given a template */
2342
0
    if (!tempfile && (m || !(flags & (SEND_REPLY | SEND_FORWARD))) &&
2343
0
        (generate_body(fp_tmp, e_templ, flags, m, ea, sub) == -1))
2344
0
    {
2345
0
      goto cleanup;
2346
0
    }
2347
2348
0
    if (!c_sig_on_top && !(flags & (SEND_KEY | SEND_BATCH)) && c_editor)
2349
0
    {
2350
0
      append_signature(fp_tmp, sub);
2351
0
    }
2352
0
  }
2353
2354
  /* Only set format=flowed for new messages.  postponed/resent/draftfiles
2355
   * should respect the original email.
2356
   *
2357
   * This is set here so that send-hook can be used to turn the option on.  */
2358
0
  if (!(flags & (SEND_KEY | SEND_POSTPONED | SEND_RESEND | SEND_DRAFT_FILE)))
2359
0
  {
2360
0
    const bool c_text_flowed = cs_subset_bool(sub, "text_flowed");
2361
0
    if (c_text_flowed && is_text_plain(e_templ->body))
2362
0
    {
2363
0
      mutt_param_set(&e_templ->body->parameter, "format", "flowed");
2364
0
    }
2365
0
  }
2366
2367
  /* This hook is even called for postponed messages, and can, e.g., be used
2368
   * for setting the editor, the sendmail path, or the envelope sender.  */
2369
0
  mutt_message_hook(NULL, e_templ, MUTT_SEND2_HOOK);
2370
2371
  /* wait until now to set the real name portion of our return address so
2372
   * that $real_name can be set in a send-hook */
2373
0
  {
2374
0
    struct Address *from = TAILQ_FIRST(&e_templ->env->from);
2375
0
    if (from && !from->personal && !(flags & (SEND_RESEND | SEND_POSTPONED)))
2376
0
    {
2377
0
      const char *const c_real_name = cs_subset_string(sub, "real_name");
2378
0
      if (c_real_name)
2379
0
        from->personal = buf_new(c_real_name);
2380
0
    }
2381
0
  }
2382
2383
0
  if (!(((WithCrypto & APPLICATION_PGP) != 0) && (flags & SEND_KEY)))
2384
0
    mutt_file_fclose(&fp_tmp);
2385
2386
0
  if (!(flags & SEND_BATCH))
2387
0
  {
2388
0
    struct stat st = { 0 };
2389
0
    time_t mtime;
2390
0
    struct Body *b = e_templ->body;
2391
0
    while (b->parts)
2392
0
      b = b->parts;
2393
0
    mtime = mutt_file_decrease_mtime(b->filename, NULL);
2394
0
    if (mtime == (time_t) -1)
2395
0
    {
2396
0
      mutt_perror("%s", b->filename);
2397
0
      goto cleanup;
2398
0
    }
2399
2400
0
    mutt_update_encoding(b, sub);
2401
2402
0
    const bool c_edit_headers = cs_subset_bool(sub, "edit_headers");
2403
0
    const bool c_auto_edit = cs_subset_bool(sub, "auto_edit");
2404
2405
    /* Select whether or not the user's editor should be called now.  We
2406
     * don't want to do this when:
2407
     * 1) we are sending a key/cert
2408
     * 2) we are forwarding a message and the user doesn't want to edit it.
2409
     *    This is controlled by the quadoption $forward_edit.  However, if
2410
     *    both $edit_headers and $auto_edit are set, we want to ignore the
2411
     *    setting of $forward_edit because the user probably needs to add the
2412
     *    recipients.  */
2413
0
    if (!(flags & SEND_KEY) &&
2414
0
        (((flags & SEND_FORWARD) == 0) || (c_edit_headers && c_auto_edit) ||
2415
0
         (query_quadoption(_("Edit forwarded message?"), sub, "forward_edit") == MUTT_YES)))
2416
0
    {
2417
      /* If the this isn't a text message, look for a mailcap edit command */
2418
0
      const char *const c_editor = cs_subset_string(sub, "editor");
2419
0
      b = e_templ->body;
2420
0
      while (b->parts)
2421
0
        b = b->parts;
2422
0
      if (mutt_needs_mailcap(b))
2423
0
      {
2424
0
        if (!mutt_edit_attachment(b))
2425
0
          goto cleanup;
2426
0
      }
2427
0
      else if (c_edit_headers)
2428
0
      {
2429
0
        mutt_env_to_local(e_templ->env);
2430
0
        mutt_edit_headers(c_editor, b->filename, e_templ, fcc);
2431
0
        mutt_env_to_intl(e_templ->env, NULL, NULL);
2432
0
      }
2433
0
      else
2434
0
      {
2435
0
        mutt_edit_file(c_editor, b->filename);
2436
0
        if (stat(b->filename, &st) == 0)
2437
0
        {
2438
0
          if (mtime != st.st_mtime)
2439
0
            fix_end_of_file(b->filename);
2440
0
        }
2441
0
        else
2442
0
        {
2443
0
          mutt_perror("%s", b->filename);
2444
0
        }
2445
0
      }
2446
2447
0
      mutt_message_hook(NULL, e_templ, MUTT_SEND2_HOOK);
2448
0
    }
2449
2450
0
    if (!(flags & (SEND_POSTPONED | SEND_FORWARD | SEND_KEY | SEND_RESEND | SEND_DRAFT_FILE)))
2451
0
    {
2452
0
      if (stat(e_templ->body->filename, &st) == 0)
2453
0
      {
2454
        /* if the file was not modified, bail out now */
2455
0
        if ((mtime == st.st_mtime) && !e_templ->body->next &&
2456
0
            (query_quadoption(_("Abort unmodified message?"), sub, "abort_unmodified") == MUTT_YES))
2457
0
        {
2458
0
          mutt_message(_("Aborted unmodified message"));
2459
0
          goto cleanup;
2460
0
        }
2461
0
      }
2462
0
      else
2463
0
      {
2464
0
        mutt_perror("%s", e_templ->body->filename);
2465
0
      }
2466
0
    }
2467
0
  }
2468
2469
  /* Set the message security unless:
2470
   * 1) crypto support is not enabled (WithCrypto==0)
2471
   * 2) pgp: header field was present during message editing with $edit_headers (e_templ->security != 0)
2472
   * 3) we are resending a message
2473
   * 4) we are recalling a postponed message (don't override the user's saved settings)
2474
   * 5) we are in batch mode
2475
   * But 3, 4, and 5, can be overridden with '-C' in the command line (flags & SEND_CLI_CRYPTO)
2476
   *
2477
   * This is done after allowing the user to edit the message so that security
2478
   * settings can be configured with send2-hook and $edit_headers.  */
2479
0
  if ((WithCrypto != 0) && (e_templ->security == 0) &&
2480
0
      ((flags & SEND_CLI_CRYPTO) || !(flags & (SEND_BATCH | SEND_POSTPONED | SEND_RESEND))))
2481
0
  {
2482
0
    bool c_autocrypt = false;
2483
0
    bool c_autocrypt_reply = false;
2484
2485
#ifdef USE_AUTOCRYPT
2486
    c_autocrypt = cs_subset_bool(sub, "autocrypt");
2487
    c_autocrypt_reply = cs_subset_bool(sub, "autocrypt_reply");
2488
#endif
2489
2490
0
    if (c_autocrypt && c_autocrypt_reply && e_cur && (e_cur->security & SEC_AUTOCRYPT))
2491
0
    {
2492
0
      e_templ->security |= (SEC_AUTOCRYPT | SEC_AUTOCRYPT_OVERRIDE | APPLICATION_PGP);
2493
0
    }
2494
0
    else
2495
0
    {
2496
0
      const bool c_crypt_auto_sign = cs_subset_bool(sub, "crypt_auto_sign");
2497
0
      const bool c_crypt_auto_encrypt = cs_subset_bool(sub, "crypt_auto_encrypt");
2498
0
      const bool c_crypt_reply_encrypt = cs_subset_bool(sub, "crypt_reply_encrypt");
2499
0
      const bool c_crypt_reply_sign = cs_subset_bool(sub, "crypt_reply_sign");
2500
0
      const bool c_crypt_reply_sign_encrypted = cs_subset_bool(sub, "crypt_reply_sign_encrypted");
2501
2502
0
      if (c_crypt_auto_sign)
2503
0
        e_templ->security |= SEC_SIGN;
2504
0
      if (c_crypt_auto_encrypt)
2505
0
        e_templ->security |= SEC_ENCRYPT;
2506
0
      if (c_crypt_reply_encrypt && e_cur && (e_cur->security & SEC_ENCRYPT))
2507
0
        e_templ->security |= SEC_ENCRYPT;
2508
0
      if (c_crypt_reply_sign && e_cur && (e_cur->security & SEC_SIGN))
2509
0
        e_templ->security |= SEC_SIGN;
2510
0
      if (c_crypt_reply_sign_encrypted && e_cur && (e_cur->security & SEC_ENCRYPT))
2511
0
        e_templ->security |= SEC_SIGN;
2512
2513
0
      const bool c_crypt_opportunistic_encrypt = cs_subset_bool(sub, "crypt_opportunistic_encrypt");
2514
2515
0
      if (((WithCrypto & APPLICATION_PGP) != 0) &&
2516
0
          ((e_templ->security & (SEC_ENCRYPT | SEC_SIGN)) || c_crypt_opportunistic_encrypt))
2517
0
      {
2518
0
        const bool c_pgp_auto_inline = cs_subset_bool(sub, "pgp_auto_inline");
2519
0
        const bool c_pgp_reply_inline = cs_subset_bool(sub, "pgp_reply_inline");
2520
2521
0
        if (c_pgp_auto_inline)
2522
0
          e_templ->security |= SEC_INLINE;
2523
0
        if (c_pgp_reply_inline && e_cur && (e_cur->security & SEC_INLINE))
2524
0
          e_templ->security |= SEC_INLINE;
2525
0
      }
2526
0
    }
2527
2528
0
    const bool c_crypt_opportunistic_encrypt = cs_subset_bool(sub, "crypt_opportunistic_encrypt");
2529
2530
0
    if (e_templ->security || c_crypt_opportunistic_encrypt)
2531
0
    {
2532
0
      const bool c_crypt_auto_pgp = cs_subset_bool(sub, "crypt_auto_pgp");
2533
0
      const bool c_crypt_auto_smime = cs_subset_bool(sub, "crypt_auto_smime");
2534
2535
      /* When replying / forwarding, use the original message's
2536
       * crypto system.  According to the documentation,
2537
       * smime_is_default should be disregarded here.
2538
       *
2539
       * Problem: At least with forwarding, this doesn't really
2540
       * make much sense. Should we have an option to completely
2541
       * disable individual mechanisms at run-time?  */
2542
0
      if (e_cur)
2543
0
      {
2544
0
        if (((WithCrypto & APPLICATION_PGP) != 0) && c_crypt_auto_pgp &&
2545
0
            (e_cur->security & APPLICATION_PGP))
2546
0
        {
2547
0
          e_templ->security |= APPLICATION_PGP;
2548
0
        }
2549
0
        else if (((WithCrypto & APPLICATION_SMIME) != 0) &&
2550
0
                 c_crypt_auto_smime && (e_cur->security & APPLICATION_SMIME))
2551
0
        {
2552
0
          e_templ->security |= APPLICATION_SMIME;
2553
0
        }
2554
0
      }
2555
2556
0
      const bool c_smime_is_default = cs_subset_bool(sub, "smime_is_default");
2557
2558
      /* No crypto mechanism selected? Use availability + smime_is_default
2559
       * for the decision.  */
2560
0
      if (!(e_templ->security & (APPLICATION_SMIME | APPLICATION_PGP)))
2561
0
      {
2562
0
        if (((WithCrypto & APPLICATION_SMIME) != 0) && c_crypt_auto_smime && c_smime_is_default)
2563
0
        {
2564
0
          e_templ->security |= APPLICATION_SMIME;
2565
0
        }
2566
0
        else if (((WithCrypto & APPLICATION_PGP) != 0) && c_crypt_auto_pgp)
2567
0
        {
2568
0
          e_templ->security |= APPLICATION_PGP;
2569
0
        }
2570
0
        else if (((WithCrypto & APPLICATION_SMIME) != 0) && c_crypt_auto_smime)
2571
0
        {
2572
0
          e_templ->security |= APPLICATION_SMIME;
2573
0
        }
2574
0
      }
2575
0
    }
2576
2577
    /* opportunistic encrypt relies on SMIME or PGP already being selected */
2578
0
    if (c_crypt_opportunistic_encrypt)
2579
0
    {
2580
      /* If something has already enabled encryption, e.g. `$crypt_auto_encrypt`
2581
       * or `$crypt_reply_encrypt`, then don't enable opportunistic encrypt for
2582
       * the message.  */
2583
0
      if (!(e_templ->security & (SEC_ENCRYPT | SEC_AUTOCRYPT)))
2584
0
      {
2585
0
        e_templ->security |= SEC_OPPENCRYPT;
2586
0
        crypt_opportunistic_encrypt(e_templ);
2587
0
      }
2588
0
    }
2589
2590
    /* No permissible mechanisms found.  Don't sign or encrypt. */
2591
0
    if (!(e_templ->security & (APPLICATION_SMIME | APPLICATION_PGP)))
2592
0
      e_templ->security = SEC_NO_FLAGS;
2593
0
  }
2594
2595
  /* Deal with the corner case where the crypto module backend is not available.
2596
   * This can happen if configured without PGP/SMIME and with GPGME, but
2597
   * $crypt_use_gpgme is unset.  */
2598
0
  if (e_templ->security && !crypt_has_module_backend(e_templ->security))
2599
0
  {
2600
0
    mutt_error(_("No crypto backend configured.  Disabling message security setting."));
2601
0
    e_templ->security = SEC_NO_FLAGS;
2602
0
  }
2603
2604
  /* specify a default fcc.  if we are in batchmode, only save a copy of
2605
   * the message if the value of $copy is yes or ask-yes */
2606
2607
0
  const enum QuadOption c_copy = cs_subset_quad(sub, "copy");
2608
2609
0
  if (buf_is_empty(fcc) && !(flags & SEND_POSTPONED_FCC) &&
2610
0
      (!(flags & SEND_BATCH) || (c_copy & 0x1)))
2611
0
  {
2612
    /* set the default FCC */
2613
0
    const bool killfrom = TAILQ_EMPTY(&e_templ->env->from);
2614
0
    if (killfrom)
2615
0
    {
2616
0
      mutt_addrlist_append(&e_templ->env->from, mutt_default_from(sub));
2617
0
    }
2618
0
    mutt_select_fcc(fcc, e_templ);
2619
0
    if (killfrom)
2620
0
    {
2621
0
      mutt_addrlist_clear(&e_templ->env->from);
2622
0
    }
2623
0
  }
2624
2625
0
  mutt_rfc3676_space_stuff(e_templ);
2626
2627
0
  mutt_update_encoding(e_templ->body, sub);
2628
2629
0
  if (!(flags & SEND_BATCH))
2630
0
  {
2631
0
  main_loop:
2632
2633
0
    buf_pretty_mailbox(fcc);
2634
0
    i = dlg_compose(e_templ, fcc,
2635
0
                    ((flags & SEND_NO_FREE_HEADER) ? MUTT_COMPOSE_NOFREEHEADER : 0), sub);
2636
0
    if (i == -1)
2637
0
    {
2638
      /* abort */
2639
0
      if (flags & SEND_NEWS)
2640
0
        mutt_message(_("Article not posted"));
2641
0
      else
2642
0
        mutt_message(_("Mail not sent"));
2643
0
      goto cleanup;
2644
0
    }
2645
0
    else if (i == 1)
2646
0
    {
2647
0
      if (postpone_message(e_templ, e_cur, buf_string(fcc), flags, sub) != 0)
2648
0
        goto main_loop;
2649
0
      mutt_message(_("Message postponed"));
2650
0
      rc = 1;
2651
0
      goto cleanup;
2652
0
    }
2653
0
  }
2654
2655
0
  if (!(flags & SEND_NEWS))
2656
0
  {
2657
0
    if ((mutt_addrlist_count_recips(&e_templ->env->to) == 0) &&
2658
0
        (mutt_addrlist_count_recips(&e_templ->env->cc) == 0) &&
2659
0
        (mutt_addrlist_count_recips(&e_templ->env->bcc) == 0))
2660
0
    {
2661
0
      if (flags & SEND_BATCH)
2662
0
      {
2663
0
        puts(_("No recipients specified"));
2664
0
        goto cleanup;
2665
0
      }
2666
2667
0
      mutt_warning(_("No recipients specified"));
2668
0
      goto main_loop;
2669
0
    }
2670
0
  }
2671
2672
0
  if (mutt_env_to_intl(e_templ->env, &tag, &err))
2673
0
  {
2674
0
    mutt_error(_("Bad IDN in '%s': '%s'"), tag, err);
2675
0
    FREE(&err);
2676
0
    if (flags & SEND_BATCH)
2677
0
      goto cleanup;
2678
0
    goto main_loop;
2679
0
  }
2680
2681
0
  const enum QuadOption c_abort_nosubject = cs_subset_quad(sub, "abort_nosubject");
2682
2683
0
  if (!e_templ->env->subject && !(flags & SEND_BATCH) &&
2684
0
      (query_quadoption(_("No subject, abort sending?"), sub, "abort_nosubject") != MUTT_NO))
2685
0
  {
2686
    /* if the abort is automatic, print an error message */
2687
0
    if (c_abort_nosubject == MUTT_YES)
2688
0
      mutt_error(_("No subject specified"));
2689
0
    goto main_loop;
2690
0
  }
2691
2692
0
  if ((flags & SEND_NEWS) && !e_templ->env->subject)
2693
0
  {
2694
0
    mutt_error(_("No subject specified"));
2695
0
    goto main_loop;
2696
0
  }
2697
2698
0
  if ((flags & SEND_NEWS) && !e_templ->env->newsgroups)
2699
0
  {
2700
0
    mutt_error(_("No newsgroup specified"));
2701
0
    goto main_loop;
2702
0
  }
2703
2704
0
  if (!(flags & SEND_BATCH) && abort_for_missing_attachments(e_templ->body, sub))
2705
0
  {
2706
0
    goto main_loop;
2707
0
  }
2708
2709
0
  if (e_templ->body->next)
2710
0
    e_templ->body = mutt_make_multipart(e_templ->body);
2711
2712
  /* Ok, we need to do it this way instead of handling all fcc stuff in
2713
   * one place in order to avoid going to main_loop with encoded "env"
2714
   * in case of error.  Ugh.  */
2715
2716
0
  mutt_encode_descriptions(e_templ->body, true, sub);
2717
2718
  /* Make sure that clear_content and free_clear_content are
2719
   * properly initialized -- we may visit this particular place in
2720
   * the code multiple times, including after a failed call to
2721
   * mutt_protect().  */
2722
2723
0
  clear_content = NULL;
2724
0
  free_clear_content = false;
2725
2726
0
  if (WithCrypto)
2727
0
  {
2728
0
    if (e_templ->security & (SEC_ENCRYPT | SEC_SIGN | SEC_AUTOCRYPT))
2729
0
    {
2730
      /* save the decrypted attachments */
2731
0
      clear_content = e_templ->body;
2732
2733
0
      if ((crypt_get_keys(e_templ, &pgpkeylist, false) == -1) ||
2734
0
          (mutt_protect(e_templ, pgpkeylist, false) == -1))
2735
0
      {
2736
0
        if (mutt_istr_equal(e_templ->body->subtype, "mixed"))
2737
0
          e_templ->body = mutt_remove_multipart(e_templ->body);
2738
2739
0
        FREE(&pgpkeylist);
2740
2741
0
        decode_descriptions(e_templ->body);
2742
2743
0
        if (flags & SEND_BATCH)
2744
0
        {
2745
0
          mutt_message(_("Missing encryption key; mail not sent"));
2746
0
          rc = -1;
2747
0
          goto cleanup;
2748
0
        }
2749
2750
0
        goto main_loop;
2751
0
      }
2752
0
      mutt_encode_descriptions(e_templ->body, false, sub);
2753
0
    }
2754
2755
    /* at this point, e_templ->body is one of the following three things:
2756
     * - multipart/signed.     In this case, clear_content is a child
2757
     * - multipart/encrypted.  In this case, clear_content exists independently
2758
     * - application/pgp.      In this case, clear_content exists independently
2759
     * - something else.       In this case, it's the same as clear_content */
2760
2761
    /* This is ugly -- lack of "reporting back" from mutt_protect(). */
2762
2763
0
    if (clear_content && (e_templ->body != clear_content) &&
2764
0
        (e_templ->body->parts != clear_content))
2765
0
      free_clear_content = true;
2766
0
  }
2767
2768
0
  if (!OptNoCurses)
2769
0
    mutt_message(_("Sending message..."));
2770
2771
0
  mutt_prepare_envelope(e_templ->env, true, sub);
2772
2773
0
  const bool c_fcc_before_send = cs_subset_bool(sub, "fcc_before_send");
2774
0
  if (c_fcc_before_send)
2775
0
    save_fcc(m, e_templ, fcc, clear_content, pgpkeylist, flags, &finalpath, sub);
2776
2777
0
  i = invoke_mta(m, e_templ, sub);
2778
0
  if (i < 0)
2779
0
  {
2780
0
    if (!(flags & SEND_BATCH))
2781
0
    {
2782
0
      if (!WithCrypto)
2783
0
        ; // do nothing
2784
0
      else if ((e_templ->security & (SEC_ENCRYPT | SEC_AUTOCRYPT)) ||
2785
0
               ((e_templ->security & SEC_SIGN) && (e_templ->body->type == TYPE_APPLICATION)))
2786
0
      {
2787
0
        if (e_templ->body != clear_content)
2788
0
        {
2789
0
          mutt_body_free(&e_templ->body); /* destroy PGP data */
2790
0
          e_templ->body = clear_content;  /* restore clear text. */
2791
0
        }
2792
0
      }
2793
0
      else if ((e_templ->security & SEC_SIGN) && (e_templ->body->type == TYPE_MULTIPART))
2794
0
      {
2795
0
        mutt_body_free(&e_templ->body->parts->next); /* destroy sig */
2796
0
        if (mutt_istr_equal(e_templ->body->subtype, "mixed") ||
2797
0
            mutt_istr_equal(e_templ->body->subtype, "signed"))
2798
0
        {
2799
0
          e_templ->body = mutt_remove_multipart(e_templ->body);
2800
0
        }
2801
0
      }
2802
2803
0
      FREE(&pgpkeylist);
2804
0
      mutt_env_free(&e_templ->body->mime_headers); /* protected headers */
2805
0
      mutt_param_delete(&e_templ->body->parameter, "protected-headers");
2806
0
      if (mutt_istr_equal(e_templ->body->subtype, "mixed"))
2807
0
        e_templ->body = mutt_remove_multipart(e_templ->body);
2808
0
      decode_descriptions(e_templ->body);
2809
0
      mutt_unprepare_envelope(e_templ->env);
2810
0
      FREE(&finalpath);
2811
0
      goto main_loop;
2812
0
    }
2813
0
    else
2814
0
    {
2815
0
      puts(_("Could not send the message"));
2816
0
      goto cleanup;
2817
0
    }
2818
0
  }
2819
2820
0
  if (!c_fcc_before_send)
2821
0
    save_fcc(m, e_templ, fcc, clear_content, pgpkeylist, flags, &finalpath, sub);
2822
2823
0
  if (!OptNoCurses)
2824
0
  {
2825
0
    mutt_message((i != 0)            ? _("Sending in background") :
2826
0
                 (flags & SEND_NEWS) ? _("Article posted") :
2827
0
                                       _("Mail sent"));
2828
#ifdef USE_NOTMUCH
2829
    const bool c_nm_record = cs_subset_bool(sub, "nm_record");
2830
    if (c_nm_record)
2831
      nm_record_message(m, finalpath, e_cur);
2832
#endif
2833
0
    mutt_sleep(0);
2834
0
  }
2835
2836
0
  if (WithCrypto)
2837
0
    FREE(&pgpkeylist);
2838
2839
0
  if ((WithCrypto != 0) && free_clear_content)
2840
0
    mutt_body_free(&clear_content);
2841
2842
  /* set 'replied' flag only if the user didn't change/remove
2843
   * In-Reply-To: and References: headers during edit */
2844
0
  if (flags & SEND_REPLY)
2845
0
  {
2846
0
    if (!(flags & SEND_POSTPONED) && m)
2847
0
    {
2848
0
      struct Email **ep = NULL;
2849
0
      ARRAY_FOREACH(ep, ea)
2850
0
      {
2851
0
        struct Email *e = *ep;
2852
0
        mutt_set_flag(m, e, MUTT_REPLIED, is_reply(e, e_templ), true);
2853
0
      }
2854
0
    }
2855
0
  }
2856
2857
0
  rc = 0;
2858
2859
0
cleanup:
2860
0
  buf_pool_release(&fcc);
2861
2862
0
  if (flags & SEND_POSTPONED)
2863
0
  {
2864
0
    if (WithCrypto & APPLICATION_PGP)
2865
0
    {
2866
0
      cs_subset_str_string_set(sub, "pgp_sign_as", pgp_sign_as, NULL);
2867
0
      FREE(&pgp_sign_as);
2868
0
    }
2869
0
    if (WithCrypto & APPLICATION_SMIME)
2870
0
    {
2871
0
      cs_subset_str_string_set(sub, "smime_sign_as", smime_sign_as, NULL);
2872
0
      FREE(&smime_sign_as);
2873
0
    }
2874
0
  }
2875
2876
0
  mutt_file_fclose(&fp_tmp);
2877
0
  if (!(flags & SEND_NO_FREE_HEADER))
2878
0
    email_free(&e_templ);
2879
2880
0
  FREE(&finalpath);
2881
0
  return rc;
2882
0
}
2883
2884
/**
2885
 * send_simple_email - Compose an email given a few basic ingredients
2886
 * @param m      Mailbox
2887
 * @param ea     Array of source Emails
2888
 * @param mailto mailto address to parse (can include fields such as subject)
2889
 * @param subj   Subject, if not overridden by mailto
2890
 * @param body   text/plain body
2891
 * @retval true Success
2892
 * @retval false Failure
2893
 */
2894
static bool send_simple_email(struct Mailbox *m, struct EmailArray *ea,
2895
                              const char *mailto, const char *subj, const char *body)
2896
0
{
2897
0
  struct Email *e = email_new();
2898
2899
  /* envelope */
2900
0
  e->env = mutt_env_new();
2901
0
  mutt_parse_mailto(e->env, NULL, mailto);
2902
0
  if (!e->env->subject)
2903
0
  {
2904
0
    mutt_env_set_subject(e->env, subj);
2905
0
  }
2906
0
  if (TAILQ_EMPTY(&e->env->to) && !mutt_addrlist_parse(&e->env->to, NULL))
2907
0
  {
2908
0
    mutt_warning(_("No recipient specified"));
2909
0
  }
2910
2911
  /* body */
2912
0
  e->body = mutt_body_new();
2913
0
  char ctype[] = "text/plain";
2914
0
  mutt_parse_content_type(ctype, e->body);
2915
2916
0
  struct Buffer *tempfile = buf_pool_get();
2917
0
  buf_mktemp(tempfile);
2918
0
  if (body)
2919
0
  {
2920
0
    FILE *fp = mutt_file_fopen(buf_string(tempfile), "w+");
2921
0
    if (!fp)
2922
0
    {
2923
0
      email_free(&e);
2924
0
      buf_pool_release(&tempfile);
2925
0
      return false;
2926
0
    }
2927
0
    fprintf(fp, "%s\n", body);
2928
0
    mutt_file_fclose(&fp);
2929
0
  }
2930
0
  e->body->filename = buf_strdup(tempfile);
2931
0
  e->body->unlink = true;
2932
0
  buf_pool_release(&tempfile);
2933
2934
0
  const int rc = mutt_send_message(SEND_DRAFT_FILE, e, NULL, m, ea, NeoMutt->sub);
2935
0
  return rc >= 0;
2936
0
}
2937
2938
/**
2939
 * mutt_send_list_subscribe - Send a mailing-list subscription email
2940
 * @param m Mailbox
2941
 * @param e Email carrying mailing-list subscription headers
2942
 * @retval true Success
2943
 * @retval false Failure
2944
 */
2945
bool mutt_send_list_subscribe(struct Mailbox *m, struct Email *e)
2946
0
{
2947
0
  if (!e || !e->env)
2948
0
  {
2949
0
    return false;
2950
0
  }
2951
2952
0
  const char *mailto = e->env->list_subscribe;
2953
0
  if (!mailto)
2954
0
  {
2955
0
    mutt_warning(_("No List-Subscribe header found"));
2956
0
    return false;
2957
0
  }
2958
2959
0
  struct EmailArray ea = ARRAY_HEAD_INITIALIZER;
2960
0
  ARRAY_ADD(&ea, e);
2961
0
  bool rc = send_simple_email(m, &ea, mailto, "Subscribe", "subscribe");
2962
0
  ARRAY_FREE(&ea);
2963
2964
0
  return rc;
2965
0
}
2966
2967
/**
2968
 * mutt_send_list_unsubscribe - Send a mailing-list unsubscription email
2969
 * @param m Mailbox
2970
 * @param e Email carrying mailing-list unsubscription headers
2971
 * @retval true Success
2972
 * @retval false Failure
2973
 */
2974
bool mutt_send_list_unsubscribe(struct Mailbox *m, struct Email *e)
2975
0
{
2976
0
  if (!e || !e->env)
2977
0
  {
2978
0
    return false;
2979
0
  }
2980
2981
0
  const char *mailto = e->env->list_unsubscribe;
2982
0
  if (!mailto)
2983
0
  {
2984
0
    mutt_warning(_("No List-Unsubscribe header found"));
2985
0
    return false;
2986
0
  }
2987
2988
0
  struct EmailArray ea = ARRAY_HEAD_INITIALIZER;
2989
0
  ARRAY_ADD(&ea, e);
2990
0
  bool rc = send_simple_email(m, &ea, mailto, "Unsubscribe", "unsubscribe");
2991
0
  ARRAY_FREE(&ea);
2992
2993
0
  return rc;
2994
0
}