* Implement a different way to delete a password from the cache.
[alpine.git] / alpine / reply.c
blobbb3a72cb1c2f8f7c776fd121087ffdce2fb811fe
1 /*
2 * ========================================================================
3 * Copyright 2006-2008 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
16 Code here for forward and reply to mail
17 A few support routines as well
19 This code will forward and reply to MIME messages. The Alpine composer
20 at this time will only support non-text segments at the end of a
21 message so, things don't always come out as one would like. If you
22 always forward a message in MIME format, all will be correct. Forwarding
23 of nested MULTIPART messages will work. There's still a problem with
24 MULTIPART/ALTERNATIVE as the "first text part" rule doesn't allow modifying
25 the equivalent parts. Ideally, we should probably such segments as a
26 single attachment when forwarding/replying. It would also be real nice to
27 flatten out the nesting in the composer so pieces inside can get snipped.
29 The evolution continues...
30 =====*/
33 #include "headers.h"
34 #include "reply.h"
35 #include "status.h"
36 #include "radio.h"
37 #include "send.h"
38 #include "titlebar.h"
39 #include "mailindx.h"
40 #include "help.h"
41 #include "signal.h"
42 #include "mailcmd.h"
43 #include "alpine.h"
44 #include "roleconf.h"
45 #include "../pith/state.h"
46 #include "../pith/conf.h"
47 #include "../pith/init.h"
48 #include "../pith/filter.h"
49 #include "../pith/pattern.h"
50 #include "../pith/charset.h"
51 #include "../pith/mimedesc.h"
52 #include "../pith/remote.h"
53 #include "../pith/news.h"
54 #include "../pith/util.h"
55 #include "../pith/detoken.h"
56 #include "../pith/newmail.h"
57 #include "../pith/readfile.h"
58 #include "../pith/tempfile.h"
59 #include "../pith/busy.h"
60 #include "../pith/ablookup.h"
64 * Internal Prototypes
66 int reply_poster_followup(ENVELOPE *);
67 int sigedit_exit_for_pico(struct headerentry *, void (*)(void), int, char **);
68 long new_mail_for_pico(int, int);
69 void cmd_input_for_pico(void);
70 int display_message_for_pico(UCS);
71 char *checkpoint_dir_for_pico(char *, size_t);
72 void resize_for_pico(void);
73 PCOLORS *colors_for_pico(void);
74 void free_pcolors(PCOLORS **);
77 /*----------------------------------------------------------------------
78 Fill in an outgoing message for reply and pass off to send
80 Args: pine_state -- The usual pine structure
82 Result: Reply is formatted and passed off to composer/mailer
84 Reply
86 - put senders address in To field
87 - search to and cc fields to see if we aren't the only recipients
88 - if other than us, ask if we should reply to all.
89 - if answer yes, fill out the To and Cc fields
90 - fill in the fcc argument
91 - fill in the subject field
92 - fill out the body and the attachments
93 - pass off to pine_send()
94 ---*/
95 int
96 reply(struct pine *pine_state, ACTION_S *role_arg)
98 ADDRESS *saved_from, *saved_to, *saved_cc, *saved_resent;
99 ADDRESS *us_in_to_and_cc, *ap;
100 ENVELOPE *env = NULL, *outgoing;
101 BODY *body, *orig_body = NULL;
102 REPLY_S reply;
103 void *msgtext = NULL;
104 char *tmpfix = NULL, *prefix = NULL, *fcc = NULL, *errmsg = NULL;
105 long msgno, j, totalm, rflags, *seq = NULL;
106 int i, include_text = 0, times = -1, warned = 0, rv = 0,
107 flags = RSF_QUERY_REPLY_ALL, reply_raw_body = 0;
108 int rolemsg = 0, copytomsg = 0;
109 gf_o_t pc;
110 PAT_STATE dummy;
111 REDRAFT_POS_S *redraft_pos = NULL;
112 ACTION_S *role = NULL, *nrole;
113 #if defined(DOS) && !defined(_WINDOWS)
114 char *reserve;
115 #endif
117 outgoing = mail_newenvelope();
118 totalm = mn_total_cur(pine_state->msgmap);
119 seq = (long *)fs_get(((size_t)totalm + 1) * sizeof(long));
121 dprint((4,"\n - reply (%s msgs) -\n", comatose(totalm)));
123 saved_from = (ADDRESS *) NULL;
124 saved_to = (ADDRESS *) NULL;
125 saved_cc = (ADDRESS *) NULL;
126 saved_resent = (ADDRESS *) NULL;
128 us_in_to_and_cc = (ADDRESS *) NULL;
130 outgoing->subject = NULL;
132 memset((void *)&reply, 0, sizeof(reply));
134 if(ps_global->full_header == 2
135 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
136 reply_raw_body = 1;
139 * We may have to loop through first to figure out what default
140 * reply-indent-string to offer...
142 if(mn_total_cur(pine_state->msgmap) > 1 &&
143 (F_ON(F_ALT_REPLY_MENU, pine_state)
144 || F_ON(F_ENABLE_EDIT_REPLY_INDENT, pine_state)) &&
145 reply_quote_str_contains_tokens()){
146 for(msgno = mn_first_cur(pine_state->msgmap);
147 msgno > 0L && !tmpfix;
148 msgno = mn_next_cur(pine_state->msgmap)){
150 env = pine_mail_fetchstructure(pine_state->mail_stream,
151 mn_m2raw(pine_state->msgmap, msgno),
152 NULL);
153 if(!env) {
154 q_status_message1(SM_ORDER,3,4,
155 _("Error fetching message %s. Can't reply to it."),
156 long2string(msgno));
157 goto done_early;
160 if(!tmpfix){ /* look for prefix? */
161 tmpfix = reply_quote_str(env);
162 if(prefix){
163 i = strcmp(tmpfix, prefix);
164 fs_give((void **) &tmpfix);
165 if(i){ /* don't check back if dissimilar */
166 fs_give((void **) &prefix);
168 * We free prefix, not tmpfix. We set tmpfix to prefix
169 * so that we won't come check again.
171 tmpfix = prefix = cpystr("> ");
174 else{
175 prefix = tmpfix;
176 tmpfix = NULL; /* check back later? */
181 tmpfix = prefix;
185 * Loop thru the selected messages building the
186 * outgoing envelope's destinations...
188 for(msgno = mn_first_cur(pine_state->msgmap);
189 msgno > 0L;
190 msgno = mn_next_cur(pine_state->msgmap)){
192 /*--- Grab current envelope ---*/
193 env = pine_mail_fetchstructure(pine_state->mail_stream,
194 seq[++times] = mn_m2raw(pine_state->msgmap, msgno),
195 NULL);
196 if(!env) {
197 q_status_message1(SM_ORDER,3,4,
198 _("Error fetching message %s. Can't reply to it."),
199 long2string(msgno));
200 goto done_early;
204 * We check for the prefix here if we didn't do it in the first
205 * loop above. This is just to save having to go through the loop
206 * twice in the cases where we don't need to.
208 if(!tmpfix){
209 tmpfix = reply_quote_str(env);
210 if(prefix){
211 i = strcmp(tmpfix, prefix);
212 fs_give((void **) &tmpfix);
213 if(i){ /* don't check back if dissimilar */
214 fs_give((void **) &prefix);
215 tmpfix = prefix = cpystr("> ");
218 else{
219 prefix = tmpfix;
220 tmpfix = NULL; /* check back later? */
225 * For consistency, the first question is always "include text?"
227 if(!times){ /* only first time */
228 char *p = cpystr(prefix);
230 if((include_text=reply_text_query(pine_state,totalm,env,&prefix)) < 0)
231 goto done_early;
233 /* edited prefix? */
234 if(strcmp(p, prefix))
235 tmpfix = prefix; /* stop looking */
237 fs_give((void **)&p);
241 * If we're agg-replying or there's a newsgroup and the user wants
242 * to post to news *and* via email, add relevant addresses to the
243 * outgoing envelope...
245 * The single message case gets us around the aggregate reply
246 * to messages in a mixed mail-news archive where some might
247 * have newsgroups and others not or whatever.
249 if(totalm > 1L || ((i = reply_news_test(env, outgoing)) & 1)){
250 if(totalm > 1)
251 flags |= RSF_FORCE_REPLY_TO;
253 if(!reply_harvest(pine_state, seq[times], NULL, env,
254 &saved_from, &saved_to, &saved_cc,
255 &saved_resent, &flags))
256 goto done_early;
258 else if(i == 0)
259 goto done_early;
261 /* collect a list of addresses that are us in to and cc fields */
262 if(env->to)
263 if((ap=reply_cp_addr(pine_state, 0L, NULL,
264 NULL, us_in_to_and_cc, NULL,
265 env->to, RCA_ONLY_US)) != NULL)
266 reply_append_addr(&us_in_to_and_cc, ap);
268 if(env->cc)
269 if((ap=reply_cp_addr(pine_state, 0L, NULL,
270 NULL, us_in_to_and_cc, NULL,
271 env->cc, RCA_ONLY_US)) != NULL)
272 reply_append_addr(&us_in_to_and_cc, ap);
274 /*------------ Format the subject line ---------------*/
275 if(outgoing->subject){
277 * if reply to more than one message, and all subjects
278 * match, so be it. otherwise set it to something generic...
280 if(!same_subject(outgoing->subject,
281 reply_subject(env->subject,tmp_20k_buf,SIZEOF_20KBUF))){
282 fs_give((void **)&outgoing->subject);
283 outgoing->subject = cpystr("Re: several messages");
286 else
287 outgoing->subject = reply_subject(env->subject, NULL, 0);
290 /* fill reply header */
291 reply_seed(pine_state, outgoing, env, saved_from,
292 saved_to, saved_cc, saved_resent,
293 &fcc, flags & RSF_FORCE_REPLY_ALL, &errmsg);
294 if(errmsg){
295 if(*errmsg){
296 q_status_message1(SM_ORDER, 3, 3, "%.200s", errmsg);
297 display_message(NO_OP_COMMAND);
300 fs_give((void **)&errmsg);
303 if(sp_expunge_count(pine_state->mail_stream)) /* cur msg expunged */
304 goto done_early;
306 /* Setup possible role */
307 if (ps_global->reply.role_chosen)
308 role = ps_global->reply.role_chosen;
309 else if(role_arg)
310 role = copy_action(role_arg);
312 if(!role){
313 rflags = ROLE_REPLY;
314 if(!ps_global->reply.role_chosen && nonempty_patterns(rflags, &dummy)){
315 /* setup default role */
316 nrole = NULL;
317 j = mn_first_cur(pine_state->msgmap);
318 do {
319 role = nrole;
320 nrole = set_role_from_msg(pine_state, rflags,
321 mn_m2raw(pine_state->msgmap, j),
322 NULL);
323 } while(nrole && (!role || nrole == role)
324 && (j=mn_next_cur(pine_state->msgmap)) > 0L);
326 if(!role || nrole == role)
327 role = nrole;
328 else
329 role = NULL;
331 if(confirm_role(rflags, &role))
332 role = combine_inherited_role(role);
333 else{ /* cancel reply */
334 role = NULL;
335 cmd_cancelled("Reply");
336 goto done_early;
342 * Reply_seed may call c-client in get_fcc_based_on_to, so env may
343 * no longer be valid. Get it again.
344 * Similarly for set_role_from_message.
346 env = pine_mail_fetchstructure(pine_state->mail_stream, seq[times], NULL);
348 if(role){
349 rolemsg++;
350 /* override fcc gotten in reply_seed */
351 if(role->fcc && fcc)
352 fs_give((void **) &fcc);
355 if(F_ON(F_COPY_TO_TO_FROM, pine_state) && !(role && role->from)){
357 * A list of all of our addresses that appear in the To
358 * and cc fields is in us_in_to_and_cc.
359 * If there is exactly one address in that list then
360 * use it for the outgoing From.
362 if(us_in_to_and_cc && !us_in_to_and_cc->next){
363 PINEFIELD *custom, *pf;
364 ADDRESS *a = NULL;
365 char *addr = NULL;
368 * Check to see if this address is different from what
369 * we would have used anyway. If it is, notify the user
370 * with a status message. This is pretty hokey because we're
371 * mimicking how pine_send would set the From address and
372 * there is no coordination between the two.
375 /* in case user has a custom From value */
376 custom = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef);
378 pf = (PINEFIELD *) fs_get(sizeof(*pf));
379 memset((void *) pf, 0, sizeof(*pf));
380 pf->name = cpystr("From");
381 pf->addr = &a;
382 if(set_default_hdrval(pf, custom) >= UseAsDef
383 && pf->textbuf && pf->textbuf[0]){
384 removing_trailing_white_space(pf->textbuf);
385 (void)removing_double_quotes(pf->textbuf);
386 build_address(pf->textbuf, &addr, NULL, NULL, NULL);
387 rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain);
388 if(addr)
389 fs_give((void **) &addr);
392 if(!*pf->addr)
393 *pf->addr = generate_from();
395 if(*pf->addr && !address_is_same(*pf->addr, us_in_to_and_cc)){
396 copytomsg++;
397 if(!role){
398 role = (ACTION_S *) fs_get(sizeof(*role));
399 memset((void *) role, 0, sizeof(*role));
400 role->is_a_role = 1;
403 role->from = us_in_to_and_cc;
404 us_in_to_and_cc = NULL;
407 free_customs(custom);
408 free_customs(pf);
412 if(role){
413 if(rolemsg && copytomsg)
414 q_status_message1(SM_ORDER, 3, 4,
415 _("Replying using role \"%s\" and To as From"), role->nick);
416 else if(rolemsg)
417 q_status_message1(SM_ORDER, 3, 4,
418 _("Replying using role \"%s\""), role->nick);
419 else if(copytomsg)
420 q_status_message(SM_ORDER, 3, 4,
421 _("Replying using incoming To as outgoing From"));
424 if(us_in_to_and_cc)
425 mail_free_address(&us_in_to_and_cc);
428 seq[++times] = -1L; /* mark end of sequence list */
430 /*========== Other miscellaneous fields ===================*/
431 outgoing->in_reply_to = reply_in_reply_to(env);
432 outgoing->references = reply_build_refs(env);
433 outgoing->message_id = generate_message_id(role);
435 if(!outgoing->to &&
436 !outgoing->cc &&
437 !outgoing->bcc &&
438 !outgoing->newsgroups)
439 q_status_message(SM_ORDER | SM_DING, 3, 6,
440 _("Warning: no valid addresses to reply to!"));
443 /*==================== Now fix up the message body ====================*/
446 * create storage object to be used for message text
448 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
449 q_status_message(SM_ORDER | SM_DING, 3, 4,
450 _("Error allocating message text"));
451 goto done_early;
454 gf_set_so_writec(&pc, (STORE_S *) msgtext);
456 /*---- Include the original text if requested ----*/
457 if(include_text && totalm > 1L){
458 char *sig;
459 int impl, template_len = 0, leave_cursor_at_top = 0;
462 env = NULL;
463 if(role && role->template){
464 char *filtered;
466 impl = 0;
467 filtered = detoken(role, env, 0,
468 ps_global->reply.signature_bottom,
469 0, &redraft_pos, &impl);
470 if(filtered){
471 if(*filtered){
472 so_puts((STORE_S *)msgtext, filtered);
473 if(impl == 1)
474 template_len = strlen(filtered);
475 else if(impl == 2)
476 leave_cursor_at_top++;
479 fs_give((void **)&filtered);
481 else
482 impl = 1;
484 else
485 impl = 1;
487 if((sig = reply_signature(role, env, &redraft_pos, &impl)) &&
488 !ps_global->reply.signature_bottom){
491 * If CURSORPOS was set explicitly in sig_file, and there was a
492 * template file before that, we need to adjust the offset by the
493 * length of the template file. However, if the template had
494 * a set CURSORPOS in it then impl was 2 before getting to the
495 * signature, so offset wouldn't have been reset by the signature
496 * CURSORPOS and offset would already be correct. That case will
497 * be ok here because template_len will be 0 and adding it does
498 * nothing. If template
499 * didn't have CURSORPOS in it, then impl was 1 and got set to 2
500 * by the CURSORPOS in the sig. In that case we have to adjust the
501 * offset. That's what the next line does. It adjusts it if
502 * template_len is nonzero and if CURSORPOS was set in sig_file.
504 if(impl == 2)
505 redraft_pos->offset += template_len;
507 if(*sig)
508 so_puts((STORE_S *)msgtext, sig);
511 * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM
512 * is set, we won't have used it yet and want it to be non-NULL.
514 fs_give((void **)&sig);
518 * Only put cursor in sig if there is a cursorpos there but not
519 * one in the template, and sig-at-bottom.
521 if(!(sig && impl == 2 && !leave_cursor_at_top))
522 leave_cursor_at_top++;
524 body = mail_newbody();
525 body->type = TYPETEXT;
526 body->contents.text.data = msgtext;
528 for(msgno = mn_first_cur(pine_state->msgmap);
529 msgno > 0L;
530 msgno = mn_next_cur(pine_state->msgmap)){
532 if(env){ /* put 2 between messages */
533 gf_puts(NEWLINE, pc);
534 gf_puts(NEWLINE, pc);
537 /*--- Grab current envelope ---*/
538 env = pine_mail_fetchstructure(pine_state->mail_stream,
539 mn_m2raw(pine_state->msgmap, msgno),
540 &orig_body);
541 if(!env){
542 q_status_message1(SM_ORDER,3,4,
543 _("Error fetching message %s. Can't reply to it."),
544 long2string(mn_get_cur(pine_state->msgmap)));
545 goto done_early;
548 if(orig_body == NULL || orig_body->type == TYPETEXT || reply_raw_body) {
549 reply_delimiter(env, role, pc);
550 if(ps_global->reply.include_header)
551 reply_forward_header(pine_state->mail_stream,
552 mn_m2raw(pine_state->msgmap,msgno),
553 NULL, env, pc, prefix);
555 get_body_part_text(pine_state->mail_stream, reply_raw_body ? NULL : orig_body,
556 mn_m2raw(pine_state->msgmap, msgno),
557 reply_raw_body ? NULL : "1", 0L, pc, prefix,
558 NULL, GBPT_NONE);
560 else if(orig_body->type == TYPEMULTIPART) {
561 if(!warned++)
562 q_status_message(SM_ORDER,3,7,
563 _("WARNING! Attachments not included in multiple reply."));
565 if(orig_body->nested.part
566 && orig_body->nested.part->body.type == TYPETEXT) {
567 /*---- First part of the message is text -----*/
568 reply_delimiter(env, role, pc);
569 if(ps_global->reply.include_header)
570 reply_forward_header(pine_state->mail_stream,
571 mn_m2raw(pine_state->msgmap,
572 msgno),
573 NULL, env, pc, prefix);
575 get_body_part_text(pine_state->mail_stream,
576 &orig_body->nested.part->body,
577 mn_m2raw(pine_state->msgmap, msgno),
578 "1", 0L, pc, prefix, NULL, GBPT_NONE);
580 else{
581 q_status_message(SM_ORDER,0,3,
582 _("Multipart with no leading text part."));
585 else{
586 /*---- Single non-text message of some sort ----*/
587 q_status_message(SM_ORDER,3,3,
588 _("Non-text message not included."));
592 if(!leave_cursor_at_top){
593 long cnt = 0L;
594 unsigned char c;
596 /* rewind and count chars to start of sig file */
597 so_seek((STORE_S *)msgtext, 0L, 0);
598 while(so_readc(&c, (STORE_S *)msgtext))
599 cnt++;
601 if(!redraft_pos){
602 redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(*redraft_pos));
603 memset((void *)redraft_pos, 0,sizeof(*redraft_pos));
604 redraft_pos->hdrname = cpystr(":");
608 * If explicit cursor positioning in sig file,
609 * add offset to start of sig file plus offset into sig file.
610 * Else, just offset to start of sig file.
612 redraft_pos->offset += cnt;
615 if(sig){
616 if(*sig)
617 so_puts((STORE_S *)msgtext, sig);
619 fs_give((void **)&sig);
622 else{
623 msgno = mn_m2raw(pine_state->msgmap,
624 mn_get_cur(pine_state->msgmap));
626 /*--- Grab current envelope ---*/
627 env = pine_mail_fetchstructure(pine_state->mail_stream, msgno,
628 &orig_body);
631 * If the charset of the body part is different from ascii and
632 * charset conversion is _not_ happening, then preserve the original
633 * charset from the message so that if we don't enter any new
634 * chars with the hibit set we can use the original charset.
635 * If not all those things, then don't try to preserve it.
637 if(orig_body){
638 char *charset;
640 charset = parameter_val(orig_body->parameter, "charset");
641 if(charset && strucmp(charset, "us-ascii") != 0){
642 CONV_TABLE *ct;
645 * There is a non-ascii charset, is there conversion happening?
647 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
648 reply.orig_charset = charset;
649 charset = NULL;
653 if(charset)
654 fs_give((void **) &charset);
657 if(env) {
658 if(!(body = reply_body(pine_state->mail_stream, env, orig_body,
659 msgno, NULL, msgtext, prefix,
660 include_text, role, 1, &redraft_pos)))
661 goto done_early;
663 else{
664 q_status_message1(SM_ORDER,3,4,
665 _("Error fetching message %s. Can't reply to it."),
666 long2string(mn_get_cur(pine_state->msgmap)));
667 goto done_early;
671 /* fill in reply structure */
672 reply.prefix = prefix;
673 reply.mailbox = cpystr(pine_state->mail_stream->mailbox);
674 reply.origmbox = cpystr(pine_state->mail_stream->original_mailbox
675 ? pine_state->mail_stream->original_mailbox
676 : pine_state->mail_stream->mailbox);
677 reply.data.uid.msgs = (imapuid_t *) fs_get((times + 1) * sizeof(imapuid_t));
678 if((reply.data.uid.validity = pine_state->mail_stream->uid_validity) != 0){
679 reply.uid = 1;
680 for(i = 0; i < times ; i++)
681 reply.data.uid.msgs[i] = mail_uid(pine_state->mail_stream, seq[i]);
683 else{
684 reply.msgno = 1;
685 for(i = 0; i < times ; i++)
686 reply.data.uid.msgs[i] = seq[i];
689 reply.data.uid.msgs[i] = 0; /* tie off list */
691 #if defined(DOS) && !defined(_WINDOWS)
692 free((void *)reserve);
693 #endif
695 /* partially formatted outgoing message */
696 pine_send(outgoing, &body, _("COMPOSE MESSAGE REPLY"),
697 role, fcc, &reply, redraft_pos, NULL, NULL, 0);
699 rv++;
700 pine_free_body(&body);
701 if(reply.mailbox)
702 fs_give((void **) &reply.mailbox);
703 if(reply.origmbox)
704 fs_give((void **) &reply.origmbox);
705 if(reply.orig_charset)
706 fs_give((void **) &reply.orig_charset);
707 fs_give((void **) &reply.data.uid.msgs);
708 done_early:
709 if((STORE_S *) msgtext)
710 gf_clear_so_writec((STORE_S *) msgtext);
712 mail_free_envelope(&outgoing);
713 mail_free_address(&saved_from);
714 mail_free_address(&saved_to);
715 mail_free_address(&saved_cc);
716 mail_free_address(&saved_resent);
718 fs_give((void **)&seq);
720 if(prefix)
721 fs_give((void **)&prefix);
723 if(fcc)
724 fs_give((void **) &fcc);
726 free_redraft_pos(&redraft_pos);
727 free_action(&role);
728 return rv;
733 * Ask user to confirm role choice, or choose another role.
735 * Args role -- A pointer into the pattern_h space at the default
736 * role to use. This can't be a copy, the comparison
737 * relies on it pointing at the actual role.
738 * This arg is also used to return a pointer to the
739 * chosen role.
741 * Returns 1 -- Yes, use role which is now in *role. This may not be
742 * the same as the role passed in and it may even be NULL.
743 * 0 -- Cancel reply.
746 confirm_role(long int rflags, ACTION_S **role)
748 ACTION_S *role_p = NULL;
749 ACTION_S *default_role = NULL;
750 char prompt[80], *prompt_fodder;
751 int cmd, done, ret = 1;
752 void (*prev_screen)(struct pine *) = ps_global->prev_screen,
753 (*redraw)(void) = ps_global->redrawer;
754 PAT_S *curpat, *pat;
755 PAT_STATE pstate;
756 HelpType help;
757 ESCKEY_S ekey[4];
759 if(!nonempty_patterns(ROLE_DO_ROLES, &pstate) || !role)
760 return(ret);
763 * If this is a reply or forward and the role doesn't require confirmation,
764 * then we just return with what was passed in.
766 if(((rflags & ROLE_REPLY) &&
767 *role && (*role)->repl_type == ROLE_REPL_NOCONF) ||
768 ((rflags & ROLE_FORWARD) &&
769 *role && (*role)->forw_type == ROLE_FORW_NOCONF) ||
770 ((rflags & ROLE_COMPOSE) &&
771 *role && (*role)->comp_type == ROLE_COMP_NOCONF) ||
772 (!*role && F_OFF(F_ROLE_CONFIRM_DEFAULT, ps_global)
773 && !ps_global->default_role))
774 return(ret);
777 * Check that there is at least one role available. This is among all
778 * roles, not just the reply roles or just the forward roles. That's
779 * because we have ^T take us to all the roles, not the category-specific
780 * roles.
782 if(!(pat = last_pattern(&pstate)))
783 return(ret);
785 ekey[0].ch = 'y';
786 ekey[0].rval = 'y';
788 ekey[1].ch = 'n';
789 ekey[1].rval = 'n';
791 ekey[2].ch = ctrl('T');
792 ekey[2].rval = 2;
793 ekey[2].name = "^T";
795 ekey[3].ch = -1;
797 /* check for more than one role available (or no role set) */
798 if(pat == first_pattern(&pstate) && *role) /* no ^T */
799 ekey[2].ch = -1;
802 * Setup default role
803 * Go through the loop just in case default_role doesn't point
804 * to a real current role.
806 if(ps_global->default_role){
807 for(pat = first_pattern(&pstate);
808 pat;
809 pat = next_pattern(&pstate)){
810 if(pat->action == ps_global->default_role){
811 default_role = ps_global->default_role;
812 break;
817 curpat = NULL;
819 /* override default */
820 if(*role){
821 for(pat = first_pattern(&pstate);
822 pat;
823 pat = next_pattern(&pstate)){
824 if(pat->action == *role){
825 curpat = pat;
826 break;
831 if(rflags & ROLE_REPLY)
832 prompt_fodder = _("Reply");
833 else if(rflags & ROLE_FORWARD)
834 prompt_fodder = _("Forward");
835 else
836 prompt_fodder = _("Compose");
838 done = 0;
839 while(!done){
840 if(curpat){
841 char buf[100];
843 help = h_role_confirm;
844 ekey[0].name = "Y";
845 ekey[0].label = N_("Yes");
846 ekey[1].name = "N";
847 if(default_role)
848 ekey[1].label = N_("No, use default role");
849 else
850 ekey[1].label = N_("No, use default settings");
852 ekey[2].label = N_("To Select Alternate Role");
854 if(curpat->patgrp && curpat->patgrp->nick)
855 /* TRANSLATORS: This is something like Use role <nickname of role> for Reply? */
856 snprintf(prompt, sizeof(prompt), _("Use role \"%s\" for %s? "),
857 short_str(curpat->patgrp->nick, buf, sizeof(buf), 50, MidDots),
858 prompt_fodder);
859 else
860 snprintf(prompt, sizeof(prompt),
861 _("Use role \"<a role without a nickname>\" for %s? "),
862 prompt_fodder);
864 else{
865 help = h_norole_confirm;
866 ekey[0].name = "Ret";
867 ekey[0].label = prompt_fodder;
868 ekey[1].name = "";
869 ekey[1].label = "";
870 ekey[2].label = N_("To Select Role");
871 snprintf(prompt, sizeof(prompt),
872 _("Press Return to %s using %s role, or ^T to select a role "),
873 prompt_fodder, default_role ? _("default") : _("no"));
876 prompt[sizeof(prompt)-1] = '\0';
878 cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
879 'y', 'x', help, RB_NORM);
881 switch(cmd){
882 case 'y': /* Accept */
883 done++;
884 *role = curpat ? curpat->action : default_role;
885 break;
887 case 'x': /* Cancel */
888 ret = 0;
889 /* fall through */
891 case 'n': /* NoRole */
892 done++;
893 *role = default_role;
894 break;
896 case 2: /* ^T */
897 if(role_select_screen(ps_global, &role_p, 0) >= 0){
898 if(role_p){
899 for(pat = first_pattern(&pstate);
900 pat;
901 pat = next_pattern(&pstate)){
902 if(pat->action == role_p){
903 curpat = pat;
904 break;
908 else
909 curpat = NULL;
912 ClearBody();
913 ps_global->mangled_body = 1;
914 ps_global->prev_screen = prev_screen;
915 ps_global->redrawer = redraw;
916 break;
920 return(ret);
925 * reply_to_all_query - Ask user about replying to all recipients
927 * Returns: -1 if cancel, 0 otherwise
928 * by reference: flagp
931 reply_to_all_query(int *flagp)
933 char prompt[80];
934 ESCKEY_S ekey[4];
935 char cmd;
937 ekey[0].name = "Y";
938 ekey[0].ch = 'y';
939 ekey[0].rval = 'y';
940 ekey[0].label = N_("Yes");
941 ekey[1].name = "N";
942 ekey[1].ch = 'n';
943 ekey[1].rval = 'n';
944 ekey[1].label = N_("No");
945 ekey[2].name = "P";
946 ekey[2].ch = 'p';
947 ekey[2].rval = 'p';
948 ekey[3].ch = -1;
950 ps_global->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps_global);
951 loop:
952 ekey[2].label = ps_global->reply.preserve_fields ? N_("Not Preserve") : N_("Preserve");
953 snprintf(prompt, sizeof(prompt), _("Reply to all recipients%s"),
954 ps_global->reply.preserve_fields ? _(" (preserving fields)? ") : "? ");
956 prompt[sizeof(prompt)-1] = '\0';
958 switch(cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), ekey,
959 'n', 'x', h_preserve_field, RB_NORM)){
961 case 'x' :
962 return(-1);
964 case 'y' : /* set reply-all bit */
965 (*flagp) |= RSF_FORCE_REPLY_ALL;
966 break;
968 case 'n' : /* clear reply-all bit */
969 (*flagp) &= ~RSF_FORCE_REPLY_ALL;
970 break;
972 case 'p' :
973 ps_global->reply.preserve_fields =
974 (ps_global->reply.preserve_fields + 1) % 2;
975 goto loop; /* ugly, but saves me a variable */
976 break;
979 return(0);
984 * reply_using_replyto_query - Ask user about replying with reply-to value
986 * Returns: 'y' if yes
987 * 'x' if cancel
990 reply_using_replyto_query(void)
992 return(want_to("Use \"Reply-To:\" address instead of \"From:\" address",
993 'y', 'x', NO_HELP,WT_SEQ_SENSITIVE));
998 * reply_text_query - Ask user about replying with text, or in the case
999 * of alternate reply menu, set values to the answer to all questions
1000 * asked during reply.
1002 * Returns: 1 if include the text
1003 * 0 if we're NOT to include the text
1004 * -1 on cancel or error
1006 #define MAX_REPLY_OPTIONS 10
1008 reply_text_query(struct pine *ps, long int many, ENVELOPE *env, char **prefix)
1010 int ret, edited = 0, headers = 0;
1011 static ESCKEY_S compose_style[MAX_REPLY_OPTIONS];
1012 int ekey_num;
1014 ps->reply.use_flowed = *prefix && **prefix ? (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1015 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1016 && (strcmp(*prefix, "> ") == 0
1017 || strcmp(*prefix, ">") == 0)) : 0;
1018 ps->reply.strip_signature = ps->full_header == 0
1019 && (F_ON(F_ENABLE_STRIP_SIGDASHES, ps)
1020 || F_ON(F_ENABLE_SIGDASHES, ps));
1021 ps->reply.keep_attach = F_ON(F_ATTACHMENTS_IN_REPLY, ps);
1022 ps->reply.include_header = F_ON(F_INCLUDE_HEADER, ps);
1023 ps->reply.preserve_fields = F_ON(F_PRESERVE_ORIGINAL_FIELD, ps);
1024 ps->reply.signature_bottom = F_ON(F_SIG_AT_BOTTOM, ps);
1025 ps->reply.role_chosen = NULL;
1027 if(F_OFF(F_ALT_REPLY_MENU, ps)
1028 && F_ON(F_AUTO_INCLUDE_IN_REPLY, ps)
1029 && F_OFF(F_ENABLE_EDIT_REPLY_INDENT, ps)
1030 && F_OFF(F_ALT_REPLY_MENU,ps))
1031 return(1);
1033 while(1){
1034 compose_style[ekey_num = 0].ch = 'y';
1035 compose_style[ekey_num].rval = 'y';
1036 compose_style[ekey_num].name = "Y";
1037 compose_style[ekey_num++].label = N_("Yes");
1039 compose_style[ekey_num].ch = 'n';
1040 compose_style[ekey_num].rval = 'n';
1041 compose_style[ekey_num].name = "N";
1042 compose_style[ekey_num++].label = N_("No");
1044 if (F_OFF(F_ALT_REPLY_MENU, ps)){ /**** Standard menu ****/
1045 /* TRANSLATORS: The final five %s's can probably be safely ignored */
1046 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply%s%s%s%s%s%s? "),
1047 (many > 1L) ? comatose(many) : "",
1048 (many > 1L) ? " " : "",
1049 (many > 1L) ? "s" : "",
1050 (many > 1L) ? "s" : "",
1051 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? " (using \"" : "",
1052 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? *prefix : "",
1053 ps->reply.role_chosen ? "\" and role \"" : "",
1054 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "",
1055 F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps) ? "\")" : "");
1056 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1058 if (F_ON(F_ENABLE_EDIT_REPLY_INDENT, ps)){
1059 compose_style[ekey_num].ch = ctrl('R');
1060 compose_style[ekey_num].rval = 'r';
1061 compose_style[ekey_num].name = "^R";
1062 compose_style[ekey_num++].label = N_("Edit Indent String");
1064 } else { /***** Alternate Reply Menu ********/
1065 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Include %s%soriginal message%s in Reply (using \"%s%s%s\")? "),
1066 (many > 1L) ? comatose(many) : "",
1067 (many > 1L) ? " " : "",
1068 (many > 1L) ? "s" : "",
1069 *prefix,
1070 ps->reply.role_chosen ? "\" and role \"" : "",
1071 ps->reply.role_chosen ? ps->reply.role_chosen->nick : "");
1072 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1074 compose_style[ekey_num].ch = 'h';
1075 compose_style[ekey_num].rval = 'H';
1076 compose_style[ekey_num].name = "H";
1077 compose_style[ekey_num++].label = ps->reply.include_header
1078 ? N_("No Header") : N_("Inc Headr");
1080 compose_style[ekey_num].ch = 's';
1081 compose_style[ekey_num].rval = 'S';
1082 compose_style[ekey_num].name = "S";
1083 compose_style[ekey_num++].label = ps->reply.strip_signature
1084 ? N_("No Strip"): N_("Strip Sig");
1086 compose_style[ekey_num].ch = 'a';
1087 compose_style[ekey_num].rval = 'A';
1088 compose_style[ekey_num].name = "A";
1089 compose_style[ekey_num++].label = ps->reply.keep_attach
1090 ? N_("No Attach"): N_("Inc Attach");
1092 compose_style[ekey_num].ch = 'b';
1093 compose_style[ekey_num].rval = 'B';
1094 compose_style[ekey_num].name = "B";
1095 compose_style[ekey_num++].label = ps->reply.signature_bottom
1096 ? N_("Sig Top") : N_("Sig Bottom");
1098 compose_style[ekey_num].ch = 'r';
1099 compose_style[ekey_num].rval = 'R';
1100 compose_style[ekey_num].name = "R";
1101 compose_style[ekey_num++].label = N_("Set Role");
1103 compose_style[ekey_num].ch = ctrl('R');
1104 compose_style[ekey_num].rval = 'r';
1105 compose_style[ekey_num].name = "^R";
1106 compose_style[ekey_num++].label = N_("Edit Indent String");
1108 /***** End Alt Reply Menu *********/
1111 compose_style[ekey_num].ch = -1;
1112 compose_style[ekey_num].name = NULL;
1113 compose_style[ekey_num].label = NULL;
1115 switch(ret = radio_buttons(tmp_20k_buf,
1116 ps->ttyo->screen_rows > 4
1117 ? -FOOTER_ROWS(ps) : -1,
1118 compose_style,
1119 (edited || headers || F_ON(F_AUTO_INCLUDE_IN_REPLY, ps))
1120 ? 'y' : 'n',
1121 'x', NO_HELP, RB_SEQ_SENSITIVE)){
1122 case 'x':
1123 cmd_cancelled("Reply");
1124 return(-1);
1126 case 'A':
1127 ps->reply.keep_attach = (ps->reply.keep_attach + 1) % 2;
1128 break;
1130 case 'B':
1131 ps->reply.signature_bottom = (ps->reply.signature_bottom + 1) % 2;
1132 break;
1134 case 'F':
1135 ps->reply.use_flowed = (ps->reply.use_flowed + 1) % 2;
1136 break;
1138 case 'S':
1139 ps->reply.strip_signature = (ps->reply.strip_signature + 1) % 2;
1140 break;
1142 case 'H':
1143 ps->reply.include_header = (ps->reply.include_header + 1) % 2;
1144 headers = ps->reply.include_header;
1145 break;
1147 case 'R':
1149 void (*prev_screen)(struct pine *) = ps->prev_screen,
1150 (*redraw)(void) = ps->redrawer;
1151 ps->redrawer = NULL;
1152 ps->next_screen = SCREEN_FUN_NULL;
1153 if(role_select_screen(ps, &ps->reply.role_chosen, 1) < 0){
1154 cmd_cancelled("Reply");
1155 ps->next_screen = prev_screen;
1156 ps->redrawer = redraw;
1157 if (ps->redrawer)
1158 (*ps->redrawer)();
1159 continue;
1161 ps->next_screen = prev_screen;
1162 ps->redrawer = redraw;
1163 if(ps->reply.role_chosen)
1164 ps->reply.role_chosen = combine_inherited_role(ps->reply.role_chosen);
1166 if (ps->redrawer)
1167 (*ps->redrawer)();
1168 break;
1170 case 'r':
1171 if(prefix && *prefix){
1172 int done = 0;
1173 char buf[64];
1174 int flags;
1176 while(!done){
1177 strncpy(buf, *prefix, sizeof(buf)-1);
1178 buf[sizeof(buf)-1] = '\0';
1180 flags = OE_APPEND_CURRENT |
1181 OE_KEEP_TRAILING_SPACE |
1182 OE_DISALLOW_HELP |
1183 OE_SEQ_SENSITIVE;
1185 switch(optionally_enter(buf, ps->ttyo->screen_rows > 4
1186 ? -FOOTER_ROWS(ps) : -1,
1187 0, sizeof(buf), "Reply prefix : ",
1188 NULL, NO_HELP, &flags)){
1189 case 0: /* entry successful, continue */
1190 if(flags & OE_USER_MODIFIED){
1191 fs_give((void **)prefix);
1192 *prefix = removing_quotes(cpystr(buf));
1193 ps->reply.use_flowed = *prefix && **prefix ?
1194 (F_OFF(F_QUELL_FLOWED_TEXT, ps)
1195 && F_OFF(F_STRIP_WS_BEFORE_SEND, ps)
1196 && (strcmp(*prefix, "> ") == 0
1197 || strcmp(*prefix, ">") == 0)) : 0;
1198 edited = 1;
1201 done++;
1202 break;
1204 case 1:
1205 cmd_cancelled("Reply");
1207 case -1:
1208 return(-1);
1210 case 4:
1211 EndInverse();
1212 ClearScreen();
1213 redraw_titlebar();
1214 if(ps_global->redrawer != NULL)
1215 (*ps->redrawer)();
1217 redraw_keymenu();
1218 break;
1220 case 3:
1221 break;
1223 case 2:
1224 default:
1225 q_status_message(SM_ORDER, 3, 4,
1226 "Programmer botch in reply_text_query()");
1227 return(-1);
1232 break;
1234 case 'y':
1235 return(1);
1237 case 'n':
1238 return(0);
1240 default:
1241 q_status_message1(SM_ORDER, 3, 4,
1242 "Invalid rval \'%s\'", pretty_command(ret));
1243 return(-1);
1250 * reply_poster_followup - return TRUE if "followup-to" set to "poster"
1252 * NOTE: queues status message indicating such
1255 reply_poster_followup(ENVELOPE *e)
1257 if(e && e->followup_to && !strucmp(e->followup_to, "poster")){
1258 q_status_message(SM_ORDER, 2, 3,
1259 _("Replying to Poster as specified in \"Followup-To\""));
1260 return(1);
1263 return(0);
1268 * reply_news_test - Test given envelope for newsgroup data and copy
1269 * it at the users request
1270 * RETURNS:
1271 * 0 if error or cancel
1272 * 1 reply via email
1273 * 2 follow-up via news
1274 * 3 do both
1277 reply_news_test(ENVELOPE *env, ENVELOPE *outgoing)
1279 int ret = 1;
1280 static ESCKEY_S news_opt[] = { {'f', 'f', "F", N_("Follow-up")},
1281 {'r', 'r', "R", N_("Reply")},
1282 {'b', 'b', "B", N_("Both")},
1283 {-1, 0, NULL, NULL} };
1285 if(env->newsgroups && *env->newsgroups && !reply_poster_followup(env))
1287 * Now that we know a newsgroups field is present,
1288 * ask if the user is posting a follow-up article...
1290 switch(radio_buttons(
1291 _("Follow-up to news group(s), Reply via email to author or Both? "),
1292 -FOOTER_ROWS(ps_global), news_opt, 'r', 'x',
1293 NO_HELP, RB_NORM)){
1294 case 'r' : /* Reply */
1295 ret = 1;
1296 break;
1298 case 'f' : /* Follow-Up via news ONLY! */
1299 ret = 2;
1300 break;
1302 case 'b' : /* BOTH */
1303 ret = 3;
1304 break;
1306 case 'x' : /* cancel or unknown response */
1307 default :
1308 cmd_cancelled("Reply");
1309 ret = 0;
1310 break;
1313 if(ret > 1){
1314 if(env->followup_to){
1315 q_status_message(SM_ORDER, 2, 3,
1316 _("Posting to specified Followup-To groups"));
1317 outgoing->newsgroups = cpystr(env->followup_to);
1319 else if(!outgoing->newsgroups)
1320 outgoing->newsgroups = cpystr(env->newsgroups);
1323 return(ret);
1327 /*----------------------------------------------------------------------
1328 Acquire the pinerc defined signature file
1329 It is allocated here and freed by the caller.
1331 file -- use this file
1332 prenewlines -- prefix the file contents with this many newlines
1333 postnewlines -- postfix the file contents with this many newlines
1334 is_sig -- this is a signature (not a template)
1335 ----*/
1336 char *
1337 get_signature_file(char *file, int prenewlines, int postnewlines, int is_sig)
1339 char *sig, *tmp_sig = NULL, sig_path[MAXPATH+1];
1340 int len, do_the_pipe_thang = 0;
1341 long sigsize = 0L, cntdown;
1343 sig_path[0] = '\0';
1344 if(!signature_path(file, sig_path, MAXPATH))
1345 return(NULL);
1347 dprint((5, "get_signature(%s)\n", sig_path));
1349 if(sig_path[(len=strlen(sig_path))-1] == '|'){
1350 if(is_sig && F_ON(F_DISABLE_PIPES_IN_SIGS, ps_global)){
1351 q_status_message(SM_ORDER | SM_DING, 3, 4,
1352 _("Pipes for signatures are administratively disabled"));
1353 return(NULL);
1355 else if(!is_sig && F_ON(F_DISABLE_PIPES_IN_TEMPLATES, ps_global)){
1356 q_status_message(SM_ORDER | SM_DING, 3, 4,
1357 _("Pipes for templates are administratively disabled"));
1358 return(NULL);
1361 sig_path[len-1] = '\0';
1362 removing_trailing_white_space(sig_path);
1363 do_the_pipe_thang++;
1366 if(!IS_REMOTE(sig_path) && ps_global->VAR_OPER_DIR &&
1367 !in_dir(ps_global->VAR_OPER_DIR, sig_path)){
1368 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1369 /* TRANSLATORS: First arg is the directory name, second is
1370 the file user wants to read but can't. */
1371 _("Can't read file outside %s: %s"),
1372 ps_global->VAR_OPER_DIR, file);
1374 return(NULL);
1377 if(IS_REMOTE(sig_path) || can_access(sig_path, ACCESS_EXISTS) == 0){
1378 if(do_the_pipe_thang){
1379 if(can_access(sig_path, EXECUTE_ACCESS) == 0){
1380 STORE_S *store;
1381 int flags;
1382 PIPE_S *syspipe;
1383 gf_o_t pc;
1384 gf_i_t gc;
1385 long start;
1387 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
1389 flags = PIPE_READ | PIPE_STDERR | PIPE_NOSHELL;
1391 start = time(0);
1393 if((syspipe = open_system_pipe(sig_path, NULL, NULL, flags, 5,
1394 pipe_callback, pipe_report_error)) != NULL){
1395 unsigned char c;
1396 char *error, *q;
1398 gf_set_so_writec(&pc, store);
1399 gf_set_readc(&gc, (void *)syspipe, 0, PipeStar, READ_FROM_LOCALE);
1400 gf_filter_init();
1402 if((error = gf_pipe(gc, pc)) != NULL){
1403 (void)close_system_pipe(&syspipe, NULL, pipe_callback);
1404 gf_clear_so_writec(store);
1405 so_give(&store);
1406 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1407 _("Can't get file: %s"), error);
1408 return(NULL);
1411 if(close_system_pipe(&syspipe, NULL, pipe_callback)){
1412 long now;
1414 now = time(0);
1415 q_status_message2(SM_ORDER, 3, 4,
1416 _("Error running program \"%s\"%s"),
1417 file,
1418 (now - start > 4) ? ": timed out" : "");
1421 gf_clear_so_writec(store);
1423 /* rewind and count chars */
1424 so_seek(store, 0L, 0);
1425 while(so_readc(&c, store) && sigsize < 100000L)
1426 sigsize++;
1428 /* allocate space */
1429 tmp_sig = fs_get((sigsize + 1) * sizeof(char));
1430 tmp_sig[0] = '\0';
1431 q = tmp_sig;
1433 /* rewind and copy chars, no prenewlines... */
1434 so_seek(store, 0L, 0);
1435 cntdown = sigsize;
1436 while(so_readc(&c, store) && cntdown-- > 0L)
1437 *q++ = c;
1439 *q = '\0';
1440 so_give(&store);
1442 else{
1443 so_give(&store);
1444 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1445 _("Error running program \"%s\""),
1446 file);
1449 else
1450 q_status_message(SM_ORDER | SM_DING, 3, 4,
1451 "Error allocating space for sig or template program");
1453 else
1454 q_status_message1(SM_ORDER | SM_DING, 3, 4,
1455 /* TRANSLATORS: Arg is a program name */
1456 _("Can't execute \"%s\": Permission denied"),
1457 sig_path);
1459 else if((IS_REMOTE(sig_path) &&
1460 (tmp_sig = simple_read_remote_file(sig_path, REMOTE_SIG_SUBTYPE))) ||
1461 (tmp_sig = read_file(sig_path, READ_FROM_LOCALE)))
1462 sigsize = strlen(tmp_sig);
1463 else
1464 q_status_message2(SM_ORDER | SM_DING, 3, 4,
1465 /* TRANSLATORS: First arg is error description, 2nd is
1466 filename */
1467 _("Error \"%s\" reading file \"%s\""),
1468 error_description(errno), sig_path);
1471 sig = get_signature_lit(tmp_sig, prenewlines, postnewlines, is_sig, 0);
1472 if(tmp_sig)
1473 fs_give((void **)&tmp_sig);
1475 return(sig);
1480 /*----------------------------------------------------------------------
1481 Partially set up message to forward and pass off to composer/mailer
1483 Args: pine_state -- The usual pine structure
1485 Result: outgoing envelope and body created and passed off to composer/mailer
1487 Create the outgoing envelope for the mail being forwarded, which is
1488 not much more than filling in the subject, and create the message body
1489 of the outgoing message which requires formatting the header from the
1490 envelope of the original message.
1491 ----------------------------------------------------------------------*/
1493 forward(struct pine *ps, ACTION_S *role_arg)
1495 char *sig;
1496 int ret, forward_raw_body = 0, rv = 0, i;
1497 long msgno = 0L, j, totalmsgs, rflags;
1498 ENVELOPE *env = NULL, *outgoing;
1499 BODY *orig_body, *body = NULL;
1500 REPLY_S reply;
1501 void *msgtext = NULL;
1502 gf_o_t pc;
1503 int impl, template_len = 0;
1504 PAT_STATE dummy;
1505 REDRAFT_POS_S *redraft_pos = NULL;
1506 ACTION_S *role = NULL, *nrole;
1507 #if defined(DOS) && !defined(_WINDOWS)
1508 char *reserve;
1509 #endif
1511 dprint((4, "\n - forward -\n"));
1513 memset((void *)&reply, 0, sizeof(reply));
1514 outgoing = mail_newenvelope();
1516 if(ps_global->full_header == 2
1517 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global))
1518 forward_raw_body = 1;
1520 if((totalmsgs = mn_total_cur(ps->msgmap)) > 1L){
1521 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s forwarded messages...", comatose(totalmsgs));
1522 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1523 outgoing->subject = cpystr(tmp_20k_buf);
1525 else{
1526 /*---------- Get the envelope of message we're forwarding ------*/
1527 msgno = mn_m2raw(ps->msgmap, mn_get_cur(ps->msgmap));
1528 if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL))
1529 && (outgoing->subject = forward_subject(env, 0)))){
1530 q_status_message1(SM_ORDER,3,4,
1531 _("Error fetching message %s. Can't forward it."),
1532 long2string(msgno));
1533 goto clean;
1538 * as with all text bound for the composer, build it in
1539 * a storage object of the type it understands...
1541 if((msgtext = (void *)so_get(PicoText, NULL, EDIT_ACCESS)) == NULL){
1542 q_status_message(SM_ORDER | SM_DING, 3, 4,
1543 _("Error allocating message text"));
1544 goto clean;
1547 ret = (F_ON(F_FORWARD_AS_ATTACHMENT, ps_global))
1548 ? 'y'
1549 : (totalmsgs > 1L)
1550 ? want_to(_("Forward messages as a MIME digest"), 'y', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1551 : (ps->full_header == 2)
1552 ? want_to(_("Forward message as an attachment"), 'n', 'x', NO_HELP, WT_SEQ_SENSITIVE)
1553 : 0;
1555 if(ret == 'x'){
1556 cmd_cancelled("Forward");
1557 so_give((STORE_S **)&msgtext);
1558 goto clean;
1561 /* Setup possible role */
1562 if(role_arg)
1563 role = copy_action(role_arg);
1565 if(!role){
1566 rflags = ROLE_FORWARD;
1567 if(nonempty_patterns(rflags, &dummy)){
1568 /* setup default role */
1569 nrole = NULL;
1570 j = mn_first_cur(ps->msgmap);
1571 do {
1572 role = nrole;
1573 nrole = set_role_from_msg(ps, rflags,
1574 mn_m2raw(ps->msgmap, j), NULL);
1575 } while(nrole && (!role || nrole == role)
1576 && (j=mn_next_cur(ps->msgmap)) > 0L);
1578 if(!role || nrole == role)
1579 role = nrole;
1580 else
1581 role = NULL;
1583 if(confirm_role(rflags, &role))
1584 role = combine_inherited_role(role);
1585 else{ /* cancel reply */
1586 role = NULL;
1587 cmd_cancelled("Forward");
1588 so_give((STORE_S **)&msgtext);
1589 goto clean;
1594 if(role)
1595 q_status_message1(SM_ORDER, 3, 4,
1596 _("Forwarding using role \"%s\""), role->nick);
1598 outgoing->message_id = generate_message_id(role);
1600 if(role && role->template){
1601 char *filtered;
1603 impl = 1;
1604 filtered = detoken(role, (totalmsgs == 1L) ? env : NULL,
1605 0, 0, 0, &redraft_pos, &impl);
1606 if(filtered){
1607 if(*filtered){
1608 so_puts((STORE_S *)msgtext, filtered);
1609 if(impl == 1)
1610 template_len = strlen(filtered);
1613 fs_give((void **)&filtered);
1616 else
1617 impl = 1;
1619 if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){
1620 if(impl == 2)
1621 redraft_pos->offset += template_len;
1623 so_puts((STORE_S *)msgtext, *sig ? sig : NEWLINE);
1625 fs_give((void **)&sig);
1627 else
1628 so_puts((STORE_S *)msgtext, NEWLINE);
1630 gf_set_so_writec(&pc, (STORE_S *)msgtext);
1632 #if defined(DOS) && !defined(_WINDOWS)
1633 #if defined(LWP) || defined(PCTCP) || defined(PCNFS)
1634 #define IN_RESERVE 8192
1635 #else
1636 #define IN_RESERVE 16384
1637 #endif
1638 if((reserve=(char *)malloc(IN_RESERVE)) == NULL){
1639 gf_clear_so_writec((STORE_S *) msgtext);
1640 so_give((STORE_S **)&msgtext);
1641 q_status_message(SM_ORDER | SM_DING, 3, 4,
1642 _("Insufficient memory for message text"));
1643 goto clean;
1645 #endif
1648 * If we're forwarding multiple messages *or* the forward-as-mime
1649 * is turned on and the users wants it done that way, package things
1650 * up...
1652 if(ret == 'y'){ /* attach message[s]!!! */
1653 PART **pp;
1654 long totalsize = 0L;
1656 /*---- New Body to start with ----*/
1657 body = mail_newbody();
1658 body->type = TYPEMULTIPART;
1660 /*---- The TEXT part/body ----*/
1661 body->nested.part = mail_newbody_part();
1662 body->nested.part->body.type = TYPETEXT;
1663 body->nested.part->body.contents.text.data = msgtext;
1665 if(totalmsgs > 1L){
1666 /*---- The MULTIPART/DIGEST part ----*/
1667 body->nested.part->next = mail_newbody_part();
1668 body->nested.part->next->body.type = TYPEMULTIPART;
1669 body->nested.part->next->body.subtype = cpystr("Digest");
1670 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "Digest of %s messages", comatose(totalmsgs));
1671 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1672 body->nested.part->next->body.description = cpystr(tmp_20k_buf);
1673 pp = &(body->nested.part->next->body.nested.part);
1675 else
1676 pp = &(body->nested.part->next);
1678 /*---- The Message body subparts ----*/
1679 for(msgno = mn_first_cur(ps->msgmap);
1680 msgno > 0L;
1681 msgno = mn_next_cur(ps->msgmap)){
1683 msgno = mn_m2raw(ps->msgmap, msgno);
1684 env = pine_mail_fetchstructure(ps->mail_stream, msgno, NULL);
1686 if(forward_mime_msg(ps->mail_stream,msgno,NULL,env,pp,msgtext)){
1687 totalsize += (*pp)->body.size.bytes;
1688 pp = &((*pp)->next);
1690 else
1691 goto bomb;
1694 if(totalmsgs > 1L)
1695 body->nested.part->next->body.size.bytes = totalsize;
1697 else if(totalmsgs > 1L){
1698 int warned = 0;
1699 body = mail_newbody();
1700 body->type = TYPETEXT;
1701 body->contents.text.data = msgtext;
1702 env = NULL;
1704 for(msgno = mn_first_cur(ps->msgmap);
1705 msgno > 0L;
1706 msgno = mn_next_cur(ps->msgmap)){
1708 if(env){ /* put 2 between messages */
1709 gf_puts(NEWLINE, pc);
1710 gf_puts(NEWLINE, pc);
1713 /*--- Grab current envelope ---*/
1714 env = pine_mail_fetchstructure(ps->mail_stream,
1715 mn_m2raw(ps->msgmap, msgno),
1716 &orig_body);
1717 if(!env || !orig_body){
1718 q_status_message1(SM_ORDER,3,4,
1719 _("Error fetching message %s. Can't forward it."),
1720 long2string(msgno));
1721 goto bomb;
1724 if(orig_body == NULL || orig_body->type == TYPETEXT || forward_raw_body) {
1725 forward_delimiter(pc);
1726 reply_forward_header(ps->mail_stream,
1727 mn_m2raw(ps->msgmap, msgno),
1728 NULL, env, pc, "");
1730 if(!get_body_part_text(ps->mail_stream, forward_raw_body ? NULL : orig_body,
1731 mn_m2raw(ps->msgmap, msgno),
1732 forward_raw_body ? NULL : "1", 0L, pc,
1733 NULL, NULL, GBPT_NONE))
1734 goto bomb;
1735 } else if(orig_body->type == TYPEMULTIPART) {
1736 if(!warned++)
1737 q_status_message(SM_ORDER,3,7,
1738 _("WARNING! Attachments not included in multiple forward."));
1740 if(orig_body->nested.part &&
1741 orig_body->nested.part->body.type == TYPETEXT) {
1742 /*---- First part of the message is text -----*/
1743 forward_delimiter(pc);
1744 reply_forward_header(ps->mail_stream,
1745 mn_m2raw(ps->msgmap,msgno),
1746 NULL, env, pc, "");
1748 if(!get_body_part_text(ps->mail_stream,
1749 &orig_body->nested.part->body,
1750 mn_m2raw(ps->msgmap, msgno),
1751 "1", 0L, pc,
1752 NULL, NULL, GBPT_NONE))
1753 goto bomb;
1754 } else {
1755 q_status_message(SM_ORDER,0,3,
1756 _("Multipart with no leading text part!"));
1758 } else {
1759 /*---- Single non-text message of some sort ----*/
1760 q_status_message(SM_ORDER,0,3,
1761 _("Non-text message not included!"));
1765 else if(!((env = pine_mail_fetchstructure(ps->mail_stream, msgno,
1766 &orig_body))
1767 && (body = forward_body(ps->mail_stream, env, orig_body, msgno,
1768 NULL, msgtext,
1769 FWD_NONE)))){
1770 q_status_message1(SM_ORDER,3,4,
1771 _("Error fetching message %s. Can't forward it."),
1772 long2string(msgno));
1773 goto clean;
1776 if(ret != 'y' && totalmsgs == 1L && orig_body){
1777 char *charset;
1779 charset = parameter_val(orig_body->parameter, "charset");
1780 if(charset && strucmp(charset, "us-ascii") != 0){
1781 CONV_TABLE *ct;
1784 * There is a non-ascii charset, is there conversion happening?
1786 if(!(ct=conversion_table(charset, ps_global->posting_charmap)) || !ct->table){
1787 reply.orig_charset = charset;
1788 charset = NULL;
1792 if(charset)
1793 fs_give((void **) &charset);
1796 * I don't think orig_charset is ever used except possibly
1797 * right here. Hubert 2008-01-15.
1799 if(reply.orig_charset)
1800 reply.forw = 1;
1803 /* fill in reply structure */
1804 reply.forwarded = 1;
1805 reply.mailbox = cpystr(ps->mail_stream->mailbox);
1806 reply.origmbox = cpystr(ps->mail_stream->original_mailbox
1807 ? ps->mail_stream->original_mailbox
1808 : ps->mail_stream->mailbox);
1809 reply.data.uid.msgs = (imapuid_t *) fs_get((totalmsgs + 1) * sizeof(imapuid_t));
1810 if((reply.data.uid.validity = ps->mail_stream->uid_validity) != 0){
1811 reply.uid = 1;
1812 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1813 msgno > 0L;
1814 msgno = mn_next_cur(ps->msgmap), i++)
1815 reply.data.uid.msgs[i] = mail_uid(ps->mail_stream, mn_m2raw(ps->msgmap, msgno));
1817 else{
1818 reply.msgno = 1;
1819 for(msgno = mn_first_cur(ps->msgmap), i = 0;
1820 msgno > 0L;
1821 msgno = mn_next_cur(ps->msgmap), i++)
1822 reply.data.uid.msgs[i] = mn_m2raw(ps->msgmap, msgno);
1825 reply.data.uid.msgs[i] = 0; /* tie off list */
1827 #if defined(DOS) && !defined(_WINDOWS)
1828 free((void *)reserve);
1829 #endif
1830 pine_send(outgoing, &body, "FORWARD MESSAGE",
1831 role, NULL, &reply, redraft_pos,
1832 NULL, NULL, 0);
1833 rv++;
1835 clean:
1836 if(body)
1837 pine_free_body(&body);
1839 if((STORE_S *) msgtext)
1840 gf_clear_so_writec((STORE_S *) msgtext);
1842 mail_free_envelope(&outgoing);
1843 free_redraft_pos(&redraft_pos);
1844 free_action(&role);
1846 if(reply.orig_charset)
1847 fs_give((void **)&reply.orig_charset);
1849 return rv;
1851 bomb:
1852 q_status_message(SM_ORDER | SM_DING, 4, 5,
1853 _("Error fetching message contents. Can't forward message."));
1854 goto clean;
1858 /*----------------------------------------------------------------------
1859 Partially set up message to forward and pass off to composer/mailer
1861 Args: pine_state -- The usual pine structure
1862 message -- The MESSAGECACHE of entry to reply to
1864 Result: outgoing envelope and body created and passed off to composer/mailer
1866 Create the outgoing envelope for the mail being forwarded, which is
1867 not much more than filling in the subject, and create the message body
1868 of the outgoing message which requires formatting the header from the
1869 envelope of the original message.
1870 ----------------------------------------------------------------------*/
1871 void
1872 forward_text(struct pine *pine_state, void *text, SourceType source)
1874 ENVELOPE *env;
1875 BODY *body;
1876 gf_o_t pc;
1877 gf_i_t gc;
1878 STORE_S *msgtext;
1879 char *enc_error, *sig;
1880 ACTION_S *role = NULL;
1881 PAT_STATE dummy;
1882 long rflags = ROLE_COMPOSE;
1884 if((msgtext = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){
1885 env = mail_newenvelope();
1886 body = mail_newbody();
1887 body->type = TYPETEXT;
1888 body->contents.text.data = (void *) msgtext;
1890 if(nonempty_patterns(rflags, &dummy)){
1892 * This is really more like Compose, even though it
1893 * is called Forward.
1895 if(confirm_role(rflags, &role))
1896 role = combine_inherited_role(role);
1897 else{ /* cancel */
1898 cmd_cancelled("Composition");
1899 display_message('x');
1900 mail_free_envelope(&env);
1901 pine_free_body(&body);
1902 return;
1906 if(role)
1907 q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""),
1908 role->nick);
1910 env->message_id = generate_message_id(role);
1912 sig = detoken(role, NULL, 2, 0, 1, NULL, NULL);
1913 so_puts(msgtext, (sig && *sig) ? sig : NEWLINE);
1914 so_puts(msgtext, NEWLINE);
1915 so_puts(msgtext, "----- Included text -----");
1916 so_puts(msgtext, NEWLINE);
1917 if(sig)
1918 fs_give((void **)&sig);
1920 gf_filter_init();
1921 gf_set_so_writec(&pc, msgtext);
1922 gf_set_readc(&gc,text,(source == CharStar) ? strlen((char *)text) : 0L,
1923 source, 0);
1925 if((enc_error = gf_pipe(gc, pc)) == NULL){
1926 pine_send(env, &body, "SEND MESSAGE", role, NULL, NULL, NULL,
1927 NULL, NULL, 0);
1928 pine_state->mangled_screen = 1;
1930 else{
1931 q_status_message1(SM_ORDER | SM_DING, 3, 5,
1932 _("Error reading text \"%s\""),enc_error);
1933 display_message('x');
1936 gf_clear_so_writec(msgtext);
1937 mail_free_envelope(&env);
1938 pine_free_body(&body);
1940 else {
1941 q_status_message(SM_ORDER | SM_DING, 3, 4,
1942 _("Error allocating message text"));
1943 display_message('x');
1946 free_action(&role);
1950 /*----------------------------------------------------------------------
1951 Partially set up message to resend and pass off to mailer
1953 Args: pine_state -- The usual pine structure
1955 Result: outgoing envelope and body created and passed off to mailer
1957 Create the outgoing envelope for the mail being resent, which is
1958 not much more than filling in the subject, and create the message body
1959 of the outgoing message which requires formatting the header from the
1960 envelope of the original message.
1961 ----------------------------------------------------------------------*/
1963 bounce(struct pine *pine_state, ACTION_S *role)
1965 ENVELOPE *env;
1966 long msgno, rawno;
1967 char *save_to = NULL, **save_toptr = NULL, *errstr = NULL,
1968 *prmpt_who = NULL, *prmpt_cnf = NULL;
1970 dprint((4, "\n - bounce -\n"));
1972 if(mn_total_cur(pine_state->msgmap) > 1L){
1973 save_toptr = &save_to;
1974 if(role)
1975 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages (using role %s) to : "),
1976 mn_total_cur(pine_state->msgmap), role->nick);
1977 else
1978 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("BOUNCE (redirect) %ld messages to : "),
1979 mn_total_cur(pine_state->msgmap));
1980 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1981 prmpt_who = cpystr(tmp_20k_buf);
1982 snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Send %ld messages "),
1983 mn_total_cur(pine_state->msgmap));
1984 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
1985 prmpt_cnf = cpystr(tmp_20k_buf);
1988 for(msgno = mn_first_cur(pine_state->msgmap);
1989 msgno > 0L;
1990 msgno = mn_next_cur(pine_state->msgmap)){
1992 rawno = mn_m2raw(pine_state->msgmap, msgno);
1993 if((env = pine_mail_fetchstructure(pine_state->mail_stream, rawno, NULL)) != NULL)
1994 errstr = bounce_msg(pine_state->mail_stream, rawno, NULL, role,
1995 save_toptr, env->subject, prmpt_who, prmpt_cnf);
1996 else
1997 errstr = _("Can't fetch Subject for Bounce");
2000 if(errstr){
2001 if(*errstr)
2002 q_status_message(SM_ORDER | SM_DING, 4, 7, errstr);
2004 break;
2008 if(save_to)
2009 fs_give((void **)&save_to);
2011 if(prmpt_who)
2012 fs_give((void **) &prmpt_who);
2014 if(prmpt_cnf)
2015 fs_give((void **) &prmpt_cnf);
2017 return(errstr ? 0 : 1);
2022 char *
2023 bounce_msg(MAILSTREAM *stream,
2024 long int rawno,
2025 char *part,
2026 ACTION_S *role,
2027 char **to,
2028 char *subject,
2029 char *pmt_who,
2030 char *pmt_cnf)
2032 char *errstr = NULL;
2033 int was_seen = -1;
2034 ENVELOPE *outgoing;
2035 BODY *body = NULL;
2036 MESSAGECACHE *mc;
2038 #ifdef SMIME
2039 /* When we bounce a message, we will leave the original message
2040 * intact, which means that it will not be signed or encrypted,
2041 * so we turn off signing and encrypting now. It will be turned
2042 * on again in send_exit_for_pico().
2044 if(ps_global->smime)
2045 ps_global->smime->do_sign = ps_global->smime->do_encrypt = 0;
2046 #endif /* SMIME */
2048 if((errstr = bounce_msg_body(stream, rawno, part, to, subject, &outgoing, &body, &was_seen)) == NULL){
2049 if(pine_simple_send(outgoing, &body, &role, pmt_who, pmt_cnf, to,
2050 !(to && *to) ? SS_PROMPTFORTO : 0) < 0){
2051 errstr = ""; /* p_s_s() better have explained! */
2052 /* clear seen flag */
2053 if(was_seen == 0 && rawno > 0L
2054 && stream && rawno <= stream->nmsgs
2055 && (mc = mail_elt(stream, rawno)) && mc->seen)
2056 mail_flag(stream, long2string(rawno), "\\SEEN", 0);
2060 /* Just for good measure... */
2061 mail_free_envelope(&outgoing);
2062 pine_free_body(&body);
2064 return(errstr); /* no problem-o */
2068 /*----------------------------------------------------------------------
2069 Serve up the current signature within pico for editing
2071 Args: file to edit
2073 Result: signature changed or not.
2074 ---*/
2075 char *
2076 signature_edit(char *sigfile, char *title)
2078 int editor_result;
2079 char sig_path[MAXPATH+1], errbuf[2000], *errstr = NULL;
2080 char *ret = NULL;
2081 STORE_S *msgso, *tmpso = NULL;
2082 gf_i_t gc;
2083 gf_o_t pc;
2084 PICO pbf;
2085 struct variable *vars = ps_global->vars;
2086 REMDATA_S *rd = NULL;
2088 if(!signature_path(sigfile, sig_path, MAXPATH))
2089 return(cpystr(_("No signature file defined.")));
2091 if(IS_REMOTE(sigfile)){
2092 rd = rd_create_remote(RemImap, sig_path, REMOTE_SIG_SUBTYPE,
2093 NULL, "Error: ",
2094 _("Can't access remote configuration."));
2095 if(!rd)
2096 return(cpystr(_("Error attempting to edit remote configuration")));
2098 (void)rd_read_metadata(rd);
2100 if(rd->access == MaybeRorW){
2101 if(rd->read_status == 'R')
2102 rd->access = ReadOnly;
2103 else
2104 rd->access = ReadWrite;
2107 if(rd->access != NoExists){
2109 rd_check_remvalid(rd, 1L);
2112 * If the cached info says it is readonly but
2113 * it looks like it's been fixed now, change it to readwrite.
2115 if(rd->read_status == 'R'){
2116 rd_check_readonly_access(rd);
2117 if(rd->read_status == 'W'){
2118 rd->access = ReadWrite;
2119 rd->flags |= REM_OUTOFDATE;
2121 else
2122 rd->access = ReadOnly;
2126 if(rd->flags & REM_OUTOFDATE){
2127 if(rd_update_local(rd) != 0){
2129 dprint((1,
2130 "signature_edit: rd_update_local failed\n"));
2131 rd_close_remdata(&rd);
2132 return(cpystr(_("Can't access remote sig")));
2135 else
2136 rd_open_remote(rd);
2138 if(rd->access != ReadWrite || rd_remote_is_readonly(rd)){
2139 rd_close_remdata(&rd);
2140 return(cpystr(_("Can't get write permission for remote sig")));
2143 rd->flags |= DO_REMTRIM;
2145 strncpy(sig_path, rd->lf, sizeof(sig_path)-1);
2146 sig_path[sizeof(sig_path)-1] = '\0';
2149 standard_picobuf_setup(&pbf);
2150 pbf.tty_fix = PineRaw;
2151 pbf.composer_help = h_composer_sigedit;
2152 pbf.exittest = sigedit_exit_for_pico;
2153 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2154 ? upload_msg_to_pico : NULL;
2155 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2156 ? VAR_EDITOR : NULL;
2157 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2158 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2159 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2160 pbf.allow_flowed_text = 0;
2162 pbf.pine_anchor = set_titlebar(title,
2163 ps_global->mail_stream,
2164 ps_global->context_current,
2165 ps_global->cur_folder,
2166 ps_global->msgmap,
2167 0, FolderName, 0, 0, NULL);
2169 /* NOTE: at this point, a lot of pico struct fields are null'd out
2170 * thanks to the leading memset; in particular "headents" which tells
2171 * pico to behave like a normal editor (though modified slightly to
2172 * let the caller dictate the file to edit and such)...
2175 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, sig_path)){
2176 size_t l;
2178 l = strlen(VAR_OPER_DIR) + 100;
2179 ret = (char *) fs_get((l+1) * sizeof(char));
2180 snprintf(ret, l+1, _("Can't edit file outside of %s"), VAR_OPER_DIR);
2181 ret[l] = '\0';
2182 return(ret);
2186 * Now alloc and init the text to pass pico
2188 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2189 ret = cpystr(_("Error allocating space for file"));
2190 dprint((1, "Can't alloc space for signature_edit"));
2191 return(ret);
2193 else
2194 pbf.msgtext = so_text(msgso);
2196 if(can_access(sig_path, READ_ACCESS) == 0
2197 && !(tmpso = so_get(FileStar, sig_path, READ_ACCESS|READ_FROM_LOCALE))){
2198 char *problem = error_description(errno);
2200 snprintf(errbuf, sizeof(errbuf), _("Error editing \"%s\": %s"),
2201 sig_path, problem ? problem : "<NULL>");
2202 errbuf[sizeof(errbuf)-1] = '\0';
2203 ret = cpystr(errbuf);
2205 dprint((1, "signature_edit: can't open %s: %s", sig_path,
2206 problem ? problem : "<NULL>"));
2207 return(ret);
2209 else if(tmpso){ /* else, fill pico's edit buffer */
2210 gf_set_so_readc(&gc, tmpso); /* read from file, write pico buf */
2211 gf_set_so_writec(&pc, msgso);
2212 gf_filter_init(); /* no filters needed */
2213 if((errstr = gf_pipe(gc, pc)) != NULL){
2214 snprintf(errbuf, sizeof(errbuf), _("Error reading file: \"%s\""), errstr);
2215 errbuf[sizeof(errbuf)-1] = '\0';
2216 ret = cpystr(errbuf);
2219 gf_clear_so_readc(tmpso);
2220 gf_clear_so_writec(msgso);
2221 so_give(&tmpso);
2224 if(!errstr){
2225 #ifdef _WINDOWS
2226 mswin_setwindowmenu (MENU_COMPOSER);
2227 #endif
2229 /*------ OK, Go edit the signature ------*/
2230 editor_result = pico(&pbf);
2232 #ifdef _WINDOWS
2233 mswin_setwindowmenu (MENU_DEFAULT);
2234 #endif
2235 if(editor_result & COMP_GOTHUP){
2236 hup_signal(0); /* do what's normal for a hup */
2238 else{
2239 fix_windsize(ps_global);
2240 init_signals();
2243 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2245 else{
2246 /*------ Must have an edited buffer, write it to .sig -----*/
2247 our_unlink(sig_path); /* blast old copy */
2248 if((tmpso = so_get(FileStar, sig_path, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
2249 so_seek(msgso, 0L, 0);
2250 gf_set_so_readc(&gc, msgso); /* read from pico buf */
2251 gf_set_so_writec(&pc, tmpso); /* write sig file */
2252 gf_filter_init(); /* no filters needed */
2253 if((errstr = gf_pipe(gc, pc)) != NULL){
2254 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2255 errstr);
2256 errbuf[sizeof(errbuf)-1] = '\0';
2257 ret = cpystr(errbuf);
2260 gf_clear_so_readc(msgso);
2261 gf_clear_so_writec(tmpso);
2262 if(so_give(&tmpso)){
2263 errstr = error_description(errno);
2264 snprintf(errbuf, sizeof(errbuf), _("Error writing file: \"%s\""),
2265 errstr);
2266 errbuf[sizeof(errbuf)-1] = '\0';
2267 ret = cpystr(errbuf);
2270 if(IS_REMOTE(sigfile)){
2271 int e, we_cancel;
2272 char datebuf[200];
2274 datebuf[0] = '\0';
2276 we_cancel = busy_cue("Copying to remote sig", NULL, 1);
2277 if((e = rd_update_remote(rd, datebuf)) != 0){
2278 if(e == -1){
2279 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2280 _("Error opening temporary sig file %s: %s"),
2281 rd->lf, error_description(errno));
2282 dprint((1,
2283 "write_remote_sig: error opening temp file %s\n",
2284 rd->lf ? rd->lf : "?"));
2286 else{
2287 q_status_message2(SM_ORDER | SM_DING, 3, 5,
2288 _("Error copying to %s: %s"),
2289 rd->rn, error_description(errno));
2290 dprint((1,
2291 "write_remote_sig: error copying from %s to %s\n",
2292 rd->lf ? rd->lf : "?", rd->rn ? rd->rn : "?"));
2295 q_status_message(SM_ORDER | SM_DING, 5, 5,
2296 _("Copy of sig to remote folder failed, changes NOT saved remotely"));
2298 else{
2299 rd_update_metadata(rd, datebuf);
2300 rd->read_status = 'W';
2303 rd_close_remdata(&rd);
2305 if(we_cancel)
2306 cancel_busy_cue(-1);
2309 else{
2310 snprintf(errbuf, sizeof(errbuf), _("Error writing \"%s\""), sig_path);
2311 errbuf[sizeof(errbuf)-1] = '\0';
2312 ret = cpystr(errbuf);
2313 dprint((1, "signature_edit: can't write %s",
2314 sig_path));
2319 standard_picobuf_teardown(&pbf);
2320 so_give(&msgso);
2321 return(ret);
2325 /*----------------------------------------------------------------------
2326 Serve up the current signature within pico for editing
2328 Args: literal signature to edit
2330 Result: raw edited signature is returned in result arg
2331 ---*/
2332 char *
2333 signature_edit_lit(char *litsig, char **result, char *title, HelpType composer_help)
2335 int editor_result;
2336 char *errstr = NULL;
2337 char *ret = NULL;
2338 STORE_S *msgso;
2339 PICO pbf;
2340 struct variable *vars = ps_global->vars;
2342 standard_picobuf_setup(&pbf);
2343 pbf.tty_fix = PineRaw;
2344 pbf.search_help = h_sigedit_search;
2345 pbf.composer_help = composer_help;
2346 pbf.exittest = sigedit_exit_for_pico;
2347 pbf.upload = (VAR_UPLOAD_CMD && VAR_UPLOAD_CMD[0])
2348 ? upload_msg_to_pico : NULL;
2349 pbf.alt_ed = (VAR_EDITOR && VAR_EDITOR[0] && VAR_EDITOR[0][0])
2350 ? VAR_EDITOR : NULL;
2351 pbf.alt_spell = (VAR_SPELLER && VAR_SPELLER[0]) ? VAR_SPELLER : NULL;
2352 pbf.always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global);
2353 pbf.strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global);
2354 pbf.allow_flowed_text = 0;
2356 pbf.pine_anchor = set_titlebar(title,
2357 ps_global->mail_stream,
2358 ps_global->context_current,
2359 ps_global->cur_folder,
2360 ps_global->msgmap,
2361 0, FolderName, 0, 0, NULL);
2363 /* NOTE: at this point, a lot of pico struct fields are null'd out
2364 * thanks to the leading memset; in particular "headents" which tells
2365 * pico to behave like a normal editor (though modified slightly to
2366 * let the caller dictate the file to edit and such)...
2370 * Now alloc and init the text to pass pico
2372 if(!(msgso = so_get(PicoText, NULL, EDIT_ACCESS))){
2373 ret = cpystr(_("Error allocating space"));
2374 dprint((1, "Can't alloc space for signature_edit_lit"));
2375 return(ret);
2377 else
2378 pbf.msgtext = so_text(msgso);
2380 so_puts(msgso, litsig ? litsig : "");
2383 if(!errstr){
2384 #ifdef _WINDOWS
2385 mswin_setwindowmenu (MENU_COMPOSER);
2386 #endif
2388 /*------ OK, Go edit the signature ------*/
2389 editor_result = pico(&pbf);
2391 #ifdef _WINDOWS
2392 mswin_setwindowmenu (MENU_DEFAULT);
2393 #endif
2394 if(editor_result & COMP_GOTHUP){
2395 hup_signal(0); /* do what's normal for a hup */
2397 else{
2398 fix_windsize(ps_global);
2399 init_signals();
2402 if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){
2403 ret = cpystr(_("Edit Cancelled"));
2405 else{
2406 /*------ Must have an edited buffer, write it to .sig -----*/
2407 unsigned char c;
2408 int cnt = 0;
2409 char *p;
2411 so_seek(msgso, 0L, 0);
2412 while(so_readc(&c, msgso))
2413 cnt++;
2415 *result = (char *)fs_get((cnt+1) * sizeof(char));
2416 p = *result;
2417 so_seek(msgso, 0L, 0);
2418 while(so_readc(&c, msgso))
2419 *p++ = c;
2421 *p = '\0';
2425 standard_picobuf_teardown(&pbf);
2426 so_give(&msgso);
2427 return(ret);
2432 * Returns 0 for Save Changes and exit
2433 * 1 for Cancel Exit
2434 * -1 exit but Dont Save Changes
2437 sigedit_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed,
2438 char **result)
2440 int rv;
2441 char *rstr = NULL;
2442 void (*redraw)(void) = ps_global->redrawer;
2443 static ESCKEY_S opts[] = {
2444 {'s', 's', "S", N_("Save changes")},
2445 {'d', 'd', "D", N_("Don't save changes")},
2446 {-1, 0, NULL, NULL}
2449 ps_global->redrawer = redraw_pico;
2450 fix_windsize(ps_global);
2452 while(1){
2453 rv = radio_buttons(_("Exit editor? "),
2454 -FOOTER_ROWS(ps_global), opts,
2455 's', 'x', h_exit_editor, RB_NORM);
2456 if(rv == 's'){ /* user ACCEPTS! */
2457 break;
2459 else if(rv == 'd'){ /* Declined! */
2460 rstr = _("No Changes Saved");
2461 break;
2463 else if(rv == 'x'){ /* Cancelled! */
2464 rstr = _("Exit Cancelled");
2465 break;
2469 if(result)
2470 *result = rstr;
2472 ps_global->redrawer = redraw;
2473 return((rv == 's') ? 0 : (rv == 'd') ? -1 : 1);
2478 * Common stuff we almost always want to set when calling pico.
2480 void
2481 standard_picobuf_setup(PICO *pbf)
2483 memset(pbf, 0, sizeof(*pbf));
2485 pbf->pine_version = ALPINE_VERSION;
2486 pbf->fillcolumn = ps_global->composer_fillcol;
2487 pbf->menu_rows = FOOTER_ROWS(ps_global) - 1;
2488 pbf->colors = colors_for_pico();
2489 pbf->wordseps = user_wordseps(ps_global->VAR_WORDSEPS);
2490 pbf->helper = helper;
2491 pbf->showmsg = display_message_for_pico;
2492 pbf->suspend = do_suspend;
2493 pbf->keybinput = cmd_input_for_pico;
2494 pbf->tty_fix = ttyfix; /* watch out for this one */
2495 pbf->newmail = new_mail_for_pico;
2496 pbf->ckptdir = checkpoint_dir_for_pico;
2497 pbf->resize = resize_for_pico;
2498 pbf->input_cs = ps_global->input_cs;
2499 pbf->winch_cleanup = winch_cleanup;
2500 pbf->search_help = h_composer_search;
2501 pbf->ins_help = h_composer_ins;
2502 pbf->ins_m_help = h_composer_ins_m;
2503 pbf->composer_help = h_composer;
2504 pbf->browse_help = h_composer_browse;
2505 pbf->attach_help = h_composer_ctrl_j;
2507 pbf->pine_flags =
2508 ( (F_ON(F_CAN_SUSPEND,ps_global) ? P_SUSPEND : 0L)
2509 | (F_ON(F_USE_FK,ps_global) ? P_FKEYS : 0L)
2510 | (ps_global->restricted ? P_SECURE : 0L)
2511 | (F_ON(F_ALT_ED_NOW,ps_global) ? P_ALTNOW : 0L)
2512 | (F_ON(F_USE_CURRENT_DIR,ps_global) ? P_CURDIR : 0L)
2513 | (F_ON(F_SUSPEND_SPAWNS,ps_global) ? P_SUBSHELL : 0L)
2514 | (F_ON(F_COMPOSE_MAPS_DEL,ps_global) ? P_DELRUBS : 0L)
2515 | (F_ON(F_ENABLE_TAB_COMPLETE,ps_global) ? P_COMPLETE : 0L)
2516 | (F_ON(F_SHOW_CURSOR,ps_global) ? P_SHOCUR : 0L)
2517 | (F_ON(F_DEL_FROM_DOT,ps_global) ? P_DOTKILL : 0L)
2518 | (F_ON(F_ENABLE_DOT_FILES,ps_global) ? P_DOTFILES : 0L)
2519 | (F_ON(F_ALLOW_GOTO,ps_global) ? P_ALLOW_GOTO : 0L)
2520 | (F_ON(F_ENABLE_SEARCH_AND_REPL,ps_global) ? P_REPLACE : 0L)
2521 | (!ps_global->pass_ctrl_chars
2522 && !ps_global->pass_c1_ctrl_chars ? P_HICTRL : 0L)
2523 | ((F_ON(F_ENABLE_ALT_ED,ps_global)
2524 || F_ON(F_ALT_ED_NOW,ps_global)
2525 || (ps_global->VAR_EDITOR
2526 && ps_global->VAR_EDITOR[0]
2527 && ps_global->VAR_EDITOR[0][0]))
2528 ? P_ADVANCED : 0L)
2529 | ((!ps_global->keyboard_charmap
2530 || !strucmp(ps_global->keyboard_charmap, "US-ASCII"))
2531 ? P_HIBITIGN : 0L));
2533 if(ps_global->VAR_OPER_DIR){
2534 pbf->oper_dir = ps_global->VAR_OPER_DIR;
2535 pbf->pine_flags |= P_TREE;
2538 pbf->home_dir = ps_global->home_dir;
2542 void
2543 standard_picobuf_teardown(PICO *pbf)
2545 if(pbf){
2546 if(pbf->colors)
2547 free_pcolors(&pbf->colors);
2549 if(pbf->wordseps)
2550 fs_give((void **) &pbf->wordseps);
2555 /*----------------------------------------------------------------------
2556 Call back for pico to use to check for new mail.
2558 Args: cursor -- pointer to in to tell caller if cursor location changed
2559 if NULL, turn off cursor positioning.
2560 timing -- whether or not it's a good time to check
2563 Returns: returns 1 on success, zero on error.
2564 ----*/
2565 long
2566 new_mail_for_pico(int timing, int status)
2569 * If we're not interested in the status, don't display the busy
2570 * cue either...
2572 /* don't know where the cursor's been, reset it */
2573 clear_cursor_pos();
2574 return(new_mail(0, timing,
2575 (status ? NM_STATUS_MSG : NM_NONE) | NM_DEFER_SORT
2576 | NM_FROM_COMPOSER));
2580 void
2581 cmd_input_for_pico(void)
2583 zero_new_mail_count();
2587 /*----------------------------------------------------------------------
2588 Call back for pico to get newmail status messages displayed
2590 Args: x -- char processed
2592 Returns:
2593 ----*/
2595 display_message_for_pico(UCS x)
2597 int rv;
2599 clear_cursor_pos(); /* can't know where cursor is */
2600 mark_status_dirty(); /* don't count on cached text */
2601 fix_windsize(ps_global);
2602 init_sigwinch();
2603 display_message(x);
2604 rv = ps_global->mangled_screen;
2605 ps_global->mangled_screen = 0;
2606 return(rv);
2610 /*----------------------------------------------------------------------
2611 Call back for pico to get desired directory for its check point file
2613 Args: s -- buffer to write directory name
2614 n -- length of that buffer
2616 Returns: pointer to static buffer
2617 ----*/
2618 char *
2619 checkpoint_dir_for_pico(char *s, size_t n)
2621 #if defined(DOS) || defined(OS2)
2623 * we can't assume anything about root or home dirs, so
2624 * just plunk it down in the same place as the pinerc
2626 if(!getenv("HOME")){
2627 char *lc = last_cmpnt(ps_global->pinerc);
2629 if(lc != NULL){
2630 strncpy(s, ps_global->pinerc, MIN(n-1,lc-ps_global->pinerc));
2631 s[MIN(n-1,lc-ps_global->pinerc)] = '\0';
2633 else{
2634 strncpy(s, ".\\", n-1);
2635 s[n-1] = '\0';
2638 else
2639 #endif
2640 strncpy(s, ps_global->home_dir, n-1);
2641 s[n-1] = '\0';
2643 return(s);
2647 /*----------------------------------------------------------------------
2648 Call back for pico to tell us the window size's changed
2650 Args: none
2652 Returns: none (but pine's ttyo structure may have been updated)
2653 ----*/
2654 void
2655 resize_for_pico(void)
2657 fix_windsize(ps_global);
2661 PCOLORS *
2662 colors_for_pico(void)
2664 PCOLORS *colors = NULL;
2665 struct variable *vars = ps_global->vars;
2667 if (pico_usingcolor()){
2668 colors = (PCOLORS *)fs_get(sizeof(PCOLORS));
2670 colors->tbcp = current_titlebar_color();
2672 if (VAR_KEYLABEL_FORE_COLOR && VAR_KEYLABEL_BACK_COLOR){
2673 colors->klcp = new_color_pair(VAR_KEYLABEL_FORE_COLOR,
2674 VAR_KEYLABEL_BACK_COLOR);
2675 if (!pico_is_good_colorpair(colors->klcp))
2676 free_color_pair(&colors->klcp);
2678 else colors->klcp = NULL;
2680 if (colors->klcp && VAR_KEYNAME_FORE_COLOR && VAR_KEYNAME_BACK_COLOR){
2681 colors->kncp = new_color_pair(VAR_KEYNAME_FORE_COLOR,
2682 VAR_KEYNAME_BACK_COLOR);
2684 else colors->kncp = NULL;
2686 if (VAR_STATUS_FORE_COLOR && VAR_STATUS_BACK_COLOR){
2687 colors->stcp = new_color_pair(VAR_STATUS_FORE_COLOR,
2688 VAR_STATUS_BACK_COLOR);
2690 else colors->stcp = NULL;
2692 if (VAR_PROMPT_FORE_COLOR && VAR_PROMPT_BACK_COLOR){
2693 colors->prcp = new_color_pair(VAR_PROMPT_FORE_COLOR,
2694 VAR_PROMPT_BACK_COLOR);
2696 else colors->prcp = NULL;
2698 if (VAR_QUOTE1_FORE_COLOR && VAR_QUOTE1_BACK_COLOR){
2699 colors->qlcp = new_color_pair(VAR_QUOTE1_FORE_COLOR,
2700 VAR_QUOTE1_BACK_COLOR);
2702 else colors->qlcp = NULL;
2704 if (VAR_QUOTE2_FORE_COLOR && VAR_QUOTE2_BACK_COLOR){
2705 colors->qllcp = new_color_pair(VAR_QUOTE2_FORE_COLOR,
2706 VAR_QUOTE2_BACK_COLOR);
2708 else colors->qllcp = NULL;
2710 if (VAR_QUOTE3_FORE_COLOR && VAR_QUOTE3_BACK_COLOR){
2711 colors->qlllcp = new_color_pair(VAR_QUOTE3_FORE_COLOR,
2712 VAR_QUOTE3_BACK_COLOR);
2714 else colors->qlllcp = NULL;
2716 if (VAR_NORM_FORE_COLOR && VAR_NORM_BACK_COLOR){
2717 colors->ntcp = new_color_pair(VAR_NORM_FORE_COLOR,
2718 VAR_NORM_BACK_COLOR);
2720 else colors->ntcp = NULL;
2722 if (VAR_REV_FORE_COLOR && VAR_REV_BACK_COLOR){
2723 colors->rtcp = new_color_pair(VAR_REV_FORE_COLOR,
2724 VAR_REV_BACK_COLOR);
2726 else colors->rtcp = NULL;
2728 if (VAR_SIGNATURE_FORE_COLOR && VAR_SIGNATURE_BACK_COLOR){
2729 colors->sbcp = new_color_pair(VAR_SIGNATURE_FORE_COLOR,
2730 VAR_SIGNATURE_BACK_COLOR);
2732 else colors->sbcp = NULL;
2735 return colors;
2739 void
2740 free_pcolors(PCOLORS **colors)
2742 if (*colors){
2743 if ((*colors)->tbcp)
2744 free_color_pair(&(*colors)->tbcp);
2745 if ((*colors)->kncp)
2746 free_color_pair(&(*colors)->kncp);
2747 if ((*colors)->klcp)
2748 free_color_pair(&(*colors)->klcp);
2749 if ((*colors)->stcp)
2750 free_color_pair(&(*colors)->stcp);
2751 if ((*colors)->prcp)
2752 free_color_pair(&(*colors)->prcp);
2753 if ((*colors)->qlcp)
2754 free_color_pair(&(*colors)->qlcp);
2755 if ((*colors)->qllcp)
2756 free_color_pair(&(*colors)->qllcp);
2757 if ((*colors)->qlllcp)
2758 free_color_pair(&(*colors)->qlllcp);
2759 if ((*colors)->ntcp)
2760 free_color_pair(&(*colors)->ntcp);
2761 if ((*colors)->rtcp)
2762 free_color_pair(&(*colors)->rtcp);
2763 if ((*colors)->sbcp)
2764 free_color_pair(&(*colors)->sbcp);
2765 fs_give((void **)colors);
2766 *colors = NULL;