* New version 2.21.999
[alpine.git] / pith / imap.c
blob5d9a789c3f60f187f0496473de86e441ce865098
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: imap.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2008 University of Washington
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 #include "../pith/headers.h"
20 #include "../pith/imap.h"
21 #include "../pith/msgno.h"
22 #include "../pith/state.h"
23 #include "../pith/flag.h"
24 #include "../pith/pineelt.h"
25 #include "../pith/status.h"
26 #include "../pith/conftype.h"
27 #include "../pith/context.h"
28 #include "../pith/thread.h"
29 #include "../pith/mailview.h"
30 #include "../pith/mailpart.h"
31 #include "../pith/mailindx.h"
32 #include "../pith/mailcmd.h"
33 #include "../pith/save.h"
34 #include "../pith/util.h"
35 #include "../pith/stream.h"
36 #include "../pith/newmail.h"
37 #include "../pith/icache.h"
38 #include "../pith/options.h"
40 #ifdef _WINDOWS
41 #include "../pico/osdep/mswin.h"
42 #endif
46 * Internal prototypes
48 long imap_seq_exec(MAILSTREAM *, char *, long (*)(MAILSTREAM *, long, void *), void *);
49 long imap_seq_exec_append(MAILSTREAM *, long, void *);
50 char *ps_get(size_t);
54 * Exported globals setup by searching functions to tell mm_searched
55 * where to put message numbers that matched the search criteria,
56 * and to allow mm_searched to return number of matches.
58 MAILSTREAM *mm_search_stream;
59 long mm_search_count = 0L;
60 MAILSTATUS mm_status_result;
62 MM_LIST_S *mm_list_info;
64 MMLOGIN_S *mm_login_list = NULL;
65 MMLOGIN_S *cert_failure_list = NULL;
68 * Instead of storing cached passwords in free storage, store them in this
69 * private space. This makes it easier and more reliable when we want
70 * to zero this space out. We only store passwords here (char *) so we
71 * don't need to worry about alignment.
73 static volatile char private_store[1024];
75 static int critical_depth = 0;
77 /* hook to hang callback on "current" message expunge */
78 void (*pith_opt_current_expunged)(long unsigned int);
80 #ifdef SIGINT
81 RETSIGTYPE (*hold_int)(int);
82 #endif
84 #ifdef SIGTERM
85 RETSIGTYPE (*hold_term)(int);
86 #endif
88 #ifdef SIGHUP
89 RETSIGTYPE (*hold_hup)(int);
90 #endif
92 #ifdef SIGUSR2
93 RETSIGTYPE (*hold_usr2)(int);
94 #endif
98 /*----------------------------------------------------------------------
99 receive notification that search found something
101 Input: mail stream and message number of located item
103 Result: nothing, not used by pine
104 ----*/
105 void
106 mm_searched(MAILSTREAM *stream, long unsigned int rawno)
108 MESSAGECACHE *mc;
110 if(rawno > 0L && stream && rawno <= stream->nmsgs
111 && (mc = mail_elt(stream, rawno))){
112 mc->searched = 1;
113 if(stream == mm_search_stream)
114 mm_search_count++;
119 /*----------------------------------------------------------------------
120 receive notification of new mail from imap daemon
122 Args: stream -- The stream the message count report is for.
123 number -- The number of messages now in folder.
125 Result: Sets value in pine state indicating new mailbox size
127 Called when the number of messages in the mailbox goes up. This
128 may also be called as a result of an expunge. It increments the
129 new_mail_count based on a the difference between the current idea of
130 the maximum number of messages and what mm_exists claims. The new mail
131 notification is done in newmail.c
133 Only worry about the cases when the number grows, as mm_expunged
134 handles shrinkage...
136 ----*/
137 void
138 mm_exists(MAILSTREAM *stream, long unsigned int number)
140 long new_this_call, n;
141 int exbits = 0, lflags = 0;
142 MSGNO_S *msgmap;
144 #ifdef DEBUG
145 if(ps_global->debug_imap > 1 || ps_global->debugmem)
146 dprint((3, "=== mm_exists(%lu,%s) called ===\n", number,
147 !stream ? "(no stream)" : !stream->mailbox ? "(null)" : stream->mailbox));
148 #endif
150 msgmap = sp_msgmap(stream);
151 if(!msgmap)
152 return;
154 if(mn_get_nmsgs(msgmap) != (long) number){
155 sp_set_mail_box_changed(stream, 1);
156 /* titlebar will be affected */
157 if(ps_global->mail_stream == stream)
158 ps_global->mangled_header = 1;
161 if(mn_get_nmsgs(msgmap) < (long) number){
162 new_this_call = (long) number - mn_get_nmsgs(msgmap);
163 sp_set_new_mail_count(stream,
164 sp_new_mail_count(stream) + new_this_call);
165 sp_set_recent_since_visited(stream,
166 sp_recent_since_visited(stream) + new_this_call);
168 mn_add_raw(msgmap, new_this_call);
171 * Set local "recent" and "hidden" bits...
173 for(n = 0; n < new_this_call; n++, number--){
174 if(msgno_exceptions(stream, number, "0", &exbits, FALSE))
175 exbits |= MSG_EX_RECENT;
176 else
177 exbits = MSG_EX_RECENT;
179 msgno_exceptions(stream, number, "0", &exbits, TRUE);
181 if(SORT_IS_THREADED(msgmap))
182 lflags |= MN_USOR;
185 * If we're zoomed, then hide this message too since
186 * it couldn't have possibly been selected yet...
188 lflags |= (any_lflagged(msgmap, MN_HIDE) ? MN_HIDE : 0);
189 if(lflags)
190 set_lflag(stream, msgmap, mn_get_total(msgmap) - n, lflags, 1);
196 /*----------------------------------------------------------------------
197 Receive notification from IMAP that a single message has been expunged
199 Args: stream -- The stream/folder the message is expunged from
200 rawno -- The raw message number that was expunged
202 mm_expunged is always called on an expunge. Simply remove all
203 reference to the expunged message, shifting internal mappings as
204 necessary.
205 ----*/
206 void
207 mm_expunged(MAILSTREAM *stream, long unsigned int rawno)
209 MESSAGECACHE *mc = NULL;
210 long i;
211 int is_current = 0;
212 MSGNO_S *msgmap;
214 #ifdef DEBUG
215 if(ps_global->debug_imap > 1 || ps_global->debugmem)
216 dprint((3, "mm_expunged(%s,%lu)\n",
217 stream
218 ? (stream->mailbox
219 ? stream->mailbox
220 : "(no stream)")
221 : "(null)", rawno));
222 #endif
224 msgmap = sp_msgmap(stream);
225 if(!msgmap)
226 return;
228 if(ps_global->mail_stream == stream)
229 is_current++;
231 if((i = mn_raw2m(msgmap, (long) rawno)) != 0L){
232 dprint((7, "mm_expunged: rawno=%lu msgno=%ld nmsgs=%ld max_msgno=%ld flagged_exld=%ld\n", rawno, i, mn_get_nmsgs(msgmap), mn_get_total(msgmap), msgmap->flagged_exld));
234 sp_set_mail_box_changed(stream, 1);
235 sp_set_expunge_count(stream, sp_expunge_count(stream) + 1);
237 if(is_current){
238 reset_check_point(stream);
239 ps_global->mangled_header = 1;
241 /* flush invalid cache entries */
242 while(i <= mn_get_total(msgmap))
243 clear_index_cache_ent(stream, i++, 0);
245 /* let app know what happened */
246 if(pith_opt_current_expunged)
247 (*pith_opt_current_expunged)(rawno);
250 else{
251 dprint((7,
252 "mm_expunged: rawno=%lu was excluded, flagged_exld was %d\n",
253 rawno, msgmap->flagged_exld));
254 dprint((7, " nmsgs=%ld max_msgno=%ld\n",
255 mn_get_nmsgs(msgmap), mn_get_total(msgmap)));
256 if(rawno > 0L && rawno <= stream->nmsgs)
257 mc = mail_elt(stream, rawno);
259 if(!mc){
260 dprint((7, " cannot get mail_elt(%lu)\n",
261 rawno));
263 else if(!mc->sparep){
264 dprint((7, " mail_elt(%lu)->sparep is NULL\n",
265 rawno));
267 else{
268 dprint((7, " mail_elt(%lu)->sparep->excluded=%d\n",
269 rawno, (int) (((PINELT_S *) mc->sparep)->excluded)));
273 if(SORT_IS_THREADED(msgmap)
274 && (SEP_THRDINDX()
275 || ps_global->thread_disp_style != THREAD_NONE)){
276 long cur;
279 * When we're sorting with a threaded method an expunged
280 * message may cause the rest of the sort to be wrong. This
281 * isn't so bad if we're just looking at the index. However,
282 * it also causes the thread tree (PINETHRD_S) to become
283 * invalid, so if we're using a threading view we need to
284 * sort in order to fix the tree and to protect fetch_thread().
286 sp_set_need_to_rethread(stream, 1);
289 * If we expunged the current message which was a member of the
290 * viewed thread, and the adjustment to current will take us
291 * out of that thread, fix it if we can, by backing current up
292 * into the thread. We'd like to just check after mn_flush_raw
293 * below but the problem is that the elts won't change until
294 * after we return from mm_expunged. So we have to manually
295 * check the other messages for CHID2 flags instead of thinking
296 * that we can expunge the current message and then check. It won't
297 * work because the elt will still refer to the expunged message.
299 if(sp_viewing_a_thread(stream)
300 && get_lflag(stream, NULL, rawno, MN_CHID2)
301 && mn_total_cur(msgmap) == 1
302 && mn_is_cur(msgmap, mn_raw2m(msgmap, (long) rawno))
303 && (cur = mn_get_cur(msgmap)) > 1L
304 && cur < mn_get_total(msgmap)
305 && !get_lflag(stream, msgmap, cur + 1L, MN_CHID2)
306 && get_lflag(stream, msgmap, cur - 1L, MN_CHID2))
307 mn_set_cur(msgmap, cur - 1L);
311 * Keep on top of our special flag counts.
313 * NOTE: This is allowed since mail_expunged releases
314 * data for this message after the callback.
316 if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){
317 PINELT_S *pelt = (PINELT_S *) mc->sparep;
319 if(pelt){
320 if(pelt->hidden)
321 msgmap->flagged_hid--;
323 if(pelt->excluded)
324 msgmap->flagged_exld--;
326 if(pelt->selected)
327 msgmap->flagged_tmp--;
329 if(pelt->colhid)
330 msgmap->flagged_chid--;
332 if(pelt->colhid2)
333 msgmap->flagged_chid2--;
335 if(pelt->collapsed)
336 msgmap->flagged_coll--;
338 if(pelt->tmp)
339 msgmap->flagged_stmp--;
341 if(pelt->unsorted)
342 msgmap->flagged_usor--;
344 if(pelt->searched)
345 msgmap->flagged_srch--;
347 if(pelt->hidden || pelt->colhid)
348 msgmap->flagged_invisible--;
350 free_pine_elt(&mc->sparep);
355 * if it's in the sort array, flush it, otherwise
356 * decrement raw sequence numbers greater than "rawno"
358 mn_flush_raw(msgmap, (long) rawno);
362 void
363 mm_flags(MAILSTREAM *stream, long unsigned int rawno)
366 * The idea here is to clean up any data pine might have cached
367 * that has anything to do with the indicated message number.
369 if(stream == ps_global->mail_stream){
370 long msgno, t;
371 PINETHRD_S *thrd;
373 if(scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP)
374 clear_msg_score(stream, rawno);
376 msgno = mn_raw2m(sp_msgmap(stream), (long) rawno);
378 /* if in thread index */
379 if(THRD_INDX()){
380 if((thrd = fetch_thread(stream, rawno))
381 && thrd->top
382 && (thrd = fetch_thread(stream, thrd->top))
383 && thrd->rawno
384 && (t = mn_raw2m(sp_msgmap(stream), thrd->rawno)))
385 clear_index_cache_ent(stream, t, 0);
387 else if(THREADING()){
388 if(msgno > 0L)
389 clear_index_cache_ent(stream, msgno, 0);
392 * If a parent is collapsed, clear that parent's
393 * index cache entry.
395 if((thrd = fetch_thread(stream, rawno)) && thrd->parent){
396 thrd = fetch_thread(stream, thrd->parent);
397 while(thrd){
398 if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
399 && (t = mn_raw2m(sp_msgmap(stream), (long) thrd->rawno)))
400 clear_index_cache_ent(stream, t, 0);
402 if(thrd->parent)
403 thrd = fetch_thread(stream, thrd->parent);
404 else
405 thrd = NULL;
409 else if(msgno > 0L)
410 clear_index_cache_ent(stream, msgno, 0);
412 if(msgno && mn_is_cur(sp_msgmap(stream), msgno))
413 ps_global->mangled_header = 1;
417 * We count up flag changes here. The
418 * dont_count_flagchanges variable tries to prevent us from
419 * counting when we're just fetching flags.
421 if(!(ps_global->dont_count_flagchanges
422 && stream == ps_global->mail_stream)){
423 int exbits;
425 check_point_change(stream);
427 /* we also note flag changes for filtering purposes */
428 if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
429 exbits |= MSG_EX_STATECHG;
430 else
431 exbits = MSG_EX_STATECHG;
433 msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
438 void
439 mm_list(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
441 #ifdef DEBUG
442 if(ps_global->debug_imap > 2 || ps_global->debugmem)
443 dprint((5, "mm_list \"%s\": delim: '%c', %s%s%s%s%s%s\n",
444 mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
445 (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
446 (attributes & LATT_NOSELECT) ? ", no select" : "",
447 (attributes & LATT_MARKED) ? ", marked" : "",
448 (attributes & LATT_UNMARKED) ? ", unmarked" : "",
449 (attributes & LATT_HASCHILDREN) ? ", has children" : "",
450 (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
451 #endif
453 if(!mm_list_info->stream || stream == mm_list_info->stream)
454 (*mm_list_info->filter)(stream, mailbox, delimiter,
455 attributes, mm_list_info->data,
456 mm_list_info->options);
460 void
461 mm_lsub(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
463 #ifdef DEBUG
464 if(ps_global->debug_imap > 2 || ps_global->debugmem)
465 dprint((5, "LSUB \"%s\": delim: '%c', %s%s%s%s%s%s\n",
466 mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
467 (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
468 (attributes & LATT_NOSELECT) ? ", no select" : "",
469 (attributes & LATT_MARKED) ? ", marked" : "",
470 (attributes & LATT_UNMARKED) ? ", unmarked" : "",
471 (attributes & LATT_HASCHILDREN) ? ", has children" : "",
472 (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
473 #endif
475 if(!mm_list_info->stream || stream == mm_list_info->stream)
476 (*mm_list_info->filter)(stream, mailbox, delimiter,
477 attributes, mm_list_info->data,
478 mm_list_info->options);
482 void
483 mm_status(MAILSTREAM *stream, char *mailbox, MAILSTATUS *status)
486 * We implement mail_status for the #move namespace by adding a wrapper
487 * routine, pine_mail_status. It may have to call the real mail_status
488 * twice for #move folders and combine the results. It sets
489 * pine_cached_status to point to a local status variable to store the
490 * intermediate results.
492 if(status){
493 if(pine_cached_status != NULL)
494 *pine_cached_status = *status;
495 else
496 mm_status_result = *status;
499 #ifdef DEBUG
500 if(status){
501 if(pine_cached_status)
502 dprint((2,
503 "mm_status: Preliminary pass for #move\n"));
505 dprint((2, "mm_status: Mailbox \"%s\"",
506 mailbox ? mailbox : "?"));
507 if(status->flags & SA_MESSAGES)
508 dprint((2, ", %lu messages", status->messages));
510 if(status->flags & SA_RECENT)
511 dprint((2, ", %lu recent", status->recent));
513 if(status->flags & SA_UNSEEN)
514 dprint((2, ", %lu unseen", status->unseen));
516 if(status->flags & SA_UIDVALIDITY)
517 dprint((2, ", %lu UID validity", status->uidvalidity));
519 if(status->flags & SA_UIDNEXT)
520 dprint((2, ", %lu next UID", status->uidnext));
522 dprint((2, "\n"));
524 #endif
528 /*----------------------------------------------------------------------
529 Write imap debugging information into log file
531 Args: strings -- the string for the debug file
533 Result: message written to the debug log file
534 ----*/
535 void
536 mm_dlog(char *string)
538 char *p, *q = NULL, save, *continued;
539 int more = 1;
541 #ifdef _WINDOWS
542 mswin_imaptelemetry(string);
543 #endif
544 #ifdef DEBUG
545 continued = "";
546 p = string;
547 #ifdef DEBUGJOURNAL
548 /* because string can be really long and we don't want to lose any of it */
549 if(p)
550 more = 1;
552 while(more){
553 if(q){
554 *q = save;
555 p = q;
556 continued = "(Continuation line) ";
559 if(strlen(p) > 63000){
560 q = p + 60000;
561 save = *q;
562 *q = '\0';
564 else
565 more = 0;
566 #endif
567 dprint(((ps_global->debug_imap >= 4 && debug < 4) ? debug : 4,
568 "IMAP DEBUG %s%s: %s\n",
569 continued ? continued : "",
570 debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress), p ? p : "?"));
571 #ifdef DEBUGJOURNAL
573 #endif
574 #endif
578 /*----------------------------------------------------------------------
579 Ignore signals when imap is running through critical code
581 Args: stream -- The stream on which critical operation is proceeding
582 ----*/
584 void
585 mm_critical(MAILSTREAM *stream)
587 stream = stream; /* For compiler complaints that this isn't used */
589 if(++critical_depth == 1){
591 #ifdef SIGHUP
592 hold_hup = signal(SIGHUP, SIG_IGN);
593 #endif
594 #ifdef SIGUSR2
595 hold_usr2 = signal(SIGUSR2, SIG_IGN);
596 #endif
597 #ifdef SIGINT
598 hold_int = signal(SIGINT, SIG_IGN);
599 #endif
600 #ifdef SIGTERM
601 hold_term = signal(SIGTERM, SIG_IGN);
602 #endif
605 dprint((9, "IMAP critical (depth now %d) on %s\n",
606 critical_depth,
607 (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
611 /*----------------------------------------------------------------------
612 Reset signals after critical imap code
613 ----*/
614 void
615 mm_nocritical(MAILSTREAM *stream)
617 stream = stream; /* For compiler complaints that this isn't used */
619 if(--critical_depth == 0){
621 #ifdef SIGHUP
622 (void)signal(SIGHUP, hold_hup);
623 #endif
624 #ifdef SIGUSR2
625 (void)signal(SIGUSR2, hold_usr2);
626 #endif
627 #ifdef SIGINT
628 (void)signal(SIGINT, hold_int);
629 #endif
630 #ifdef SIGTERM
631 (void)signal(SIGTERM, hold_term);
632 #endif
635 critical_depth = MAX(critical_depth, 0);
637 dprint((9, "Done with IMAP critical (depth now %d) on %s\n",
638 critical_depth,
639 (stream && stream->mailbox) ? stream->mailbox : "<no folder>" ));
643 void
644 mm_fatal(char *message)
646 alpine_panic(message);
650 char *
651 imap_referral(MAILSTREAM *stream, char *ref, long int code)
653 char *buf = NULL;
655 if(ref && !struncmp(ref, "imap://", 7)){
656 char *folder = NULL;
657 imapuid_t uid_val, uid;
658 int rv;
660 rv = url_imap_folder(ref, &folder, &uid, &uid_val, NULL, 1);
661 switch(code){
662 case REFAUTHFAILED :
663 case REFAUTH :
664 if((rv & URL_IMAP_IMAILBOXLIST) && (rv & URL_IMAP_ISERVERONLY))
665 buf = cpystr(folder);
667 break;
669 case REFSELECT :
670 case REFCREATE :
671 case REFDELETE :
672 case REFRENAME :
673 case REFSUBSCRIBE :
674 case REFUNSUBSCRIBE :
675 case REFSTATUS :
676 case REFCOPY :
677 case REFAPPEND :
678 if(rv & URL_IMAP_IMESSAGELIST)
679 buf = cpystr(folder);
681 break;
683 default :
684 break;
687 if(folder)
688 fs_give((void **) &folder);
691 return(buf);
695 long
696 imap_proxycopy(MAILSTREAM *stream, char *sequence, char *mailbox, long int flags)
698 SE_APP_S args;
699 long ret;
701 args.folder = mailbox;
702 args.flags = flags;
704 if(pith_opt_save_size_changed_prompt)
705 (*pith_opt_save_size_changed_prompt)(0L, SSCP_INIT);
707 ret = imap_seq_exec(stream, sequence, imap_seq_exec_append, &args);
709 if(pith_opt_save_size_changed_prompt)
710 (*pith_opt_save_size_changed_prompt)(0L, SSCP_END);
712 return(ret);
716 long
717 imap_seq_exec(MAILSTREAM *stream, char *sequence,
718 long int (*func)(MAILSTREAM *, long int, void *),
719 void *args)
721 unsigned long i,j,x;
723 while (*sequence) { /* while there is something to parse */
724 if (*sequence == '*') { /* maximum message */
725 if(!(i = stream->nmsgs)){
726 mm_log ("No messages, so no maximum message number",ERROR);
727 return(0L);
730 sequence++; /* skip past * */
732 else if (!(i = strtoul ((const char *) sequence,&sequence,10))
733 || (i > stream->nmsgs)){
734 mm_log ("Sequence invalid",ERROR);
735 return(0L);
738 switch (*sequence) { /* see what the delimiter is */
739 case ':': /* sequence range */
740 if (*++sequence == '*') { /* maximum message */
741 if (stream->nmsgs) j = stream->nmsgs;
742 else {
743 mm_log ("No messages, so no maximum message number",ERROR);
744 return NIL;
747 sequence++; /* skip past * */
749 /* parse end of range */
750 else if (!(j = strtoul ((const char *) sequence,&sequence,10)) ||
751 (j > stream->nmsgs)) {
752 mm_log ("Sequence range invalid",ERROR);
753 return NIL;
756 if (*sequence && *sequence++ != ',') {
757 mm_log ("Sequence range syntax error",ERROR);
758 return NIL;
761 if (i > j) { /* swap the range if backwards */
762 x = i; i = j; j = x;
765 while (i <= j)
766 if(!(*func)(stream, i++, args))
767 return(0L);
769 break;
770 case ',': /* single message */
771 ++sequence; /* skip the delimiter, fall into end case */
772 case '\0': /* end of sequence */
773 if(!(*func)(stream, i, args))
774 return(0L);
776 break;
777 default: /* anything else is a syntax error! */
778 mm_log ("Sequence syntax error",ERROR);
779 return NIL;
783 return T; /* successfully parsed sequence */
787 long
788 imap_seq_exec_append(MAILSTREAM *stream, long int msgno, void *args)
790 char *save_folder, *flags = NULL, date[64];
791 CONTEXT_S *cntxt = NULL;
792 int our_stream = 0;
793 long rv = 0L;
794 MAILSTREAM *save_stream;
795 SE_APP_S *sa = (SE_APP_S *) args;
796 MESSAGECACHE *mc;
797 STORE_S *so;
799 save_folder = (strucmp(sa->folder, ps_global->inbox_name) == 0)
800 ? ps_global->VAR_INBOX_PATH : sa->folder;
802 save_stream = save_msg_stream(cntxt, save_folder, &our_stream);
804 if((so = so_get(CharStar, NULL, WRITE_ACCESS)) != NULL){
805 /* store flags before the fetch so UNSEEN bit isn't flipped */
806 mc = (msgno > 0L && stream && msgno <= stream->nmsgs)
807 ? mail_elt(stream, msgno) : NULL;
809 flags = flag_string(stream, msgno, F_ANS|F_FWD|F_FLAG|F_SEEN|F_KEYWORD);
810 if(mc && mc->day)
811 mail_date(date, mc);
812 else
813 *date = '\0';
815 rv = save_fetch_append(stream, msgno, NULL,
816 save_stream, save_folder, NULL,
817 mc ? mc->rfc822_size : 0L, flags, date, so);
818 if(flags)
819 fs_give((void **) &flags);
821 if(rv < 0 || sp_expunge_count(stream)){
822 cmd_cancelled("Attached message Save");
823 rv = 0L;
825 /* else whatever broke in save_fetch_append shoulda bitched */
827 so_give(&so);
829 else{
830 dprint((1, "Can't allocate store for save: %s\n",
831 error_description(errno)));
832 q_status_message(SM_ORDER | SM_DING, 3, 4,
833 "Problem creating space for message text.");
836 if(our_stream)
837 mail_close(save_stream);
839 return(rv);
844 /*----------------------------------------------------------------------
845 Get login and password from user for IMAP login
847 Args: mb -- The mail box property struct
848 user -- Buffer to return the user name in
849 passwd -- Buffer to return the passwd in
850 trial -- The trial number or number of attempts to login
851 user is at least size NETMAXUSER
852 passwd is apparently at least MAILTMPLEN, but mrc has asked us to
853 use a max size of about 100 instead
855 Result: username and password passed back to imap
856 ----*/
857 void
858 mm_login(NETMBX *mb, char *user, char *pwd, long int trial)
860 mm_login_work(mb, user, pwd, trial, NULL, NULL);
864 /*----------------------------------------------------------------------
865 Exported method to retrieve logged in user name associated with stream
867 Args: host -- host to find associated login name with.
869 Result:
870 ----*/
871 char *
872 cached_user_name(char *host)
874 MMLOGIN_S *l;
875 STRLIST_S *h;
877 if((l = mm_login_list) && host)
879 for(h = l->hosts; h; h = h->next)
880 if(!strucmp(host, h->name))
881 return(l->user);
882 while((l = l->next) != NULL);
884 return(NULL);
889 imap_same_host(STRLIST_S *hl1, STRLIST_S *hl2)
891 STRLIST_S *lp;
893 for( ; hl1; hl1 = hl1->next)
894 for(lp = hl2; lp; lp = lp->next)
895 if(!strucmp(hl1->name, lp->name))
896 return(TRUE);
898 return(FALSE);
903 * For convenience, we use the same m_list structure (but a different
904 * instance) for storing a list of hosts we've asked the user about when
905 * SSL validation fails. If this function returns TRUE, that means we
906 * have previously asked the user about this host. Ok_novalidate == 1 means
907 * the user said yes, it was ok. Ok_novalidate == 0 means the user
908 * said no. Warned means we warned them already.
911 imap_get_ssl(MMLOGIN_S *m_list, STRLIST_S *hostlist, int *ok_novalidate, int *warned)
913 MMLOGIN_S *l;
915 for(l = m_list; l; l = l->next)
916 if(imap_same_host(l->hosts, hostlist)){
917 if(ok_novalidate)
918 *ok_novalidate = l->ok_novalidate;
920 if(warned)
921 *warned = l->warned;
923 return(TRUE);
926 return(FALSE);
931 * Just trying to guess the username the user might want to use on this
932 * host, the user will confirm.
934 char *
935 imap_get_user(MMLOGIN_S *m_list, STRLIST_S *hostlist)
937 MMLOGIN_S *l;
939 for(l = m_list; l; l = l->next)
940 if(imap_same_host(l->hosts, hostlist))
941 return(l->user);
943 return(NULL);
948 * If we have a matching hostname, username, and altflag in our cache,
949 * attempt to login with the password from the cache.
952 imap_get_passwd(MMLOGIN_S *m_list, char *passwd, char *user, STRLIST_S *hostlist, int altflag)
954 MMLOGIN_S *l;
956 dprint((9,
957 "imap_get_passwd: checking user=%s alt=%d host=%s%s%s\n",
958 user ? user : "(null)",
959 altflag,
960 hostlist->name ? hostlist->name : "",
961 (hostlist->next && hostlist->next->name) ? ", " : "",
962 (hostlist->next && hostlist->next->name) ? hostlist->next->name
963 : ""));
964 for(l = m_list; l; l = l->next)
965 if(imap_same_host(l->hosts, hostlist)
966 && *user
967 && !strcmp(user, l->user)
968 && l->altflag == altflag){
969 if(passwd){
970 strncpy(passwd, l->passwd, NETMAXPASSWD);
971 passwd[NETMAXPASSWD-1] = '\0';
973 dprint((9, "imap_get_passwd: match\n"));
974 dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n",
975 passwd ? passwd : "?"));
976 return(TRUE);
979 dprint((9, "imap_get_passwd: no match\n"));
980 return(FALSE);
985 void
986 imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
987 int altflag, int ok_novalidate, int warned)
989 STRLIST_S **listp;
990 size_t len;
992 dprint((9, "imap_set_passwd\n"));
993 for(; *l; l = &(*l)->next)
994 if(imap_same_host((*l)->hosts, hostlist)
995 && !strcmp(user, (*l)->user)
996 && altflag == (*l)->altflag){
997 if(strcmp(passwd, (*l)->passwd) ||
998 (*l)->ok_novalidate != ok_novalidate ||
999 (*l)->warned != warned)
1000 break;
1001 else
1002 return;
1005 if(!*l){
1006 *l = (MMLOGIN_S *)fs_get(sizeof(MMLOGIN_S));
1007 memset(*l, 0, sizeof(MMLOGIN_S));
1010 len = strlen(passwd);
1011 if(!(*l)->passwd || strlen((*l)->passwd) < len)
1012 (*l)->passwd = ps_get(len+1);
1014 strncpy((*l)->passwd, passwd, len+1);
1016 (*l)->altflag = altflag;
1017 (*l)->ok_novalidate = ok_novalidate;
1018 (*l)->warned = warned;
1020 if(!(*l)->user)
1021 (*l)->user = cpystr(user);
1023 dprint((9, "imap_set_passwd: user=%s altflag=%d\n",
1024 (*l)->user ? (*l)->user : "?",
1025 (*l)->altflag));
1027 for( ; hostlist; hostlist = hostlist->next){
1028 for(listp = &(*l)->hosts;
1029 *listp && strucmp((*listp)->name, hostlist->name);
1030 listp = &(*listp)->next)
1033 if(!*listp){
1034 *listp = new_strlist(hostlist->name);
1035 dprint((9, "imap_set_passwd: host=%s\n",
1036 (*listp)->name ? (*listp)->name : "?"));
1040 dprint((10, "imap_set_passwd: passwd=\"%s\"\n",
1041 passwd ? passwd : "?"));
1046 void
1047 imap_flush_passwd_cache(int dumpcache)
1049 size_t len;
1050 volatile char *p;
1052 /* equivalent of memset but can't be optimized away */
1053 len = sizeof(private_store);
1054 p = private_store;
1055 while(len-- > 0)
1056 *p++ = '\0';
1058 if(dumpcache){
1059 MMLOGIN_S *l;
1061 while((l = mm_login_list) != NULL){
1062 mm_login_list = mm_login_list->next;
1063 if(l->user)
1064 fs_give((void **) &l->user);
1066 free_strlist(&l->hosts);
1068 fs_give((void **) &l);
1071 while((l = cert_failure_list) != NULL){
1072 cert_failure_list = cert_failure_list->next;
1073 if(l->user)
1074 fs_give((void **) &l->user);
1076 free_strlist(&l->hosts);
1078 fs_give((void **) &l);
1085 * Mimics fs_get except it only works for char * (no alignment hacks), it
1086 * stores in a static array so it is easy to zero it out (that's the whole
1087 * purpose), allocations always happen at the end (no free).
1088 * If we go past array limit, we don't break, we just use free storage.
1089 * Should be awfully rare, though.
1091 char *
1092 ps_get(size_t size)
1094 static char *last = (char *) private_store;
1095 char *block = NULL;
1097 /* there is enough space */
1098 if(size <= sizeof(private_store) - (last - (char *) private_store)){
1099 block = last;
1100 last += size;
1102 else{
1103 dprint((2,
1104 "Out of password caching space in private_store\n"));
1105 dprint((2,
1106 "Using free storage instead\n"));
1107 block = fs_get(size);
1110 return(block);
1113 #ifdef PASSFILE
1114 char *
1115 passfile_name(char *pinerc, char *path, size_t len)
1117 struct stat sbuf;
1118 char *p = NULL;
1119 int i, j;
1121 if(!path || !((pinerc && pinerc[0]) || ps_global->passfile))
1122 return(NULL);
1124 if(ps_global->passfile)
1125 strncpy(path, ps_global->passfile, len-1);
1126 else{
1127 if((p = last_cmpnt(pinerc)) && *(p-1) && *(p-1) != PASSFILE[0])
1128 for(i = 0; pinerc < p && i < len; pinerc++, i++)
1129 path[i] = *pinerc;
1130 else
1131 i = 0;
1133 for(j = 0; (i < len) && (path[i] = PASSFILE[j]); i++, j++)
1138 path[len-1] = '\0';
1140 dprint((9, "Looking for passfile \"%s\"\n",
1141 path ? path : "?"));
1143 #if defined(DOS) || defined(OS2)
1144 return((our_stat(path, &sbuf) == 0
1145 && ((sbuf.st_mode & S_IFMT) == S_IFREG))
1146 ? path : NULL);
1147 #else
1148 /* First, make sure it's ours and not sym-linked */
1149 if(our_lstat(path, &sbuf) == 0
1150 && ((sbuf.st_mode & S_IFMT) == S_IFREG)
1151 && sbuf.st_uid == getuid()){
1152 /* if too liberal permissions, fix them */
1153 if((sbuf.st_mode & 077) != 0)
1154 if(our_chmod(path, sbuf.st_mode & ~077) != 0)
1155 return(NULL);
1157 return(path);
1159 else
1160 return(NULL);
1161 #endif
1163 #endif /* PASSFILE */