* New version 2.21
[alpine.git] / alpine / reply.c
blob585172772600ef7c4adccad4ef3b4c05f6230779
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2008 University of Washington
8 * Copyright 2013-2017 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 Code here for forward and reply to mail
21 A few support routines as well
23 This code will forward and reply to MIME messages. The Alpine composer
24 at this time will only support non-text segments at the end of a
25 message so, things don't always come out as one would like. If you
26 always forward a message in MIME format, all will be correct. Forwarding
27 of nested MULTIPART messages will work. There's still a problem with
28 MULTIPART/ALTERNATIVE as the "first text part" rule doesn't allow modifying
29 the equivalent parts. Ideally, we should probably such segments as a
30 single attachment when forwarding/replying. It would also be real nice to
31 flatten out the nesting in the composer so pieces inside can get snipped.
33 The evolution continues...
34 =====*/
37 #include "headers.h"
38 #include "reply.h"
39 #include "status.h"
40 #include "radio.h"
41 #include "send.h"
42 #include "titlebar.h"
43 #include "mailindx.h"
44 #include "help.h"
45 #include "signal.h"
46 #include "mailcmd.h"
47 #include "alpine.h"
48 #include "roleconf.h"
49 #include "../pith/state.h"
50 #include "../pith/conf.h"
51 #include "../pith/init.h"
52 #include "../pith/filter.h"
53 #include "../pith/pattern.h"
54 #include "../pith/charset.h"
55 #include "../pith/mimedesc.h"
56 #include "../pith/remote.h"
57 #include "../pith/news.h"
58 #include "../pith/util.h"
59 #include "../pith/detoken.h"
60 #include "../pith/newmail.h"
61 #include "../pith/readfile.h"
62 #include "../pith/tempfile.h"
63 #include "../pith/busy.h"
64 #include "../pith/ablookup.h"
68 * Internal Prototypes
70 int reply_poster_followup(ENVELOPE *);
71 int sigedit_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
72 long new_mail_for_pico(int, int);
73 void cmd_input_for_pico(void);
74 int display_message_for_pico(int);
75 char *checkpoint_dir_for_pico(char *, size_t);
76 void resize_for_pico(void);
77 PCOLORS *colors_for_pico(void);
78 void free_pcolors(PCOLORS **);
81 /*----------------------------------------------------------------------
82 Fill in an outgoing message for reply and pass off to send
84 Args: pine_state -- The usual pine structure
86 Result: Reply is formatted and passed off to composer/mailer
88 Reply
90 - put senders address in To field
91 - search to and cc fields to see if we aren't the only recipients
92 - if other than us, ask if we should reply to all.
93 - if answer yes, fill out the To and Cc fields
94 - fill in the fcc argument
95 - fill in the subject field
96 - fill out the body and the attachments
97 - pass off to pine_send()
98 ---*/
99 int
100 reply(struct pine *pine_state, ACTION_S *role_arg)
102 ADDRESS *saved_from, *saved_to, *saved_cc, *saved_resent;
103 ADDRESS *us_in_to_and_cc, *ap;
104 ENVELOPE *env, *outgoing;
105 BODY *body, *orig_body = NULL;
106 REPLY_S reply;
107 void *msgtext = NULL;
108 char *tmpfix = NULL, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
109 long msgno, j, totalm, rflags, *seq = NULL;
110 int i, include_text = 0, times = -1, warned = 0, rv = 0,
111 flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0;
112 int rolemsg = 0, copytomsg = 0;
113 gf_io_t pc;
114 PAT_STATE dummy;
115 REDRAFT_POS_S *redraft_pos = NULL;
116 ACTION_S *role = NULL, *nrole;
117 #if defined(DOS) && !defined(_WINDOWS)
118 char *reserve;
119 #endif
121 outgoing = mail_newenvelope();
122 totalm = mn_total_cur(pine_state->msgmap);
123 seq = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));
125 dprint((4,"\n - reply (%s msgs) -\n", comatose(totalm)));
127 saved_from = (ADDRESS *) NULL;
128 saved_to = (ADDRESS *) NULL;
129 saved_cc = (ADDRESS *) NULL;
130 saved_resent = (ADDRESS *) NULL;
132 us_in_to_and_cc = (ADDRESS *) NULL;
134 outgoing->subject = NULL;
136 memset((void *)&reply, 0, sizeof(reply));
138 if(ps_global->full_header == 2
139 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
140 reply_raw_body = 1;
143 * We may have to loop through first to figure out what default
144 * reply-indent-string to offer...
146 if(mn_total_cur(pine_state->msgmap) > 1 &&
147 (F_ON(F_ALT_REPLY_MENU, pine_state)
148 || F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)) &&
149 reply_quote_str_contains_tokens()){
150 for(msgno = mn_first_cur(pine_state->msgmap);
151 msgno > 0L && !tmpfix;
152 msgno = mn_next_cur(pine_state->msgmap)){
154 env = pine_mail_fetchstructure(pine_state->mail_stream,
155 mn_m2raw(pine_state->msgmap, msgno),
156 NULL);
157 if(!env) {
158 q_status_message1(SM_ORDER,3,4,
159 _("Error fetching message %s. Can't reply to it."),
160 long2string(msgno));
161 goto done_early;
164 if(!tmpfix){ /* look for prefix? */
165 tmpfix = reply_quote_str(env);
166 if(prefix){
167 i = strcmp(tmpfix, prefix);
168 fs_give((void **) &tmpfix);
169 if(i){ /* don't check back if dissimilar */
170 fs_give((void **) &prefix);
172 * We free prefix, not tmpfix. We set tmpfix to prefix
173 * so that we won't come check again.
175 tmpfix = prefix = cpystr("> ");
178 else{
179 prefix = tmpfix;
180 tmpfix = NULL; /* check back later? */
185 tmpfix = prefix;
189 * Loop thru the selected messages building the
190 * outgoing envelope's destinations...
192 for(msgno = mn_first_cur(pine_state->msgmap);
193 msgno > 0L;
194 msgno = mn_next_cur(pine_state->msgmap)){
196 /*--- Grab current envelope ---*/
197 env = pine_mail_fetchstructure(pine_state->mail_stream,
198 seq[++times] = mn_m2raw(pine_state->msgmap, msgno),
199 NULL);
200 if(!env) {
201 q_status_message1(SM_ORDER,3,4,
202 _("Error fetching message %s. Can't reply to it."),
203 long2string(msgno));
204 goto done_early;
208 * We check for the prefix here if we didn't do it in the first
209 * loop above. This is just to save having to go through the loop
210 * twice in the cases where we don't need to.
212 if(!tmpfix){
213 tmpfix = reply_quote_str(env);
214 if(prefix){
215 i = strcmp(tmpfix, prefix);
216 fs_give((void **) &tmpfix);
217 if(i){ /* don't check back if dissimilar */
218 fs_give((void **) &prefix);
219 tmpfix = prefix = cpystr("> ");
222 else{
223 prefix = tmpfix;
224 tmpfix = NULL; /* check back later? */
229 * For consistency, the first question is always "include text?"
231 if(!times){ /* only first time */
232 char *p = cpystr(prefix);
234 if((include_text=reply_text_query(pine_state,totalm,env,&prefix)) < 0)
235 goto done_early;
237 /* edited prefix? */
238 if(strcmp(p, prefix))
239 tmpfix = prefix; /* stop looking */
241 fs_give((void **)&p);
245 * If we're agg-replying or there's a newsgroup and the user want's
246 * to post to news *and* via email, add relevant addresses to the
247 * outgoing envelope...
249 * The single message case gets us around the aggregate reply
250 * to messages in a mixed mail-news archive where some might
251 * have newsgroups and others not or whatever.
253 if(totalm > 1L || ((i = reply_news_test(env, outgoing)) & 1)){
254 if(totalm > 1)
255 flags |= RSF_FORCE_REPLY_TO;
257 if(!reply_harvest(pine_state, seq[times], NULL, env,
258 &saved_from, &saved_to, &saved_cc,
259 &saved_resent, &flags))
260 goto done_early;
262 else if(i == 0)
263 goto done_early;
265 /* collect a list of addresses that are us in to and cc fields */
266 if(env->to)
267 if((ap=reply_cp_addr(pine_state, 0L, NULL,
268 NULL, us_in_to_and_cc, NULL,
269 env->to, RCA_ONLY_US)) != NULL)
270 reply_append_addr(&us_in_to_and_cc, ap);
272 if(env->cc)
273 if((ap=reply_cp_addr(pine_state, 0L, NULL,
274 NULL, us_in_to_and_cc, NULL,
275 env->cc, RCA_ONLY_US)) != NULL)
276 reply_append_addr(&us_in_to_and_cc, ap);
278 /*------------ Format the subject line ---------------*/
279 if(outgoing->subject){
281 * if reply to more than one message, and all subjects
282 * match, so be it. otherwise set it to something generic...
284 if(!same_subject(outgoing->subject,
285 reply_subject(env->subject,tmp_20k_buf,SIZEOF_20KBUF))){
286 fs_give((void **)&outgoing->subject);
287 outgoing->subject = cpystr("Re: several messages");
290 else
291 outgoing->subject = reply_subject(env->subject, NULL, 0);
294 /* fill reply header */
295 reply_seed(pine_state, outgoing, env, saved_from,
296 saved_to, saved_cc, saved_resent,
297 &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
298 if(errmsg){
299 if(*errmsg){
300 q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
301 display_message(NO_OP_COMMAND);
304 fs_give((void **)&errmsg);
307 if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */
308 goto done_early;
310 /* Setup possible role */
311 if (ps_global->reply.role_chosen)
312 role = ps_global->reply.role_chosen;
313 else if(role_arg)
314 role = copy_action(role_arg);
316 if(!role){
317 rflags = ROLE_REPLY;
318 if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){
319 /* setup default role */
320 nrole = NULL;
321 j = mn_first_cur(pine_state->msgmap);
322 do {
323 role = nrole;
324 nrole = set_role_from_msg(pine_state, rflags,
325 mn_m2raw(pine_state->msgmap, j),
326 NULL);
327 } while(nrole && (!role || nrole == role)
328 && (j=mn_next_cur(pine_state->msgmap)) > 0L);
330 if(!role || nrole == role)
331 role = nrole;
332 else
333 role = NULL;
335 if(confirm_role(rflags, &role))
336 role = combine_inherited_role(role);
337 else{ /* cancel reply */
338 role = NULL;
339 cmd_cancelled("Reply");
340 goto done_early;
346 * Reply_seed may call c-client in get_fcc_based_on_to, so env may
347 * no longer be valid. Get it again.
348 * Similarly for set_role_from_message.
350 env = pine_mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);
352 if(role){
353 rolemsg++;
354 /* override fcc gotten in reply_seed */
355 if(role->fcc && fcc)
356 fs_give((void **) &fcc);
359 if(F_ON(F_COPY_TO_TO_FROM, pine_state) && !(role && role->from)){
361 * A list of all of our addresses that appear in the To
362 * and cc fields is in us_in_to_and_cc.
363 * If there is exactly one address in that list then
364 * use it for the outgoing From.
366 if(us_in_to_and_cc && !us_in_to_and_cc->next){
367 PINEFIELD *custom, *pf;
368 ADDRESS *a = NULL;
369 char *addr = NULL;
372 * Check to see if this address is different from what
373 * we would have used anyway. If it is, notify the user
374 * with a status message. This is pretty hokey because we're
375 * mimicking how pine_send would set the From address and
376 * there is no coordination between the two.
379 /* in case user has a custom From value */
380 custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
382 pf = (PINEFIELD *) fs_get(sizeof(*pf));
383 memset((void *) pf, 0, sizeof(*pf));
384 pf->name = cpystr("From");
385 pf->addr = &a;
386 if(set_default_hdrval(pf, custom) >= UseAsDef
387 && pf->textbuf && pf->textbuf[0]){
388 removing_trailing_white_space(pf->textbuf);
389 (void)removing_double_quotes(pf->textbuf);
390 build_address(pf->textbuf, &addr, NULL, NULL, NULL);
391 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
392 if(addr)
393 fs_give((void **) &addr);
396 if(!*pf->addr)
397 *pf->addr = generate_from();
399 if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
400 copytomsg++;
401 if(!role){
402 role = (ACTION_S *) fs_get(sizeof(*role));
403 memset((void *) role, 0, sizeof(*role));
404 role->is_a_role = 1;
407 role->from = us_in_to_and_cc;
408 us_in_to_and_cc = NULL;
411 free_customs(custom);
412 free_customs(pf);
416 if(role){
417 if(rolemsg && copytomsg)
418 q_status_message1(SM_ORDER, 3, 4,
419 _("Replying using role \"%s\" and To as From"), role->nick);
420 else if(rolemsg)
421 q_status_message1(SM_ORDER, 3, 4,
422 _("Replying using role \"%s\""), role->nick);
423 else if(copytomsg)
424 q_status_message(SM_ORDER, 3, 4,
425 _("Replying using incoming To as outgoing From"));
428 if(us_in_to_and_cc)
429 mail_free_address(&us_in_to_and_cc);
432 seq[++times] = -1L; /* mark end of sequence list */
434 /*========== Other miscelaneous fields ===================*/
435 outgoing->in_reply_to = reply_in_reply_to(env);
436 outgoing->references = reply_build_refs(env);
437 outgoing->message_id = generate_message_id();
439 if(!outgoing->to &&
440 !outgoing->cc &&
441 !outgoing->bcc &&
442 !outgoing->newsgroups)
443 q_status_message(SM_ORDER | SM_DING, 3, 6,
444 _("Warning: no valid addresses to reply to!"));
447 /*==================== Now fix up the message body ====================*/
450 * create storage object to be used for message text
452 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
453 q_status_message(SM_ORDER | SM_DING, 3, 4,
454 _("Error allocating message text"));
455 goto done_early;
458 gf_set_so_writec(&pc, (STORE_S *) msgtext);
460 /*---- Include the original text if requested ----*/
461 if(include_text && totalm > 1L){
462 char *sig;
463 int impl, template_len = 0, leave_cursor_at_top = 0;
466 env = NULL;
467 if(role && role->template){
468 char *filtered;
470 impl = 0;
471 filtered = detoken(role, env, 0,
472 ps_global->reply.signature_bottom,
473 0, &redraft_pos, &impl);
474 if(filtered){
475 if(*filtered){
476 so_puts((STORE_S *)msgtext, filtered);
477 if(impl == 1)
478 template_len = strlen(filtered);
479 else if(impl == 2)
480 leave_cursor_at_top++;
483 fs_give((void **)&filtered);
485 else
486 impl = 1;
488 else
489 impl = 1;
491 if((sig = reply_signature(role, env, &redraft_pos, &impl)) &&
492 !ps_global->reply.signature_bottom){
495 * If CURSORPOS was set explicitly in sig_file, and there was a
496 * template file before that, we need to adjust the offset by the
497 * length of the template file. However, if the template had
498 * a set CURSORPOS in it then impl was 2 before getting to the
499 * signature, so offset wouldn't have been reset by the signature
500 * CURSORPOS and offset would already be correct. That case will
501 * be ok here because template_len will be 0 and adding it does
502 * nothing. If template
503 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
504 * by the CURSORPOS in the sig. In that case we have to adjust the
505 * offset. That's what the next line does. It adjusts it if
506 * template_len is nonzero and if CURSORPOS was set in sig_file.
508 if(impl == 2)
509 redraft_pos->offset += template_len;
511 if(*sig)
512 so_puts((STORE_S *)msgtext, sig);
515 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
516 * is set, we won't have used it yet and want it to be non-NULL.
518 fs_give((void **)&sig);
522 * Only put cursor in sig if there is a cursorpos there but not
523 * one in the template, and sig-at-bottom.
525 if(!(sig && impl == 2 && !leave_cursor_at_top))
526 leave_cursor_at_top++;
528 body = mail_newbody();
529 body->type = TYPETEXT;
530 body->contents.text.data = msgtext;
532 for(msgno = mn_first_cur(pine_state->msgmap);
533 msgno > 0L;
534 msgno = mn_next_cur(pine_state->msgmap)){
536 if(env){ /* put 2 between messages */
537 gf_puts(NEWLINE, pc);
538 gf_puts(NEWLINE, pc);
541 /*--- Grab current envelope ---*/
542 env = pine_mail_fetchstructure(pine_state->mail_stream,
543 mn_m2raw(pine_state->msgmap, msgno),
544 &orig_body);
545 if(!env){
546 q_status_message1(SM_ORDER,3,4,
547 _("Error fetching message %s. Can't reply to it."),
548 long2string(mn_get_cur(pine_state->msgmap)));
549 goto done_early;
552 if(orig_body == NULL || orig_body->type == TYPETEXT || reply_raw_body) {
553 reply_delimiter(env, role, pc);
554 if(ps_global->reply.include_header)
555 reply_forward_header(pine_state->mail_stream,
556 mn_m2raw(pine_state->msgmap,msgno),
557 NULL, env, pc, prefix);
559 get_body_part_text(pine_state->mail_stream, reply_raw_body ? NULL : orig_body,
560 mn_m2raw(pine_state->msgmap, msgno),
561 reply_raw_body ? NULL : "1", 0L, pc, prefix,
562 NULL, GBPT_NONE);
564 else if(orig_body->type == TYPEMULTIPART) {
565 if(!warned++)
566 q_status_message(SM_ORDER,3,7,
567 _("WARNING! Attachments not included in multiple reply."));
569 if(orig_body->nested.part
570 && orig_body->nested.part->body.type == TYPETEXT) {
571 /*---- First part of the message is text -----*/
572 reply_delimiter(env, role, pc);
573 if(ps_global->reply.include_header)
574 reply_forward_header(pine_state->mail_stream,
575 mn_m2raw(pine_state->msgmap,
576 msgno),
577 NULL, env, pc, prefix);
579 get_body_part_text(pine_state->mail_stream,
580 &orig_body->nested.part->body,
581 mn_m2raw(pine_state->msgmap, msgno),
582 "1", 0L, pc, prefix, NULL, GBPT_NONE);
584 else{
585 q_status_message(SM_ORDER,0,3,
586 _("Multipart with no leading text part."));
589 else{
590 /*---- Single non-text message of some sort ----*/
591 q_status_message(SM_ORDER,3,3,
592 _("Non-text message not included."));
596 if(!leave_cursor_at_top){
597 long cnt = 0L;
598 unsigned char c;
600 /* rewind and count chars to start of sig file */
601 so_seek((STORE_S *)msgtext, 0L, 0);
602 while(so_readc(&c, (STORE_S *)msgtext))
603 cnt++;
605 if(!redraft_pos){
606 redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(*redraft_pos));
607 memset((void *)redraft_pos, 0,sizeof(*redraft_pos));
608 redraft_pos->hdrname = cpystr(":");
612 * If explicit cursor positioning in sig file,
613 * add offset to start of sig file plus offset into sig file.
614 * Else, just offset to start of sig file.
616 redraft_pos->offset += cnt;
619 if(sig){
620 if(*sig)
621 so_puts((STORE_S *)msgtext, sig);
623 fs_give((void **)&sig);
626 else{
627 msgno = mn_m2raw(pine_state->msgmap,
628 mn_get_cur(pine_state->msgmap));
630 /*--- Grab current envelope ---*/
631 env = pine_mail_fetchstructure(pine_state->mail_stream, msgno,
632 &orig_body);
635 * If the charset of the body part is different from ascii and
636 * charset conversion is _not_ happening, then preserve the original
637 * charset from the message so that if we don't enter any new
638 * chars with the hibit set we can use the original charset.
639 * If not all those things, then don't try to preserve it.
641 if(orig_body){
642 char *charset;
644 charset = parameter_val(orig_body->parameter, "charset");
645 if(charset && strucmp(charset, "us-ascii") != 0){
646 CONV_TABLE *ct;
649 * There is a non-ascii charset, is there conversion happening?
651 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
652 reply.orig_charset = charset;
653 charset = NULL;
657 if(charset)
658 fs_give((void **) &charset);
661 if(env) {
662 if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
663 msgno, NULL, msgtext, prefix,
664 include_text, role, 1, &redraft_pos)))
665 goto done_early;
667 else{
668 q_status_message1(SM_ORDER,3,4,
669 _("Error fetching message %s. Can't reply to it."),
670 long2string(mn_get_cur(pine_state->msgmap)));
671 goto done_early;
675 /* fill in reply structure */
676 reply.prefix = prefix;
677 reply.mailbox = cpystr(pine_state->mail_stream->mailbox);
678 reply.origmbox = cpystr(pine_state->mail_stream->original_mailbox
679 ? pine_state->mail_stream->original_mailbox
680 : pine_state->mail_stream->mailbox);
681 reply.data.uid.msgs = (imapuid_t *) fs_get((times + 1) * sizeof(imapuid_t));
682 if((reply.data.uid.validity = pine_state->mail_stream->uid_validity) != 0){
683 reply.uid = 1;
684 for(i = 0; i < times ; i++)
685 reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
687 else{
688 reply.msgno = 1;
689 for(i = 0; i < times ; i++)
690 reply.data.uid.msgs[i] = seq[i];
693 reply.data.uid.msgs[i] = 0; /* tie off list */
695 #if defined(DOS) && !defined(_WINDOWS)
696 free((void *)reserve);
697 #endif
699 /* partially formatted outgoing message */
700 pine_send(outgoing, &body, _("COMPOSE MESSAGE REPLY"),
701 role, fcc, &reply, redraft_pos, NULL, NULL, 0);
703 rv++;
704 pine_free_body(&body);
705 if(reply.mailbox)
706 fs_give((void **) &reply.mailbox);
707 if(reply.origmbox)
708 fs_give((void **) &reply.origmbox);
709 if(reply.orig_charset)
710 fs_give((void **) &reply.orig_charset);
711 fs_give((void **) &reply.data.uid.msgs);
712 done_early:
713 if((STORE_S *) msgtext)
714 gf_clear_so_writec((STORE_S *) msgtext);
716 mail_free_envelope(&outgoing);
717 mail_free_address(&saved_from);
718 mail_free_address(&saved_to);
719 mail_free_address(&saved_cc);
720 mail_free_address(&saved_resent);
722 fs_give((void **)&seq);
724 if(prefix)
725 fs_give((void **)&prefix);
727 if(fcc)
728 fs_give((void **) &fcc);
730 free_redraft_pos(&redraft_pos);
731 free_action(&role);
732 return rv;
737 * Ask user to confirm role choice, or choose another role.
739 * Args role -- A pointer into the pattern_h space at the default
740 * role to use. This can't be a copy, the comparison
741 * relies on it pointing at the actual role.
742 * This arg is also used to return a pointer to the
743 * chosen role.
745 * Returns 1 -- Yes, use role which is now in *role. This may not be
746 * the same as the role passed in and it may even be NULL.
747 * 0 -- Cancel reply.
750 confirm_role(long int rflags, ACTION_S **role)
752 ACTION_S *role_p = NULL;
753 ACTION_S *default_role = NULL;
754 char prompt[80], *prompt_fodder;
755 int cmd, done, ret = 1;
756 void (*prev_screen)(struct pine *) = ps_global->prev_screen,
757 (*redraw)(void) = ps_global->redrawer;
758 PAT_S *curpat, *pat;
759 PAT_STATE pstate;
760 HelpType help;
761 ESCKEY_S ekey[4];
763 if(!nonempty_patterns(ROLE_DO_ROLES, &pstate) || !role)
764 return(ret);
767 * If this is a reply or forward and the role doesn't require confirmation,
768 * then we just return with what was passed in.
770 if(((rflags & ROLE_REPLY) &&
771 *role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
772 ((rflags & ROLE_FORWARD) &&
773 *role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
774 ((rflags & ROLE_COMPOSE) &&
775 *role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
776 (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global)
777 && !ps_global->default_role))
778 return(ret);
781 * Check that there is at least one role available. This is among all
782 * roles, not just the reply roles or just the forward roles. That's
783 * because we have ^T take us to all the roles, not the category-specific
784 * roles.
786 if(!(pat = last_pattern(&pstate)))
787 return(ret);
789 ekey[0].ch = 'y';
790 ekey[0].rval = 'y';
792 ekey[1].ch = 'n';
793 ekey[1].rval = 'n';
795 ekey[2].ch = ctrl('T');
796 ekey[2].rval = 2;
797 ekey[2].name = "^T";
799 ekey[3].ch = -1;
801 /* check for more than one role available (or no role set) */
802 if(pat == first_pattern(&pstate) && *role) /* no ^T */
803 ekey[2].ch = -1;
806 * Setup default role
807 * Go through the loop just in case default_role doesn't point
808 * to a real current role.
810 if(ps_global->default_role){
811 for(pat = first_pattern(&pstate);
812 pat;
813 pat = next_pattern(&pstate)){
814 if(pat->action == ps_global->default_role){
815 default_role = ps_global->default_role;
816 break;
821 curpat = NULL;
823 /* override default */
824 if(*role){
825 for(pat = first_pattern(&pstate);
826 pat;
827 pat = next_pattern(&pstate)){
828 if(pat->action == *role){
829 curpat = pat;
830 break;
835 if(rflags & ROLE_REPLY)
836 prompt_fodder = _("Reply");
837 else if(rflags & ROLE_FORWARD)
838 prompt_fodder = _("Forward");
839 else
840 prompt_fodder = _("Compose");
842 done = 0;
843 while(!done){
844 if(curpat){
845 char buf[100];
847 help = h_role_confirm;
848 ekey[0].name = "Y";
849 ekey[0].label = N_("Yes");
850 ekey[1].name = "N";
851 if(default_role)
852 ekey[1].label = N_("No, use default role");
853 else
854 ekey[1].label = N_("No, use default settings");
856 ekey[2].label = N_("To Select Alternate Role");
858 if(curpat->patgrp && curpat->patgrp->nick)
859 /* TRANSLATORS: This is something like Use role <nickname of role> for Reply? */
860 snprintf(prompt, sizeof(prompt), _("Use role \"%s\" for %s? "),
861 short_str(curpat->patgrp->nick, buf, sizeof(buf), 50, MidDots),
862 prompt_fodder);
863 else
864 snprintf(prompt, sizeof(prompt),
865 _("Use role \"<a role without a nickname>\" for %s? "),
866 prompt_fodder);
868 else{
869 help = h_norole_confirm;
870 ekey[0].name = "Ret";
871 ekey[0].label = prompt_fodder;
872 ekey[1].name = "";
873 ekey[1].label = "";
874 ekey[2].label = N_("To Select Role");
875 snprintf(prompt, sizeof(prompt),
876 _("Press Return to %s using %s role, or ^T to select a role "),
877 prompt_fodder, default_role ? _("default") : _("no"));
880 prompt[sizeof(prompt)-1] = '\0';
882 cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
883 'y', 'x', help, RB_NORM);
885 switch(cmd){
886 case 'y': /* Accept */
887 done++;
888 *role = curpat ? curpat->action : default_role;
889 break;
891 case 'x': /* Cancel */
892 ret = 0;
893 /* fall through */
895 case 'n': /* NoRole */
896 done++;
897 *role = default_role;
898 break;
900 case 2: /* ^T */
901 if(role_select_screen(ps_global, &role_p, 0) >= 0){
902 if(role_p){
903 for(pat = first_pattern(&pstate);
904 pat;
905 pat = next_pattern(&pstate)){
906 if(pat->action == role_p){
907 curpat = pat;
908 break;
912 else
913 curpat = NULL;
916 ClearBody();
917 ps_global->mangled_body = 1;
918 ps_global->prev_screen = prev_screen;
919 ps_global->redrawer = redraw;
920 break;
924 return(ret);
929 * reply_to_all_query - Ask user about replying to all recipients
931 * Returns: -1 if cancel, 0 otherwise
932 * by reference: flagp
935 reply_to_all_query(int *flagp)
937 char prompt[80];
938 ESCKEY_S ekey[4];
939 char cmd;
941 ekey[0].name = "Y";
942 ekey[0].ch = 'y';
943 ekey[0].rval = 'y';
944 ekey[0].label = N_("Yes");
945 ekey[1].name = "N";
946 ekey[1].ch = 'n';
947 ekey[1].rval = 'n';
948 ekey[1].label = N_("No");
949 ekey[2].name = "P";
950 ekey[2].ch = 'p';
951 ekey[2].rval = 'p';
952 ekey[3].ch = -1;
954 ps_global->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps_global);
955 loop:
956 ekey[2].label = ps_global->reply.preserve_fields ? N_("Not Preserve") : N_("Preserve");
957 snprintf(prompt, sizeof(prompt), _("Reply to all recipients%s"),
958 ps_global->reply.preserve_fields ? _(" (preserving fields)? ") : "? ");
960 prompt[sizeof(prompt)-1] = '\0';
962 switch(cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
963 'n', 'x', h_preserve_field, RB_NORM)){
965 case 'x' :
966 return(-1);
968 case 'y' : /* set reply-all bit */
969 (*flagp) |= RSF_FORCE_REPLY_ALL;
970 break;
972 case 'n' : /* clear reply-all bit */
973 (*flagp) &= ~RSF_FORCE_REPLY_ALL;
974 break;
976 case 'p' :
977 ps_global->reply.preserve_fields =
978 (ps_global->reply.preserve_fields + 1) % 2;
979 goto loop; /* ugly, but saves me a variable */
980 break;
983 return(0);
988 * reply_using_replyto_query - Ask user about replying with reply-to value
990 * Returns: 'y' if yes
991 * 'x' if cancel
994 reply_using_replyto_query(void)
996 return(want_to("Use \"Reply-To:\" address instead of \"From:\" address",
997 'y', 'x', NO_HELP,WT_SEQ_SENSITIVE));
1002 * reply_text_query - Ask user about replying with text, or in the case
1003 * of alternate reply menu, set values to the answer to all questions
1004 * asked during reply.
1006 * Returns: 1 if include the text
1007 * 0 if we're NOT to include the text
1008 * -1 on cancel or error
1010 #define MAX_REPLY_OPTIONS 10
1012 reply_text_query(struct pine *ps, long int many, ENVELOPE *env, char **prefix)
1014 int ret, edited = 0, headers = 0;
1015 static ESCKEY_S compose_style[MAX_REPLY_OPTIONS];
1016 int ekey_num;
1017 int orig_sf;
1019 orig_sf = ps->reply.use_flowed = *prefix && **prefix ? (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1020 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1021 && (strcmp(*prefix, "> ") == 0
1022 || strcmp(*prefix, ">") == 0)) : 0;
1023 ps->reply.strip_signature = ps->full_header == 0
1024 && (F_ON(F_ENABLE_STRIP_SIGDASHES, ps)
1025 || F_ON(F_ENABLE_SIGDASHES, ps));
1026 ps->reply.keep_attach = F_ON(F_ATTACHMENTS_IN_REPLY, ps);
1027 ps->reply.include_header = F_ON(F_INCLUDE_HEADER, ps);
1028 ps->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps);
1029 ps->reply.signature_bottom = F_ON(F_SIG_AT_BOTTOM, ps);
1030 ps->reply.role_chosen = NULL;
1032 if(F_OFF(F_ALT_REPLY_MENU, ps)
1033 && F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
1034 && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps)
1035 && F_OFF(F_ALT_REPLY_MENU,ps))
1036 return(1);
1038 while(1){
1039 compose_style[ekey_num = 0].ch = 'y';
1040 compose_style[ekey_num].rval = 'y';
1041 compose_style[ekey_num].name = "Y";
1042 compose_style[ekey_num++].label = N_("Yes");
1044 compose_style[ekey_num].ch = 'n';
1045 compose_style[ekey_num].rval = 'n';
1046 compose_style[ekey_num].name = "N";
1047 compose_style[ekey_num++].label = N_("No");
1049 if (F_OFF(F_ALT_REPLY_MENU, ps)){ /**** Standard menu ****/
1050 /* TRANSLATORS: The final five %s's can probably be safely ignored */
1051 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply%s%s%s%s%s%s? "),
1052 (many > 1L) ? comatose(many) : "",
1053 (many > 1L) ? " " : "",
1054 (many > 1L) ? "s" : "",
1055 (many > 1L) ? "s" : "",
1056 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
1057 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
1058 ps->reply.role_chosen ? "\" and role \"" : "",
1059 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "",
1060 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
1061 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1063 if (F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
1064 compose_style[ekey_num].ch = ctrl('R');
1065 compose_style[ekey_num].rval = 'r';
1066 compose_style[ekey_num].name = "^R";
1067 compose_style[ekey_num++].label = N_("Edit Indent String");
1069 } else { /***** Alternate Reply Menu ********/
1070 unsigned which_help;
1072 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply (using \"%s%s%s\")? "),
1073 (many > 1L) ? comatose(many) : "",
1074 (many > 1L) ? " " : "",
1075 (many > 1L) ? "s" : "",
1076 *prefix,
1077 ps->reply.role_chosen ? "\" and role \"" : "",
1078 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "");
1079 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1081 compose_style[ekey_num].ch = 'h';
1082 compose_style[ekey_num].rval = 'H';
1083 compose_style[ekey_num].name = "H";
1084 compose_style[ekey_num++].label = ps->reply.include_header
1085 ? N_("No Header") : N_("Inc Headr");
1087 compose_style[ekey_num].ch = 's';
1088 compose_style[ekey_num].rval = 'S';
1089 compose_style[ekey_num].name = "S";
1090 compose_style[ekey_num++].label = ps->reply.strip_signature
1091 ? N_("No Strip"): N_("Strip Sig");
1093 compose_style[ekey_num].ch = 'a';
1094 compose_style[ekey_num].rval = 'A';
1095 compose_style[ekey_num].name = "A";
1096 compose_style[ekey_num++].label = ps->reply.keep_attach
1097 ? N_("No Attach"): N_("Inc Attach");
1099 compose_style[ekey_num].ch = 'b';
1100 compose_style[ekey_num].rval = 'B';
1101 compose_style[ekey_num].name = "B";
1102 compose_style[ekey_num++].label = ps->reply.signature_bottom
1103 ? N_("Sig Top") : N_("Sig Bottom");
1105 compose_style[ekey_num].ch = 'r';
1106 compose_style[ekey_num].rval = 'R';
1107 compose_style[ekey_num].name = "R";
1108 compose_style[ekey_num++].label = N_("Set Role");
1110 compose_style[ekey_num].ch = ctrl('R');
1111 compose_style[ekey_num].rval = 'r';
1112 compose_style[ekey_num].name = "^R";
1113 compose_style[ekey_num++].label = N_("Edit Indent String");
1115 /***** End Alt Reply Menu *********/
1118 compose_style[ekey_num].ch = -1;
1119 compose_style[ekey_num].name = NULL;
1120 compose_style[ekey_num].label = NULL;
1122 switch(ret = radio_buttons(tmp_20k_buf,
1123 ps->ttyo->screen_rows > 4
1124 ? -FOOTER_ROWS(ps) : -1,
1125 compose_style,
1126 (edited || headers || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps))
1127 ? 'y' : 'n',
1128 'x', NO_HELP, RB_SEQ_SENSITIVE)){
1129 case 'x':
1130 cmd_cancelled("Reply");
1131 return(-1);
1133 case 'A':
1134 ps->reply.keep_attach = (ps->reply.keep_attach + 1) % 2;
1135 break;
1137 case 'B':
1138 ps->reply.signature_bottom = (ps->reply.signature_bottom + 1) % 2;
1139 break;
1141 case 'F':
1142 ps->reply.use_flowed = (ps->reply.use_flowed + 1) % 2;
1143 break;
1145 case 'S':
1146 ps->reply.strip_signature = (ps->reply.strip_signature + 1) % 2;
1147 break;
1149 case 'H':
1150 ps->reply.include_header = (ps->reply.include_header + 1) % 2;
1151 headers = ps->reply.include_header;
1152 break;
1154 case 'R':
1156 void (*prev_screen)(struct pine *) = ps->prev_screen,
1157 (*redraw)(void) = ps->redrawer;
1158 ps->redrawer = NULL;
1159 ps->next_screen = SCREEN_FUN_NULL;
1160 if(role_select_screen(ps, &ps->reply.role_chosen, 1) < 0){
1161 cmd_cancelled("Reply");
1162 ps->next_screen = prev_screen;
1163 ps->redrawer = redraw;
1164 if (ps->redrawer)
1165 (*ps->redrawer)();
1166 continue;
1168 ps->next_screen = prev_screen;
1169 ps->redrawer = redraw;
1170 if(ps->reply.role_chosen)
1171 ps->reply.role_chosen = combine_inherited_role(ps->reply.role_chosen);
1173 if (ps->redrawer)
1174 (*ps->redrawer)();
1175 break;
1177 case 'r':
1178 if(prefix && *prefix){
1179 int done = 0;
1180 char buf[64];
1181 int flags;
1183 while(!done){
1184 strncpy(buf, *prefix, sizeof(buf)-1);
1185 buf[sizeof(buf)-1] = '\0';
1187 flags = OE_APPEND_CURRENT |
1188 OE_KEEP_TRAILING_SPACE |
1189 OE_DISALLOW_HELP |
1190 OE_SEQ_SENSITIVE;
1192 switch(optionally_enter(buf, ps->ttyo->screen_rows > 4
1193 ? -FOOTER_ROWS(ps) : -1,
1194 0, sizeof(buf), "Reply prefix : ",
1195 NULL, NO_HELP, &flags)){
1196 case 0: /* entry successful, continue */
1197 if(flags & OE_USER_MODIFIED){
1198 fs_give((void **)prefix);
1199 *prefix = removing_quotes(cpystr(buf));
1200 orig_sf = ps->reply.use_flowed = *prefix && **prefix ?
1201 (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1202 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1203 && (strcmp(*prefix, "> ") == 0
1204 || strcmp(*prefix, ">") == 0)) : 0;
1205 edited = 1;
1208 done++;
1209 break;
1211 case 1:
1212 cmd_cancelled("Reply");
1214 case -1:
1215 return(-1);
1217 case 4:
1218 EndInverse();
1219 ClearScreen();
1220 redraw_titlebar();
1221 if(ps_global->redrawer != NULL)
1222 (*ps->redrawer)();
1224 redraw_keymenu();
1225 break;
1227 case 3:
1228 break;
1230 case 2:
1231 default:
1232 q_status_message(SM_ORDER, 3, 4,
1233 "Programmer botch in reply_text_query()");
1234 return(-1);
1239 break;
1241 case 'y':
1242 return(1);
1244 case 'n':
1245 return(0);
1247 default:
1248 q_status_message1(SM_ORDER, 3, 4,
1249 "Invalid rval \'%s\'", pretty_command(ret));
1250 return(-1);
1257 * reply_poster_followup - return TRUE if "followup-to" set to "poster"
1259 * NOTE: queues status message indicating such
1262 reply_poster_followup(ENVELOPE *e)
1264 if(e && e->followup_to && !strucmp(e->followup_to, "poster")){
1265 q_status_message(SM_ORDER, 2, 3,
1266 _("Replying to Poster as specified in \"Followup-To\""));
1267 return(1);
1270 return(0);
1275 * reply_news_test - Test given envelope for newsgroup data and copy
1276 * it at the users request
1277 * RETURNS:
1278 * 0 if error or cancel
1279 * 1 reply via email
1280 * 2 follow-up via news
1281 * 3 do both
1284 reply_news_test(ENVELOPE *env, ENVELOPE *outgoing)
1286 int ret = 1;
1287 static ESCKEY_S news_opt[] = { {'f', 'f', "F", N_("Follow-up")},
1288 {'r', 'r', "R", N_("Reply")},
1289 {'b', 'b', "B", N_("Both")},
1290 {-1, 0, NULL, NULL} };
1292 if(env->newsgroups && *env->newsgroups && !reply_poster_followup(env))
1294 * Now that we know a newsgroups field is present,
1295 * ask if the user is posting a follow-up article...
1297 switch(radio_buttons(
1298 _("Follow-up to news group(s), Reply via email to author or Both? "),
1299 -FOOTER_ROWS(ps_global), news_opt, 'r', 'x',
1300 NO_HELP, RB_NORM)){
1301 case 'r' : /* Reply */
1302 ret = 1;
1303 break;
1305 case 'f' : /* Follow-Up via news ONLY! */
1306 ret = 2;
1307 break;
1309 case 'b' : /* BOTH */
1310 ret = 3;
1311 break;
1313 case 'x' : /* cancel or unknown response */
1314 default :
1315 cmd_cancelled("Reply");
1316 ret = 0;
1317 break;
1320 if(ret > 1){
1321 if(env->followup_to){
1322 q_status_message(SM_ORDER, 2, 3,
1323 _("Posting to specified Followup-To groups"));
1324 outgoing->newsgroups = cpystr(env->followup_to);
1326 else if(!outgoing->newsgroups)
1327 outgoing->newsgroups = cpystr(env->newsgroups);
1330 return(ret);
1334 /*----------------------------------------------------------------------
1335 Acquire the pinerc defined signature file
1336 It is allocated here and freed by the caller.
1338 file -- use this file
1339 prenewlines -- prefix the file contents with this many newlines
1340 postnewlines -- postfix the file contents with this many newlines
1341 is_sig -- this is a signature (not a template)
1342 ----*/
1343 char *
1344 get_signature_file(char *file, int prenewlines, int postnewlines, int is_sig)
1346 char *sig, *tmp_sig = NULL, sig_path[MAXPATH+1];
1347 int len, do_the_pipe_thang = 0;
1348 long sigsize = 0L, cntdown;
1350 sig_path[0] = '\0';
1351 if(!signature_path(file, sig_path, MAXPATH))
1352 return(NULL);
1354 dprint((5, "get_signature(%s)\n", sig_path));
1356 if(sig_path[(len=strlen(sig_path))-1] == '|'){
1357 if(is_sig && F_ON(F_DISABLE_PIPES_IN_SIGS, ps_global)){
1358 q_status_message(SM_ORDER | SM_DING, 3, 4,
1359 _("Pipes for signatures are administratively disabled"));
1360 return(NULL);
1362 else if(!is_sig && F_ON(F_DISABLE_PIPES_IN_TEMPLATES, ps_global)){
1363 q_status_message(SM_ORDER | SM_DING, 3, 4,
1364 _("Pipes for templates are administratively disabled"));
1365 return(NULL);
1368 sig_path[len-1] = '\0';
1369 removing_trailing_white_space(sig_path);
1370 do_the_pipe_thang++;
1373 if(!IS_REMOTE(sig_path) && ps_global->VAR_OPER_DIR &&
1374 !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
1375 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1376 /* TRANSLATORS: First arg is the directory name, second is
1377 the file user wants to read but can't. */
1378 _("Can't read file outside %s: %s"),
1379 ps_global->VAR_OPER_DIR, file);
1381 return(NULL);
1384 if(IS_REMOTE(sig_path) || can_access(sig_path, ACCESS_EXISTS) == 0){
1385 if(do_the_pipe_thang){
1386 if(can_access(sig_path, EXECUTE_ACCESS) == 0){
1387 STORE_S *store;
1388 int flags;
1389 PIPE_S *syspipe;
1390 gf_io_t pc, gc;
1391 long start;
1393 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
1395 flags = PIPE_READ | PIPE_STDERR | PIPE_NOSHELL;
1397 start = time(0);
1399 if((syspipe = open_system_pipe(sig_path, NULL, NULL, flags, 5,
1400 pipe_callback, pipe_report_error)) != NULL){
1401 unsigned char c;
1402 char *error, *q;
1404 gf_set_so_writec(&pc, store);
1405 gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, READ_FROM_LOCALE);
1406 gf_filter_init();
1408 if((error = gf_pipe(gc, pc)) != NULL){
1409 (void)close_system_pipe(&syspipe, NULL, pipe_callback);
1410 gf_clear_so_writec(store);
1411 so_give(&store);
1412 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1413 _("Can't get file: %s"), error);
1414 return(NULL);
1417 if(close_system_pipe(&syspipe, NULL, pipe_callback)){
1418 long now;
1420 now = time(0);
1421 q_status_message2(SM_ORDER, 3, 4,
1422 _("Error running program \"%s\"%s"),
1423 file,
1424 (now - start > 4) ? ": timed out" : "");
1427 gf_clear_so_writec(store);
1429 /* rewind and count chars */
1430 so_seek(store, 0L, 0);
1431 while(so_readc(&c, store) && sigsize < 100000L)
1432 sigsize++;
1434 /* allocate space */
1435 tmp_sig = fs_get((sigsize + 1) * sizeof(char));
1436 tmp_sig[0] = '\0';
1437 q = tmp_sig;
1439 /* rewind and copy chars, no prenewlines... */
1440 so_seek(store, 0L, 0);
1441 cntdown = sigsize;
1442 while(so_readc(&c, store) && cntdown-- > 0L)
1443 *q++ = c;
1445 *q = '\0';
1446 so_give(&store);
1448 else{
1449 so_give(&store);
1450 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1451 _("Error running program \"%s\""),
1452 file);
1455 else
1456 q_status_message(SM_ORDER | SM_DING, 3, 4,
1457 "Error allocating space for sig or template program");
1459 else
1460 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1461 /* TRANSLATORS: Arg is a program name */
1462 _("Can't execute \"%s\": Permission denied"),
1463 sig_path);
1465 else if((IS_REMOTE(sig_path) &&
1466 (tmp_sig = simple_read_remote_file(sig_path, REMOTE_SIG_SUBTYPE))) ||
1467 (tmp_sig = read_file(sig_path, READ_FROM_LOCALE)))
1468 sigsize = strlen(tmp_sig);
1469 else
1470 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1471 /* TRANSLATORS: First arg is error description, 2nd is
1472 filename */
1473 _("Error \"%s\" reading file \"%s\""),
1474 error_description(errno), sig_path);
1477 sig = get_signature_lit(tmp_sig, prenewlines, postnewlines, is_sig, 0);
1478 if(tmp_sig)
1479 fs_give((void **)&tmp_sig);
1481 return(sig);
1486 /*----------------------------------------------------------------------
1487 Partially set up message to forward and pass off to composer/mailer
1489 Args: pine_state -- The usual pine structure
1491 Result: outgoing envelope and body created and passed off to composer/mailer
1493 Create the outgoing envelope for the mail being forwarded, which is
1494 not much more than filling in the subject, and create the message body
1495 of the outgoing message which requires formatting the header from the
1496 envelope of the original messasge.
1497 ----------------------------------------------------------------------*/
1499 forward(struct pine *ps, ACTION_S *role_arg)
1501 char *sig;
1502 int ret, forward_raw_body = 0, rv = 0, i;
1503 long msgno, j, totalmsgs, rflags;
1504 ENVELOPE *env, *outgoing;
1505 BODY *orig_body, *body = NULL;
1506 REPLY_S reply;
1507 void *msgtext = NULL;
1508 gf_io_t pc;
1509 int impl, template_len = 0;
1510 PAT_STATE dummy;
1511 REDRAFT_POS_S *redraft_pos = NULL;
1512 ACTION_S *role = NULL, *nrole;
1513 #if defined(DOS) && !defined(_WINDOWS)
1514 char *reserve;
1515 #endif
1517 dprint((4, "\n - forward -\n"));
1519 memset((void *)&reply, 0, sizeof(reply));
1520 outgoing = mail_newenvelope();
1521 outgoing->message_id = generate_message_id();
1523 if(ps_global->full_header == 2
1524 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
1525 forward_raw_body = 1;
1527 if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
1528 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s forwarded messages...", comatose(totalmsgs));
1529 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1530 outgoing->subject = cpystr(tmp_20k_buf);
1532 else{
1533 /*---------- Get the envelope of message we're forwarding ------*/
1534 msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
1535 if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL))
1536 && (outgoing->subject = forward_subject(env, 0)))){
1537 q_status_message1(SM_ORDER,3,4,
1538 _("Error fetching message %s. Can't forward it."),
1539 long2string(msgno));
1540 goto clean;
1545 * as with all text bound for the composer, build it in
1546 * a storage object of the type it understands...
1548 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
1549 q_status_message(SM_ORDER | SM_DING, 3, 4,
1550 _("Error allocating message text"));
1551 goto clean;
1554 ret = (F_ON(F_FORWARD_AS_ATTACHMENT, ps_global))
1555 ? 'y'
1556 : (totalmsgs > 1L)
1557 ? want_to(_("Forward messages as a MIME digest"), 'y', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1558 : (ps->full_header == 2)
1559 ? want_to(_("Forward message as an attachment"), 'n', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1560 : 0;
1562 if(ret == 'x'){
1563 cmd_cancelled("Forward");
1564 so_give((STORE_S **)&msgtext);
1565 goto clean;
1568 /* Setup possible role */
1569 if(role_arg)
1570 role = copy_action(role_arg);
1572 if(!role){
1573 rflags = ROLE_FORWARD;
1574 if(nonempty_patterns(rflags, &dummy)){
1575 /* setup default role */
1576 nrole = NULL;
1577 j = mn_first_cur(ps->msgmap);
1578 do {
1579 role = nrole;
1580 nrole = set_role_from_msg(ps, rflags,
1581 mn_m2raw(ps->msgmap, j), NULL);
1582 } while(nrole && (!role || nrole == role)
1583 && (j=mn_next_cur(ps->msgmap)) > 0L);
1585 if(!role || nrole == role)
1586 role = nrole;
1587 else
1588 role = NULL;
1590 if(confirm_role(rflags, &role))
1591 role = combine_inherited_role(role);
1592 else{ /* cancel reply */
1593 role = NULL;
1594 cmd_cancelled("Forward");
1595 so_give((STORE_S **)&msgtext);
1596 goto clean;
1601 if(role)
1602 q_status_message1(SM_ORDER, 3, 4,
1603 _("Forwarding using role \"%s\""), role->nick);
1605 if(role && role->template){
1606 char *filtered;
1608 impl = 1;
1609 filtered = detoken(role, (totalmsgs == 1L) ? env : NULL,
1610 0, 0, 0, &redraft_pos, &impl);
1611 if(filtered){
1612 if(*filtered){
1613 so_puts((STORE_S *)msgtext, filtered);
1614 if(impl == 1)
1615 template_len = strlen(filtered);
1618 fs_give((void **)&filtered);
1621 else
1622 impl = 1;
1624 if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
1625 if(impl == 2)
1626 redraft_pos->offset += template_len;
1628 so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
1630 fs_give((void **)&sig);
1632 else
1633 so_puts((STORE_S *)msgtext, NEWLINE);
1635 gf_set_so_writec(&pc, (STORE_S *)msgtext);
1637 #if defined(DOS) && !defined(_WINDOWS)
1638 #if defined(LWP) || defined(PCTCP) || defined(PCNFS)
1639 #define IN_RESERVE 8192
1640 #else
1641 #define IN_RESERVE 16384
1642 #endif
1643 if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
1644 gf_clear_so_writec((STORE_S *) msgtext);
1645 so_give((STORE_S **)&msgtext);
1646 q_status_message(SM_ORDER | SM_DING, 3, 4,
1647 _("Insufficient memory for message text"));
1648 goto clean;
1650 #endif
1653 * If we're forwarding multiple messages *or* the forward-as-mime
1654 * is turned on and the users wants it done that way, package things
1655 * up...
1657 if(ret == 'y'){ /* attach message[s]!!! */
1658 PART **pp;
1659 long totalsize = 0L;
1661 /*---- New Body to start with ----*/
1662 body = mail_newbody();
1663 body->type = TYPEMULTIPART;
1665 /*---- The TEXT part/body ----*/
1666 body->nested.part = mail_newbody_part();
1667 body->nested.part->body.type = TYPETEXT;
1668 body->nested.part->body.contents.text.data = msgtext;
1670 if(totalmsgs > 1L){
1671 /*---- The MULTIPART/DIGEST part ----*/
1672 body->nested.part->next = mail_newbody_part();
1673 body->nested.part->next->body.type = TYPEMULTIPART;
1674 body->nested.part->next->body.subtype = cpystr("Digest");
1675 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Digest of %s messages", comatose(totalmsgs));
1676 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1677 body->nested.part->next->body.description = cpystr(tmp_20k_buf);
1678 pp = &(body->nested.part->next->body.nested.part);
1680 else
1681 pp = &(body->nested.part->next);
1683 /*---- The Message body subparts ----*/
1684 for(msgno = mn_first_cur(ps->msgmap);
1685 msgno > 0L;
1686 msgno = mn_next_cur(ps->msgmap)){
1688 msgno = mn_m2raw(ps->msgmap, msgno);
1689 env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL);
1691 if(forward_mime_msg(ps->mail_stream,msgno,NULL,env,pp,msgtext)){
1692 totalsize += (*pp)->body.size.bytes;
1693 pp = &((*pp)->next);
1695 else
1696 goto bomb;
1699 if(totalmsgs > 1L)
1700 body->nested.part->next->body.size.bytes = totalsize;
1702 else if(totalmsgs > 1L){
1703 int warned = 0;
1704 body = mail_newbody();
1705 body->type = TYPETEXT;
1706 body->contents.text.data = msgtext;
1707 env = NULL;
1709 for(msgno = mn_first_cur(ps->msgmap);
1710 msgno > 0L;
1711 msgno = mn_next_cur(ps->msgmap)){
1713 if(env){ /* put 2 between messages */
1714 gf_puts(NEWLINE, pc);
1715 gf_puts(NEWLINE, pc);
1718 /*--- Grab current envelope ---*/
1719 env = pine_mail_fetchstructure(ps->mail_stream,
1720 mn_m2raw(ps->msgmap, msgno),
1721 &orig_body);
1722 if(!env || !orig_body){
1723 q_status_message1(SM_ORDER,3,4,
1724 _("Error fetching message %s. Can't forward it."),
1725 long2string(msgno));
1726 goto bomb;
1729 if(orig_body == NULL || orig_body->type == TYPETEXT || forward_raw_body) {
1730 forward_delimiter(pc);
1731 reply_forward_header(ps->mail_stream,
1732 mn_m2raw(ps->msgmap, msgno),
1733 NULL, env, pc, "");
1735 if(!get_body_part_text(ps->mail_stream, forward_raw_body ? NULL : orig_body,
1736 mn_m2raw(ps->msgmap, msgno),
1737 forward_raw_body ? NULL : "1", 0L, pc,
1738 NULL, NULL, GBPT_NONE))
1739 goto bomb;
1740 } else if(orig_body->type == TYPEMULTIPART) {
1741 if(!warned++)
1742 q_status_message(SM_ORDER,3,7,
1743 _("WARNING! Attachments not included in multiple forward."));
1745 if(orig_body->nested.part &&
1746 orig_body->nested.part->body.type == TYPETEXT) {
1747 /*---- First part of the message is text -----*/
1748 forward_delimiter(pc);
1749 reply_forward_header(ps->mail_stream,
1750 mn_m2raw(ps->msgmap,msgno),
1751 NULL, env, pc, "");
1753 if(!get_body_part_text(ps->mail_stream,
1754 &orig_body->nested.part->body,
1755 mn_m2raw(ps->msgmap, msgno),
1756 "1", 0L, pc,
1757 NULL, NULL, GBPT_NONE))
1758 goto bomb;
1759 } else {
1760 q_status_message(SM_ORDER,0,3,
1761 _("Multipart with no leading text part!"));
1763 } else {
1764 /*---- Single non-text message of some sort ----*/
1765 q_status_message(SM_ORDER,0,3,
1766 _("Non-text message not included!"));
1770 else if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno,
1771 &orig_body))
1772 && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
1773 NULL, msgtext,
1774 FWD_NONE)))){
1775 q_status_message1(SM_ORDER,3,4,
1776 _("Error fetching message %s. Can't forward it."),
1777 long2string(msgno));
1778 goto clean;
1781 if(ret != 'y' && totalmsgs == 1L && orig_body){
1782 char *charset;
1784 charset = parameter_val(orig_body->parameter, "charset");
1785 if(charset && strucmp(charset, "us-ascii") != 0){
1786 CONV_TABLE *ct;
1789 * There is a non-ascii charset, is there conversion happening?
1791 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
1792 reply.orig_charset = charset;
1793 charset = NULL;
1797 if(charset)
1798 fs_give((void **) &charset);
1801 * I don't think orig_charset is ever used except possibly
1802 * right here. Hubert 2008-01-15.
1804 if(reply.orig_charset)
1805 reply.forw = 1;
1808 /* fill in reply structure */
1809 reply.forwarded = 1;
1810 reply.mailbox = cpystr(ps->mail_stream->mailbox);
1811 reply.origmbox = cpystr(ps->mail_stream->original_mailbox
1812 ? ps->mail_stream->original_mailbox
1813 : ps->mail_stream->mailbox);
1814 reply.data.uid.msgs = (imapuid_t *) fs_get((totalmsgs + 1) * sizeof(imapuid_t));
1815 if((reply.data.uid.validity = ps->mail_stream->uid_validity) != 0){
1816 reply.uid = 1;
1817 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1818 msgno > 0L;
1819 msgno = mn_next_cur(ps->msgmap), i++)
1820 reply.data.uid.msgs[i] = mail_uid(ps->mail_stream, mn_m2raw(ps->msgmap, msgno));
1822 else{
1823 reply.msgno = 1;
1824 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1825 msgno > 0L;
1826 msgno = mn_next_cur(ps->msgmap), i++)
1827 reply.data.uid.msgs[i] = mn_m2raw(ps->msgmap, msgno);
1830 reply.data.uid.msgs[i] = 0; /* tie off list */
1832 #if defined(DOS) && !defined(_WINDOWS)
1833 free((void *)reserve);
1834 #endif
1835 pine_send(outgoing, &body, "FORWARD MESSAGE",
1836 role, NULL, &reply, redraft_pos,
1837 NULL, NULL, 0);
1838 rv++;
1840 clean:
1841 if(body)
1842 pine_free_body(&body);
1844 if((STORE_S *) msgtext)
1845 gf_clear_so_writec((STORE_S *) msgtext);
1847 mail_free_envelope(&outgoing);
1848 free_redraft_pos(&redraft_pos);
1849 free_action(&role);
1851 if(reply.orig_charset)
1852 fs_give((void **)&reply.orig_charset);
1854 return rv;
1856 bomb:
1857 q_status_message(SM_ORDER | SM_DING, 4, 5,
1858 _("Error fetching message contents. Can't forward message."));
1859 goto clean;
1863 /*----------------------------------------------------------------------
1864 Partially set up message to forward and pass off to composer/mailer
1866 Args: pine_state -- The usual pine structure
1867 message -- The MESSAGECACHE of entry to reply to
1869 Result: outgoing envelope and body created and passed off to composer/mailer
1871 Create the outgoing envelope for the mail being forwarded, which is
1872 not much more than filling in the subject, and create the message body
1873 of the outgoing message which requires formatting the header from the
1874 envelope of the original messasge.
1875 ----------------------------------------------------------------------*/
1876 void
1877 forward_text(struct pine *pine_state, void *text, SourceType source)
1879 ENVELOPE *env;
1880 BODY *body;
1881 gf_io_t pc, gc;
1882 STORE_S *msgtext;
1883 char *enc_error, *sig;
1884 ACTION_S *role = NULL;
1885 PAT_STATE dummy;
1886 long rflags = ROLE_COMPOSE;
1888 if((msgtext = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
1889 env = mail_newenvelope();
1890 env->message_id = generate_message_id();
1891 body = mail_newbody();
1892 body->type = TYPETEXT;
1893 body->contents.text.data = (void *) msgtext;
1895 if(nonempty_patterns(rflags, &dummy)){
1897 * This is really more like Compose, even though it
1898 * is called Forward.
1900 if(confirm_role(rflags, &role))
1901 role = combine_inherited_role(role);
1902 else{ /* cancel */
1903 cmd_cancelled("Composition");
1904 display_message('x');
1905 mail_free_envelope(&env);
1906 pine_free_body(&body);
1907 return;
1911 if(role)
1912 q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
1913 role->nick);
1915 sig = detoken(role, NULL, 2, 0, 1, NULL, NULL);
1916 so_puts(msgtext, (sig && *sig) ? sig : NEWLINE);
1917 so_puts(msgtext, NEWLINE);
1918 so_puts(msgtext, "----- Included text -----");
1919 so_puts(msgtext, NEWLINE);
1920 if(sig)
1921 fs_give((void **)&sig);
1923 gf_filter_init();
1924 gf_set_so_writec(&pc, msgtext);
1925 gf_set_readc(&gc,text,(source == CharStar) ? strlen((char *)text) : 0L,
1926 source, 0);
1928 if((enc_error = gf_pipe(gc, pc)) == NULL){
1929 pine_send(env, &body, "SEND MESSAGE", role, NULL, NULL, NULL,
1930 NULL, NULL, 0);
1931 pine_state->mangled_screen = 1;
1933 else{
1934 q_status_message1(SM_ORDER | SM_DING, 3, 5,
1935 _("Error reading text \"%s\""),enc_error);
1936 display_message('x');
1939 gf_clear_so_writec(msgtext);
1940 mail_free_envelope(&env);
1941 pine_free_body(&body);
1943 else {
1944 q_status_message(SM_ORDER | SM_DING, 3, 4,
1945 _("Error allocating message text"));
1946 display_message('x');
1949 free_action(&role);
1953 /*----------------------------------------------------------------------
1954 Partially set up message to resend and pass off to mailer
1956 Args: pine_state -- The usual pine structure
1958 Result: outgoing envelope and body created and passed off to mailer
1960 Create the outgoing envelope for the mail being resent, which is
1961 not much more than filling in the subject, and create the message body
1962 of the outgoing message which requires formatting the header from the
1963 envelope of the original messasge.
1964 ----------------------------------------------------------------------*/
1966 bounce(struct pine *pine_state, ACTION_S *role)
1968 ENVELOPE *env;
1969 long msgno, rawno;
1970 char *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
1971 *prmpt_who = NULL, *prmpt_cnf = NULL;
1973 dprint((4, "\n - bounce -\n"));
1975 if(mn_total_cur(pine_state->msgmap) > 1L){
1976 save_toptr = &save_to;
1977 if(role)
1978 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages (using role %s) to : "),
1979 mn_total_cur(pine_state->msgmap), role->nick);
1980 else
1981 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages to : "),
1982 mn_total_cur(pine_state->msgmap));
1983 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1984 prmpt_who = cpystr(tmp_20k_buf);
1985 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Send %ld messages "),
1986 mn_total_cur(pine_state->msgmap));
1987 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1988 prmpt_cnf = cpystr(tmp_20k_buf);
1991 for(msgno = mn_first_cur(pine_state->msgmap);
1992 msgno > 0L;
1993 msgno = mn_next_cur(pine_state->msgmap)){
1995 rawno = mn_m2raw(pine_state->msgmap, msgno);
1996 if((env = pine_mail_fetchstructure(pine_state->mail_stream, rawno, NULL)) != NULL)
1997 errstr = bounce_msg(pine_state->mail_stream, rawno, NULL, role,
1998 save_toptr, env->subject, prmpt_who, prmpt_cnf);
1999 else
2000 errstr = _("Can't fetch Subject for Bounce");
2003 if(errstr){
2004 if(*errstr)
2005 q_status_message(SM_ORDER | SM_DING, 4, 7, errstr);
2007 break;
2011 if(save_to)
2012 fs_give((void **)&save_to);
2014 if(prmpt_who)
2015 fs_give((void **) &prmpt_who);
2017 if(prmpt_cnf)
2018 fs_give((void **) &prmpt_cnf);
2020 return(errstr ? 0 : 1);
2025 char *
2026 bounce_msg(MAILSTREAM *stream,
2027 long int rawno,
2028 char *part,
2029 ACTION_S *role,
2030 char **to,
2031 char *subject,
2032 char *pmt_who,
2033 char *pmt_cnf)
2035 char *errstr = NULL;
2036 int was_seen = -1;
2037 ENVELOPE *outgoing;
2038 BODY *body = NULL;
2039 MESSAGECACHE *mc;
2041 #ifdef SMIME
2042 /* When we bounce a message, we will leave the original message
2043 * intact, which means that it will not be signed or encrypted,
2044 * so we turn off signing and encrypting now. It will be turned
2045 * on again in send_exit_for_pico().
2047 if(ps_global->smime)
2048 ps_global->smime->do_sign = ps_global->smime->do_encrypt = 0;
2049 #endif /* SMIME */
2051 if((errstr = bounce_msg_body(stream, rawno, part, to, subject, &outgoing, &body, &was_seen)) == NULL){
2052 if(pine_simple_send(outgoing, &body, &role, pmt_who, pmt_cnf, to,
2053 !(to && *to) ? SS_PROMPTFORTO : 0) < 0){
2054 errstr = ""; /* p_s_s() better have explained! */
2055 /* clear seen flag */
2056 if(was_seen == 0 && rawno > 0L
2057 && stream && rawno <= stream->nmsgs
2058 && (mc = mail_elt(stream, rawno)) && mc->seen)
2059 mail_flag(stream, long2string(rawno), "\\SEEN", 0);
2063 /* Just for good measure... */
2064 mail_free_envelope(&outgoing);
2065 pine_free_body(&body);
2067 return(errstr); /* no problem-o */
2071 /*----------------------------------------------------------------------
2072 Serve up the current signature within pico for editing
2074 Args: file to edit
2076 Result: signature changed or not.
2077 ---*/
2078 char *
2079 signature_edit(char *sigfile, char *title)
2081 int editor_result;
2082 char sig_path[MAXPATH+1], errbuf[2000], *errstr = NULL;
2083 char *ret = NULL;
2084 STORE_S *msgso, *tmpso = NULL;
2085 gf_io_t gc, pc;
2086 PICO pbf;
2087 struct variable *vars = ps_global->vars;
2088 REMDATA_S *rd = NULL;
2090 if(!signature_path(sigfile, sig_path, MAXPATH))
2091 return(cpystr(_("No signature file defined.")));
2093 if(IS_REMOTE(sigfile)){
2094 rd = rd_create_remote(RemImap, sig_path, REMOTE_SIG_SUBTYPE,
2095 NULL, "Error: ",
2096 _("Can't access remote configuration."));
2097 if(!rd)
2098 return(cpystr(_("Error attempting to edit remote configuration")));
2100 (void)rd_read_metadata(rd);
2102 if(rd->access == MaybeRorW){
2103 if(rd->read_status == 'R')
2104 rd->access = ReadOnly;
2105 else
2106 rd->access = ReadWrite;
2109 if(rd->access != NoExists){
2111 rd_check_remvalid(rd, 1L);
2114 * If the cached info says it is readonly but
2115 * it looks like it's been fixed now, change it to readwrite.
2117 if(rd->read_status == 'R'){
2118 rd_check_readonly_access(rd);
2119 if(rd->read_status == 'W'){
2120 rd->access = ReadWrite;
2121 rd->flags |= REM_OUTOFDATE;
2123 else
2124 rd->access = ReadOnly;
2128 if(rd->flags & REM_OUTOFDATE){
2129 if(rd_update_local(rd) != 0){
2131 dprint((1,
2132 "signature_edit: rd_update_local failed\n"));
2133 rd_close_remdata(&rd);
2134 return(cpystr(_("Can't access remote sig")));
2137 else
2138 rd_open_remote(rd);
2140 if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
2141 rd_close_remdata(&rd);
2142 return(cpystr(_("Can't get write permission for remote sig")));
2145 rd->flags |= DO_REMTRIM;
2147 strncpy(sig_path, rd->lf, sizeof(sig_path)-1);
2148 sig_path[sizeof(sig_path)-1] = '\0';
2151 standard_picobuf_setup(&pbf);
2152 pbf.tty_fix = PineRaw;
2153 pbf.composer_help = h_composer_sigedit;
2154 pbf.exittest = sigedit_exit_for_pico;
2155 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2156 ? upload_msg_to_pico : NULL;
2157 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2158 ? VAR_EDITOR : NULL;
2159 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2160 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2161 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2162 pbf.allow_flowed_text = 0;
2164 pbf.pine_anchor = set_titlebar(title,
2165 ps_global->mail_stream,
2166 ps_global->context_current,
2167 ps_global->cur_folder,
2168 ps_global->msgmap,
2169 0, FolderName, 0, 0, NULL);
2171 /* NOTE: at this point, alot of pico struct fields are null'd out
2172 * thanks to the leading memset; in particular "headents" which tells
2173 * pico to behave like a normal editor (though modified slightly to
2174 * let the caller dictate the file to edit and such)...
2177 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, sig_path)){
2178 size_t l;
2180 l = strlen(VAR_OPER_DIR) + 100;
2181 ret = (char *) fs_get((l+1) * sizeof(char));
2182 snprintf(ret, l+1, _("Can't edit file outside of %s"), VAR_OPER_DIR);
2183 ret[l] = '\0';
2184 return(ret);
2188 * Now alloc and init the text to pass pico
2190 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2191 ret = cpystr(_("Error allocating space for file"));
2192 dprint((1, "Can't alloc space for signature_edit"));
2193 return(ret);
2195 else
2196 pbf.msgtext = so_text(msgso);
2198 if(can_access(sig_path, READ_ACCESS) == 0
2199 && !(tmpso = so_get(FileStar, sig_path, READ_ACCESS|READ_FROM_LOCALE))){
2200 char *problem = error_description(errno);
2202 snprintf(errbuf, sizeof(errbuf), _("Error editing \"%s\": %s"),
2203 sig_path, problem ? problem : "<NULL>");
2204 errbuf[sizeof(errbuf)-1] = '\0';
2205 ret = cpystr(errbuf);
2207 dprint((1, "signature_edit: can't open %s: %s", sig_path,
2208 problem ? problem : "<NULL>"));
2209 return(ret);
2211 else if(tmpso){ /* else, fill pico's edit buffer */
2212 gf_set_so_readc(&gc, tmpso); /* read from file, write pico buf */
2213 gf_set_so_writec(&pc, msgso);
2214 gf_filter_init(); /* no filters needed */
2215 if((errstr = gf_pipe(gc, pc)) != NULL){
2216 snprintf(errbuf, sizeof(errbuf), _("Error reading file: \"%s\""), errstr);
2217 errbuf[sizeof(errbuf)-1] = '\0';
2218 ret = cpystr(errbuf);
2221 gf_clear_so_readc(tmpso);
2222 gf_clear_so_writec(msgso);
2223 so_give(&tmpso);
2226 if(!errstr){
2227 #ifdef _WINDOWS
2228 mswin_setwindowmenu (MENU_COMPOSER);
2229 #endif
2231 /*------ OK, Go edit the signature ------*/
2232 editor_result = pico(&pbf);
2234 #ifdef _WINDOWS
2235 mswin_setwindowmenu (MENU_DEFAULT);
2236 #endif
2237 if(editor_result & COMP_GOTHUP){
2238 hup_signal(); /* do what's normal for a hup */
2240 else{
2241 fix_windsize(ps_global);
2242 init_signals();
2245 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2247 else{
2248 /*------ Must have an edited buffer, write it to .sig -----*/
2249 our_unlink(sig_path); /* blast old copy */
2250 if((tmpso = so_get(FileStar, sig_path, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
2251 so_seek(msgso, 0L, 0);
2252 gf_set_so_readc(&gc, msgso); /* read from pico buf */
2253 gf_set_so_writec(&pc, tmpso); /* write sig file */
2254 gf_filter_init(); /* no filters needed */
2255 if((errstr = gf_pipe(gc, pc)) != NULL){
2256 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2257 errstr);
2258 errbuf[sizeof(errbuf)-1] = '\0';
2259 ret = cpystr(errbuf);
2262 gf_clear_so_readc(msgso);
2263 gf_clear_so_writec(tmpso);
2264 if(so_give(&tmpso)){
2265 errstr = error_description(errno);
2266 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2267 errstr);
2268 errbuf[sizeof(errbuf)-1] = '\0';
2269 ret = cpystr(errbuf);
2272 if(IS_REMOTE(sigfile)){
2273 int e, we_cancel;
2274 char datebuf[200];
2276 datebuf[0] = '\0';
2278 we_cancel = busy_cue("Copying to remote sig", NULL, 1);
2279 if((e = rd_update_remote(rd, datebuf)) != 0){
2280 if(e == -1){
2281 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2282 _("Error opening temporary sig file %s: %s"),
2283 rd->lf, error_description(errno));
2284 dprint((1,
2285 "write_remote_sig: error opening temp file %s\n",
2286 rd->lf ? rd->lf : "?"));
2288 else{
2289 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2290 _("Error copying to %s: %s"),
2291 rd->rn, error_description(errno));
2292 dprint((1,
2293 "write_remote_sig: error copying from %s to %s\n",
2294 rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
2297 q_status_message(SM_ORDER | SM_DING, 5, 5,
2298 _("Copy of sig to remote folder failed, changes NOT saved remotely"));
2300 else{
2301 rd_update_metadata(rd, datebuf);
2302 rd->read_status = 'W';
2305 rd_close_remdata(&rd);
2307 if(we_cancel)
2308 cancel_busy_cue(-1);
2311 else{
2312 snprintf(errbuf, sizeof(errbuf), _("Error writing \"%s\""), sig_path);
2313 errbuf[sizeof(errbuf)-1] = '\0';
2314 ret = cpystr(errbuf);
2315 dprint((1, "signature_edit: can't write %s",
2316 sig_path));
2321 standard_picobuf_teardown(&pbf);
2322 so_give(&msgso);
2323 return(ret);
2327 /*----------------------------------------------------------------------
2328 Serve up the current signature within pico for editing
2330 Args: literal signature to edit
2332 Result: raw edited signature is returned in result arg
2333 ---*/
2334 char *
2335 signature_edit_lit(char *litsig, char **result, char *title, HelpType composer_help)
2337 int editor_result;
2338 char *errstr = NULL;
2339 char *ret = NULL;
2340 STORE_S *msgso;
2341 PICO pbf;
2342 struct variable *vars = ps_global->vars;
2344 standard_picobuf_setup(&pbf);
2345 pbf.tty_fix = PineRaw;
2346 pbf.search_help = h_sigedit_search;
2347 pbf.composer_help = composer_help;
2348 pbf.exittest = sigedit_exit_for_pico;
2349 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2350 ? upload_msg_to_pico : NULL;
2351 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2352 ? VAR_EDITOR : NULL;
2353 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2354 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2355 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2356 pbf.allow_flowed_text = 0;
2358 pbf.pine_anchor = set_titlebar(title,
2359 ps_global->mail_stream,
2360 ps_global->context_current,
2361 ps_global->cur_folder,
2362 ps_global->msgmap,
2363 0, FolderName, 0, 0, NULL);
2365 /* NOTE: at this point, alot of pico struct fields are null'd out
2366 * thanks to the leading memset; in particular "headents" which tells
2367 * pico to behave like a normal editor (though modified slightly to
2368 * let the caller dictate the file to edit and such)...
2372 * Now alloc and init the text to pass pico
2374 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2375 ret = cpystr(_("Error allocating space"));
2376 dprint((1, "Can't alloc space for signature_edit_lit"));
2377 return(ret);
2379 else
2380 pbf.msgtext = so_text(msgso);
2382 so_puts(msgso, litsig ? litsig : "");
2385 if(!errstr){
2386 #ifdef _WINDOWS
2387 mswin_setwindowmenu (MENU_COMPOSER);
2388 #endif
2390 /*------ OK, Go edit the signature ------*/
2391 editor_result = pico(&pbf);
2393 #ifdef _WINDOWS
2394 mswin_setwindowmenu (MENU_DEFAULT);
2395 #endif
2396 if(editor_result & COMP_GOTHUP){
2397 hup_signal(); /* do what's normal for a hup */
2399 else{
2400 fix_windsize(ps_global);
2401 init_signals();
2404 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2405 ret = cpystr(_("Edit Cancelled"));
2407 else{
2408 /*------ Must have an edited buffer, write it to .sig -----*/
2409 unsigned char c;
2410 int cnt = 0;
2411 char *p;
2413 so_seek(msgso, 0L, 0);
2414 while(so_readc(&c, msgso))
2415 cnt++;
2417 *result = (char *)fs_get((cnt+1) * sizeof(char));
2418 p = *result;
2419 so_seek(msgso, 0L, 0);
2420 while(so_readc(&c, msgso))
2421 *p++ = c;
2423 *p = '\0';
2427 standard_picobuf_teardown(&pbf);
2428 so_give(&msgso);
2429 return(ret);
2434 * Returns 0 for Save Changes and exit
2435 * 1 for Cancel Exit
2436 * -1 exit but Dont Save Changes
2439 sigedit_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
2440 char **result)
2442 int rv;
2443 char *rstr = NULL;
2444 void (*redraw)(void) = ps_global->redrawer;
2445 static ESCKEY_S opts[] = {
2446 {'s', 's', "S", N_("Save changes")},
2447 {'d', 'd', "D", N_("Don't save changes")},
2448 {-1, 0, NULL, NULL}
2451 ps_global->redrawer = redraw_pico;
2452 fix_windsize(ps_global);
2454 while(1){
2455 rv = radio_buttons(_("Exit editor? "),
2456 -FOOTER_ROWS(ps_global), opts,
2457 's', 'x', h_exit_editor, RB_NORM);
2458 if(rv == 's'){ /* user ACCEPTS! */
2459 break;
2461 else if(rv == 'd'){ /* Declined! */
2462 rstr = _("No Changes Saved");
2463 break;
2465 else if(rv == 'x'){ /* Cancelled! */
2466 rstr = _("Exit Cancelled");
2467 break;
2471 if(result)
2472 *result = rstr;
2474 ps_global->redrawer = redraw;
2475 return((rv == 's') ? 0 : (rv == 'd') ? -1 : 1);
2480 * Common stuff we almost always want to set when calling pico.
2482 void
2483 standard_picobuf_setup(PICO *pbf)
2485 memset(pbf, 0, sizeof(*pbf));
2487 pbf->pine_version = ALPINE_VERSION;
2488 pbf->fillcolumn = ps_global->composer_fillcol;
2489 pbf->menu_rows = FOOTER_ROWS(ps_global) - 1;
2490 pbf->colors = colors_for_pico();
2491 pbf->wordseps = user_wordseps(ps_global->VAR_WORDSEPS);
2492 pbf->helper = helper;
2493 pbf->showmsg = display_message_for_pico;
2494 pbf->suspend = do_suspend;
2495 pbf->keybinput = cmd_input_for_pico;
2496 pbf->tty_fix = ttyfix; /* watch out for this one */
2497 pbf->newmail = new_mail_for_pico;
2498 pbf->ckptdir = checkpoint_dir_for_pico;
2499 pbf->resize = resize_for_pico;
2500 pbf->input_cs = ps_global->input_cs;
2501 pbf->winch_cleanup = winch_cleanup;
2502 pbf->search_help = h_composer_search;
2503 pbf->ins_help = h_composer_ins;
2504 pbf->ins_m_help = h_composer_ins_m;
2505 pbf->composer_help = h_composer;
2506 pbf->browse_help = h_composer_browse;
2507 pbf->attach_help = h_composer_ctrl_j;
2509 pbf->pine_flags =
2510 ( (F_ON(F_CAN_SUSPEND,ps_global) ? P_SUSPEND : 0L)
2511 | (F_ON(F_USE_FK,ps_global) ? P_FKEYS : 0L)
2512 | (ps_global->restricted ? P_SECURE : 0L)
2513 | (F_ON(F_ALT_ED_NOW,ps_global) ? P_ALTNOW : 0L)
2514 | (F_ON(F_USE_CURRENT_DIR,ps_global) ? P_CURDIR : 0L)
2515 | (F_ON(F_SUSPEND_SPAWNS,ps_global) ? P_SUBSHELL : 0L)
2516 | (F_ON(F_COMPOSE_MAPS_DEL,ps_global) ? P_DELRUBS : 0L)
2517 | (F_ON(F_ENABLE_TAB_COMPLETE,ps_global) ? P_COMPLETE : 0L)
2518 | (F_ON(F_SHOW_CURSOR,ps_global) ? P_SHOCUR : 0L)
2519 | (F_ON(F_DEL_FROM_DOT,ps_global) ? P_DOTKILL : 0L)
2520 | (F_ON(F_ENABLE_DOT_FILES,ps_global) ? P_DOTFILES : 0L)
2521 | (F_ON(F_ALLOW_GOTO,ps_global) ? P_ALLOW_GOTO : 0L)
2522 | (F_ON(F_ENABLE_SEARCH_AND_REPL,ps_global) ? P_REPLACE : 0L)
2523 | (!ps_global->pass_ctrl_chars
2524 && !ps_global->pass_c1_ctrl_chars ? P_HICTRL : 0L)
2525 | ((F_ON(F_ENABLE_ALT_ED,ps_global)
2526 || F_ON(F_ALT_ED_NOW,ps_global)
2527 || (ps_global->VAR_EDITOR
2528 && ps_global->VAR_EDITOR[0]
2529 && ps_global->VAR_EDITOR[0][0]))
2530 ? P_ADVANCED : 0L)
2531 | ((!ps_global->keyboard_charmap
2532 || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
2533 ? P_HIBITIGN : 0L));
2535 if(ps_global->VAR_OPER_DIR){
2536 pbf->oper_dir = ps_global->VAR_OPER_DIR;
2537 pbf->pine_flags |= P_TREE;
2540 pbf->home_dir = ps_global->home_dir;
2544 void
2545 standard_picobuf_teardown(PICO *pbf)
2547 if(pbf){
2548 if(pbf->colors)
2549 free_pcolors(&pbf->colors);
2551 if(pbf->wordseps)
2552 fs_give((void **) &pbf->wordseps);
2557 /*----------------------------------------------------------------------
2558 Call back for pico to use to check for new mail.
2560 Args: cursor -- pointer to in to tell caller if cursor location changed
2561 if NULL, turn off cursor positioning.
2562 timing -- whether or not it's a good time to check
2565 Returns: returns 1 on success, zero on error.
2566 ----*/
2567 long
2568 new_mail_for_pico(int timing, int status)
2571 * If we're not interested in the status, don't display the busy
2572 * cue either...
2574 /* don't know where the cursor's been, reset it */
2575 clear_cursor_pos();
2576 return(new_mail(0, timing,
2577 (status ? NM_STATUS_MSG : NM_NONE) | NM_DEFER_SORT
2578 | NM_FROM_COMPOSER));
2582 void
2583 cmd_input_for_pico(void)
2585 zero_new_mail_count();
2589 /*----------------------------------------------------------------------
2590 Call back for pico to get newmail status messages displayed
2592 Args: x -- char processed
2594 Returns:
2595 ----*/
2597 display_message_for_pico(int x)
2599 int rv;
2601 clear_cursor_pos(); /* can't know where cursor is */
2602 mark_status_dirty(); /* don't count on cached text */
2603 fix_windsize(ps_global);
2604 init_sigwinch();
2605 display_message(x);
2606 rv = ps_global->mangled_screen;
2607 ps_global->mangled_screen = 0;
2608 return(rv);
2612 /*----------------------------------------------------------------------
2613 Call back for pico to get desired directory for its check point file
2615 Args: s -- buffer to write directory name
2616 n -- length of that buffer
2618 Returns: pointer to static buffer
2619 ----*/
2620 char *
2621 checkpoint_dir_for_pico(char *s, size_t n)
2623 #if defined(DOS) || defined(OS2)
2625 * we can't assume anything about root or home dirs, so
2626 * just plunk it down in the same place as the pinerc
2628 if(!getenv("HOME")){
2629 char *lc = last_cmpnt(ps_global->pinerc);
2631 if(lc != NULL){
2632 strncpy(s, ps_global->pinerc, MIN(n-1,lc-ps_global->pinerc));
2633 s[MIN(n-1,lc-ps_global->pinerc)] = '\0';
2635 else{
2636 strncpy(s, ".\\", n-1);
2637 s[n-1] = '\0';
2640 else
2641 #endif
2642 strncpy(s, ps_global->home_dir, n-1);
2643 s[n-1] = '\0';
2645 return(s);
2649 /*----------------------------------------------------------------------
2650 Call back for pico to tell us the window size's changed
2652 Args: none
2654 Returns: none (but pine's ttyo structure may have been updated)
2655 ----*/
2656 void
2657 resize_for_pico(void)
2659 fix_windsize(ps_global);
2663 PCOLORS *
2664 colors_for_pico(void)
2666 PCOLORS *colors = NULL;
2667 struct variable *vars = ps_global->vars;
2669 if (pico_usingcolor()){
2670 colors = (PCOLORS *)fs_get(sizeof(PCOLORS));
2672 colors->tbcp = current_titlebar_color();
2674 if (VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
2675 colors->klcp = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
2676 VAR_KEYLABEL_BACK_COLOR);
2677 if (!pico_is_good_colorpair(colors->klcp))
2678 free_color_pair(&colors->klcp);
2680 else colors->klcp = NULL;
2682 if (colors->klcp && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
2683 colors->kncp = new_color_pair(VAR_KEYNAME_FORE_COLOR,
2684 VAR_KEYNAME_BACK_COLOR);
2686 else colors->kncp = NULL;
2688 if (VAR_STATUS_FORE_COLOR && VAR_STATUS_BACK_COLOR){
2689 colors->stcp = new_color_pair(VAR_STATUS_FORE_COLOR,
2690 VAR_STATUS_BACK_COLOR);
2692 else colors->stcp = NULL;
2694 if (VAR_PROMPT_FORE_COLOR && VAR_PROMPT_BACK_COLOR){
2695 colors->prcp = new_color_pair(VAR_PROMPT_FORE_COLOR,
2696 VAR_PROMPT_BACK_COLOR);
2698 else colors->prcp = NULL;
2700 if (VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR){
2701 colors->qlcp = new_color_pair(VAR_QUOTE1_FORE_COLOR,
2702 VAR_QUOTE1_BACK_COLOR);
2704 else colors->qlcp = NULL;
2706 if (VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR){
2707 colors->qllcp = new_color_pair(VAR_QUOTE2_FORE_COLOR,
2708 VAR_QUOTE2_BACK_COLOR);
2710 else colors->qllcp = NULL;
2712 if (VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR){
2713 colors->qlllcp = new_color_pair(VAR_QUOTE3_FORE_COLOR,
2714 VAR_QUOTE3_BACK_COLOR);
2716 else colors->qlllcp = NULL;
2718 if (VAR_NORM_FORE_COLOR && VAR_NORM_BACK_COLOR){
2719 colors->ntcp = new_color_pair(VAR_NORM_FORE_COLOR,
2720 VAR_NORM_BACK_COLOR);
2722 else colors->ntcp = NULL;
2724 if (VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR){
2725 colors->rtcp = new_color_pair(VAR_REV_FORE_COLOR,
2726 VAR_REV_BACK_COLOR);
2728 else colors->rtcp = NULL;
2730 if (VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR){
2731 colors->sbcp = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
2732 VAR_SIGNATURE_BACK_COLOR);
2734 else colors->sbcp = NULL;
2737 return colors;
2741 void
2742 free_pcolors(PCOLORS **colors)
2744 if (*colors){
2745 if ((*colors)->tbcp)
2746 free_color_pair(&(*colors)->tbcp);
2747 if ((*colors)->kncp)
2748 free_color_pair(&(*colors)->kncp);
2749 if ((*colors)->klcp)
2750 free_color_pair(&(*colors)->klcp);
2751 if ((*colors)->stcp)
2752 free_color_pair(&(*colors)->stcp);
2753 if ((*colors)->prcp)
2754 free_color_pair(&(*colors)->prcp);
2755 if ((*colors)->qlcp)
2756 free_color_pair(&(*colors)->qlcp);
2757 if ((*colors)->qllcp)
2758 free_color_pair(&(*colors)->qllcp);
2759 if ((*colors)->qlllcp)
2760 free_color_pair(&(*colors)->qlllcp);
2761 if ((*colors)->ntcp)
2762 free_color_pair(&(*colors)->ntcp);
2763 if ((*colors)->rtcp)
2764 free_color_pair(&(*colors)->rtcp);
2765 if ((*colors)->sbcp)
2766 free_color_pair(&(*colors)->sbcp);
2767 fs_give((void **)colors);
2768 *colors = NULL;