* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / pith / imap.c
blob4c1d7f396129d96c019d6ed2197fae2658407332
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
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 #include "../pith/headers.h"
16 #include "../pith/imap.h"
17 #include "../pith/msgno.h"
18 #include "../pith/state.h"
19 #include "../pith/flag.h"
20 #include "../pith/pineelt.h"
21 #include "../pith/status.h"
22 #include "../pith/conftype.h"
23 #include "../pith/context.h"
24 #include "../pith/thread.h"
25 #include "../pith/mailview.h"
26 #include "../pith/mailpart.h"
27 #include "../pith/mailindx.h"
28 #include "../pith/mailcmd.h"
29 #include "../pith/save.h"
30 #include "../pith/util.h"
31 #include "../pith/stream.h"
32 #include "../pith/newmail.h"
33 #include "../pith/icache.h"
34 #include "../pith/options.h"
36 #ifdef _WINDOWS
37 #include "../pico/osdep/mswin.h"
38 #endif
42 * Internal prototypes
44 long imap_seq_exec(MAILSTREAM *, char *, long (*)(MAILSTREAM *, long, void *), void *);
45 long imap_seq_exec_append(MAILSTREAM *, long, void *);
46 char *ps_get(size_t);
50 * Exported globals setup by searching functions to tell mm_searched
51 * where to put message numbers that matched the search criteria,
52 * and to allow mm_searched to return number of matches.
54 MAILSTREAM *mm_search_stream;
55 long mm_search_count = 0L;
56 MAILSTATUS mm_status_result;
58 MM_LIST_S *mm_list_info;
60 MMLOGIN_S *mm_login_list = NULL;
61 MMLOGIN_S *cert_failure_list = NULL;
64 * Instead of storing cached passwords in free storage, store them in this
65 * private space. This makes it easier and more reliable when we want
66 * to zero this space out. We only store passwords here (char *) so we
67 * don't need to worry about alignment.
69 static volatile char private_store[1024];
71 static int critical_depth = 0;
73 /* hook to hang callback on "current" message expunge */
74 void (*pith_opt_current_expunged)(long unsigned int);
76 #ifdef SIGINT
77 RETSIGTYPE (*hold_int)(int);
78 #endif
80 #ifdef SIGTERM
81 RETSIGTYPE (*hold_term)(int);
82 #endif
84 #ifdef SIGHUP
85 RETSIGTYPE (*hold_hup)(int);
86 #endif
88 #ifdef SIGUSR2
89 RETSIGTYPE (*hold_usr2)(int);
90 #endif
94 /*----------------------------------------------------------------------
95 receive notification that search found something
97 Input: mail stream and message number of located item
99 Result: nothing, not used by pine
100 ----*/
101 void
102 mm_searched(MAILSTREAM *stream, long unsigned int rawno)
104 MESSAGECACHE *mc;
106 if(rawno > 0L && stream && rawno <= stream->nmsgs
107 && (mc = mail_elt(stream, rawno))){
108 mc->searched = 1;
109 if(stream == mm_search_stream)
110 mm_search_count++;
115 /*----------------------------------------------------------------------
116 receive notification of new mail from imap daemon
118 Args: stream -- The stream the message count report is for.
119 number -- The number of messages now in folder.
121 Result: Sets value in pine state indicating new mailbox size
123 Called when the number of messages in the mailbox goes up. This
124 may also be called as a result of an expunge. It increments the
125 new_mail_count based on a the difference between the current idea of
126 the maximum number of messages and what mm_exists claims. The new mail
127 notification is done in newmail.c
129 Only worry about the cases when the number grows, as mm_expunged
130 handles shrinkage...
132 ----*/
133 void
134 mm_exists(MAILSTREAM *stream, long unsigned int number)
136 long new_this_call, n;
137 int exbits = 0, lflags = 0;
138 MSGNO_S *msgmap;
140 #ifdef DEBUG
141 if(ps_global->debug_imap > 1 || ps_global->debugmem)
142 dprint((3, "=== mm_exists(%lu,%s) called ===\n", number,
143 !stream ? "(no stream)" : !stream->mailbox ? "(null)" : stream->mailbox));
144 #endif
146 msgmap = sp_msgmap(stream);
147 if(!msgmap)
148 return;
150 if(mn_get_nmsgs(msgmap) != (long) number){
151 sp_set_mail_box_changed(stream, 1);
152 /* titlebar will be affected */
153 if(ps_global->mail_stream == stream)
154 ps_global->mangled_header = 1;
157 if(mn_get_nmsgs(msgmap) < (long) number){
158 new_this_call = (long) number - mn_get_nmsgs(msgmap);
159 sp_set_new_mail_count(stream,
160 sp_new_mail_count(stream) + new_this_call);
161 sp_set_recent_since_visited(stream,
162 sp_recent_since_visited(stream) + new_this_call);
164 mn_add_raw(msgmap, new_this_call);
167 * Set local "recent" and "hidden" bits...
169 for(n = 0; n < new_this_call; n++, number--){
170 if(msgno_exceptions(stream, number, "0", &exbits, FALSE))
171 exbits |= MSG_EX_RECENT;
172 else
173 exbits = MSG_EX_RECENT;
175 msgno_exceptions(stream, number, "0", &exbits, TRUE);
177 if(SORT_IS_THREADED(msgmap))
178 lflags |= MN_USOR;
181 * If we're zoomed, then hide this message too since
182 * it couldn't have possibly been selected yet...
184 lflags |= (any_lflagged(msgmap, MN_HIDE) ? MN_HIDE : 0);
185 if(lflags)
186 set_lflag(stream, msgmap, mn_get_total(msgmap) - n, lflags, 1);
192 /*----------------------------------------------------------------------
193 Receive notification from IMAP that a single message has been expunged
195 Args: stream -- The stream/folder the message is expunged from
196 rawno -- The raw message number that was expunged
198 mm_expunged is always called on an expunge. Simply remove all
199 reference to the expunged message, shifting internal mappings as
200 necessary.
201 ----*/
202 void
203 mm_expunged(MAILSTREAM *stream, long unsigned int rawno)
205 MESSAGECACHE *mc = NULL;
206 long i;
207 int is_current = 0;
208 MSGNO_S *msgmap;
210 #ifdef DEBUG
211 if(ps_global->debug_imap > 1 || ps_global->debugmem)
212 dprint((3, "mm_expunged(%s,%lu)\n",
213 stream
214 ? (stream->mailbox
215 ? stream->mailbox
216 : "(no stream)")
217 : "(null)", rawno));
218 #endif
220 msgmap = sp_msgmap(stream);
221 if(!msgmap)
222 return;
224 if(ps_global->mail_stream == stream)
225 is_current++;
227 if((i = mn_raw2m(msgmap, (long) rawno)) != 0L){
228 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));
230 sp_set_mail_box_changed(stream, 1);
231 sp_set_expunge_count(stream, sp_expunge_count(stream) + 1);
233 if(is_current){
234 reset_check_point(stream);
235 ps_global->mangled_header = 1;
237 /* flush invalid cache entries */
238 while(i <= mn_get_total(msgmap))
239 clear_index_cache_ent(stream, i++, 0);
241 /* let app know what happened */
242 if(pith_opt_current_expunged)
243 (*pith_opt_current_expunged)(rawno);
246 else{
247 dprint((7,
248 "mm_expunged: rawno=%lu was excluded, flagged_exld was %d\n",
249 rawno, msgmap->flagged_exld));
250 dprint((7, " nmsgs=%ld max_msgno=%ld\n",
251 mn_get_nmsgs(msgmap), mn_get_total(msgmap)));
252 if(rawno > 0L && rawno <= stream->nmsgs)
253 mc = mail_elt(stream, rawno);
255 if(!mc){
256 dprint((7, " cannot get mail_elt(%lu)\n",
257 rawno));
259 else if(!mc->sparep){
260 dprint((7, " mail_elt(%lu)->sparep is NULL\n",
261 rawno));
263 else{
264 dprint((7, " mail_elt(%lu)->sparep->excluded=%d\n",
265 rawno, (int) (((PINELT_S *) mc->sparep)->excluded)));
269 if(SORT_IS_THREADED(msgmap)
270 && (SEP_THRDINDX()
271 || ps_global->thread_disp_style != THREAD_NONE)){
272 long cur;
275 * When we're sorting with a threaded method an expunged
276 * message may cause the rest of the sort to be wrong. This
277 * isn't so bad if we're just looking at the index. However,
278 * it also causes the thread tree (PINETHRD_S) to become
279 * invalid, so if we're using a threading view we need to
280 * sort in order to fix the tree and to protect fetch_thread().
282 sp_set_need_to_rethread(stream, 1);
285 * If we expunged the current message which was a member of the
286 * viewed thread, and the adjustment to current will take us
287 * out of that thread, fix it if we can, by backing current up
288 * into the thread. We'd like to just check after mn_flush_raw
289 * below but the problem is that the elts won't change until
290 * after we return from mm_expunged. So we have to manually
291 * check the other messages for CHID2 flags instead of thinking
292 * that we can expunge the current message and then check. It won't
293 * work because the elt will still refer to the expunged message.
295 if(sp_viewing_a_thread(stream)
296 && get_lflag(stream, NULL, rawno, MN_CHID2)
297 && mn_total_cur(msgmap) == 1
298 && mn_is_cur(msgmap, mn_raw2m(msgmap, (long) rawno))
299 && (cur = mn_get_cur(msgmap)) > 1L
300 && cur < mn_get_total(msgmap)
301 && !get_lflag(stream, msgmap, cur + 1L, MN_CHID2)
302 && get_lflag(stream, msgmap, cur - 1L, MN_CHID2))
303 mn_set_cur(msgmap, cur - 1L);
307 * Keep on top of our special flag counts.
309 * NOTE: This is allowed since mail_expunged releases
310 * data for this message after the callback.
312 if(rawno > 0L && rawno <= stream->nmsgs && (mc = mail_elt(stream, rawno))){
313 PINELT_S *pelt = (PINELT_S *) mc->sparep;
315 if(pelt){
316 if(pelt->hidden)
317 msgmap->flagged_hid--;
319 if(pelt->excluded)
320 msgmap->flagged_exld--;
322 if(pelt->selected)
323 msgmap->flagged_tmp--;
325 if(pelt->colhid)
326 msgmap->flagged_chid--;
328 if(pelt->colhid2)
329 msgmap->flagged_chid2--;
331 if(pelt->collapsed)
332 msgmap->flagged_coll--;
334 if(pelt->tmp)
335 msgmap->flagged_stmp--;
337 if(pelt->unsorted)
338 msgmap->flagged_usor--;
340 if(pelt->searched)
341 msgmap->flagged_srch--;
343 if(pelt->hidden || pelt->colhid)
344 msgmap->flagged_invisible--;
346 free_pine_elt(&mc->sparep);
351 * if it's in the sort array, flush it, otherwise
352 * decrement raw sequence numbers greater than "rawno"
354 mn_flush_raw(msgmap, (long) rawno);
358 void
359 mm_flags(MAILSTREAM *stream, long unsigned int rawno)
362 * The idea here is to clean up any data pine might have cached
363 * that has anything to do with the indicated message number.
365 if(stream == ps_global->mail_stream){
366 long msgno, t;
367 PINETHRD_S *thrd;
369 if(scores_are_used(SCOREUSE_GET) & SCOREUSE_STATEDEP)
370 clear_msg_score(stream, rawno);
372 msgno = mn_raw2m(sp_msgmap(stream), (long) rawno);
374 /* if in thread index */
375 if(THRD_INDX()){
376 if((thrd = fetch_thread(stream, rawno))
377 && thrd->top
378 && (thrd = fetch_thread(stream, thrd->top))
379 && thrd->rawno
380 && (t = mn_raw2m(sp_msgmap(stream), thrd->rawno)))
381 clear_index_cache_ent(stream, t, 0);
383 else if(THREADING()){
384 if(msgno > 0L)
385 clear_index_cache_ent(stream, msgno, 0);
388 * If a parent is collapsed, clear that parent's
389 * index cache entry.
391 if((thrd = fetch_thread(stream, rawno)) && thrd->parent){
392 thrd = fetch_thread(stream, thrd->parent);
393 while(thrd){
394 if(get_lflag(stream, NULL, thrd->rawno, MN_COLL)
395 && (t = mn_raw2m(sp_msgmap(stream), (long) thrd->rawno)))
396 clear_index_cache_ent(stream, t, 0);
398 if(thrd->parent)
399 thrd = fetch_thread(stream, thrd->parent);
400 else
401 thrd = NULL;
405 else if(msgno > 0L)
406 clear_index_cache_ent(stream, msgno, 0);
408 if(msgno && mn_is_cur(sp_msgmap(stream), msgno))
409 ps_global->mangled_header = 1;
413 * We count up flag changes here. The
414 * dont_count_flagchanges variable tries to prevent us from
415 * counting when we're just fetching flags.
417 if(!(ps_global->dont_count_flagchanges
418 && stream == ps_global->mail_stream)){
419 int exbits;
421 check_point_change(stream);
423 /* we also note flag changes for filtering purposes */
424 if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
425 exbits |= MSG_EX_STATECHG;
426 else
427 exbits = MSG_EX_STATECHG;
429 msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
434 void
435 mm_list(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
437 #ifdef DEBUG
438 if(ps_global->debug_imap > 2 || ps_global->debugmem)
439 dprint((5, "mm_list \"%s\": delim: '%c', %s%s%s%s%s%s\n",
440 mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
441 (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
442 (attributes & LATT_NOSELECT) ? ", no select" : "",
443 (attributes & LATT_MARKED) ? ", marked" : "",
444 (attributes & LATT_UNMARKED) ? ", unmarked" : "",
445 (attributes & LATT_HASCHILDREN) ? ", has children" : "",
446 (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
447 #endif
449 if(!mm_list_info || !mm_list_info->filter) return;
451 if(!mm_list_info->stream || stream == mm_list_info->stream)
452 (*mm_list_info->filter)(stream, mailbox, delimiter,
453 attributes, mm_list_info->data,
454 mm_list_info->options);
458 void
459 mm_lsub(MAILSTREAM *stream, int delimiter, char *mailbox, long int attributes)
461 #ifdef DEBUG
462 if(ps_global->debug_imap > 2 || ps_global->debugmem)
463 dprint((5, "LSUB \"%s\": delim: '%c', %s%s%s%s%s%s\n",
464 mailbox ? mailbox : "?", delimiter ? delimiter : 'X',
465 (attributes & LATT_NOINFERIORS) ? ", no inferiors" : "",
466 (attributes & LATT_NOSELECT) ? ", no select" : "",
467 (attributes & LATT_MARKED) ? ", marked" : "",
468 (attributes & LATT_UNMARKED) ? ", unmarked" : "",
469 (attributes & LATT_HASCHILDREN) ? ", has children" : "",
470 (attributes & LATT_HASNOCHILDREN) ? ", has no children" : ""));
471 #endif
473 if(!mm_list_info || !mm_list_info->filter) return;
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);
863 void
864 mm_login_method(NETMBX *mb, char *user, void *login, long int trial, char *method)
866 mm_login_method_work(mb, user, login, trial, method, NULL, NULL);
870 /*----------------------------------------------------------------------
871 Exported method to retrieve logged in user name associated with stream
873 Args: host -- host to find associated login name with.
875 Result:
876 ----*/
877 char *
878 cached_user_name(char *host)
880 MMLOGIN_S *l;
881 STRLIST_S *h;
883 if((l = mm_login_list) && host)
885 for(h = l->hosts; h; h = h->next)
886 if(!strucmp(host, h->name))
887 return(l->user);
888 while((l = l->next) != NULL);
890 return(NULL);
895 imap_same_host(STRLIST_S *hl1, STRLIST_S *hl2)
897 return imap_same_host_auth(hl1, hl2, NULL);
900 /* An explanation about this function. The way this is used in the
901 * new code for XOAUTH2, makes it so that when we are checking if
902 * the hosts and orighosts are the same, we are given the mb->host
903 * and mb->orighost pointers, and we cannot transform them in any
904 * way to be sure that increasing their size will not overflow the
905 * fixed width buffer that contain them, so we cannot change that.
906 * For purposes of this function, these values come in the hl2 variable.
907 * However, for purposes of this function, the values in hl1 are the ones
908 * that come straight from the cache, and here no transformation is made,
909 * that is, we save them as we read them, so when we compare the values
910 * read from the cache, with those that we want to save, we need to make
911 * sure we "shift" the hl1 variable, but not the hl2 variable.
914 imap_same_host_auth(STRLIST_S *hl1, STRLIST_S *hl2, char *authtype)
916 STRLIST_S *lp;
917 size_t len, offset;
919 len = authtype ? strlen(authtype) : 0;
920 offset = authtype ? 1 : 0;
921 for( ; hl1; hl1 = hl1->next)
922 for(lp = hl2; lp; lp = lp->next)
923 if((len == 0 || (!struncmp(hl1->name, authtype, len)
924 && hl1->name[len] == PWDAUTHSEP))
925 && !strucmp(hl1->name + len + offset, lp->name))
926 return(TRUE);
928 return(FALSE);
933 * For convenience, we use the same m_list structure (but a different
934 * instance) for storing a list of hosts we've asked the user about when
935 * SSL validation fails. If this function returns TRUE, that means we
936 * have previously asked the user about this host. Ok_novalidate == 1 means
937 * the user said yes, it was ok. Ok_novalidate == 0 means the user
938 * said no. Warned means we warned them already.
941 imap_get_ssl(MMLOGIN_S *m_list, STRLIST_S *hostlist, int *ok_novalidate, int *warned)
943 MMLOGIN_S *l;
945 for(l = m_list; l; l = l->next)
946 if(imap_same_host(l->hosts, hostlist)){
947 if(ok_novalidate)
948 *ok_novalidate = l->ok_novalidate;
950 if(warned)
951 *warned = l->warned;
953 return(TRUE);
956 return(FALSE);
961 * Just trying to guess the username the user might want to use on this
962 * host, the user will confirm.
964 char *
965 imap_get_user(MMLOGIN_S *m_list, STRLIST_S *hostlist)
967 MMLOGIN_S *l;
969 for(l = m_list; l; l = l->next)
970 if(imap_same_host(l->hosts, hostlist))
971 return(l->user);
973 return(NULL);
978 * If we have a matching hostname, username, and altflag in our cache,
979 * attempt to login with the password from the cache.
982 imap_get_passwd(MMLOGIN_S *m_list, char **passwd, char *user, STRLIST_S *hostlist, int altflag)
984 return imap_get_passwd_auth(m_list, passwd, user, hostlist, altflag, NULL);
988 imap_get_passwd_auth(MMLOGIN_S *m_list, char **passwd, char *user, STRLIST_S *hostlist, int altflag, char *authtype)
990 MMLOGIN_S *l;
991 int len, offset;
993 dprint((9,
994 "imap_get_passwd: checking user=%s alt=%d host=%s%s%s\n",
995 user ? user : "(null)",
996 altflag,
997 hostlist->name ? hostlist->name : "",
998 (hostlist->next && hostlist->next->name) ? ", " : "",
999 (hostlist->next && hostlist->next->name) ? hostlist->next->name
1000 : ""));
1001 len = authtype ? strlen(authtype) : 0;
1002 offset = authtype ? 1 : 0;
1003 for(l = m_list; l; l = l->next)
1004 if(imap_same_host_auth(l->hosts, hostlist, authtype)
1005 && *user
1006 && (len == 0 || (!struncmp(l->user, authtype, len)
1007 && l->user[len] == PWDAUTHSEP))
1008 && !strcmp(user, l->user + len + offset)
1009 && l->altflag == altflag){
1010 if(passwd)
1011 *passwd = cpystr(l->passwd + len + offset);
1012 dprint((9, "imap_get_passwd: match\n"));
1013 dprint((10, "imap_get_passwd: trying passwd=\"%s\"\n",
1014 passwd && *passwd ? *passwd : "?"));
1015 return(TRUE);
1018 dprint((9, "imap_get_passwd: no match\n"));
1019 return(FALSE);
1024 void
1025 imap_set_passwd(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
1026 int altflag, int ok_novalidate, int warned)
1028 imap_set_passwd_auth(l, passwd, user, hostlist, altflag, ok_novalidate,
1029 warned, NULL);
1032 void
1033 imap_set_passwd_auth(MMLOGIN_S **l, char *passwd, char *user, STRLIST_S *hostlist,
1034 int altflag, int ok_novalidate, int warned, char *authtype)
1036 STRLIST_S **listp;
1037 size_t len;
1038 size_t authlen, offset;
1040 authlen = authtype ? strlen(authtype) : 0;
1041 offset = authtype ? 1 : 0;
1042 dprint((9, "imap_set_passwd\n"));
1043 for(; *l; l = &(*l)->next)
1044 if((authlen == 0 || (!struncmp((*l)->user, authtype, authlen)
1045 && (*l)->user[authlen] == PWDAUTHSEP))
1046 && !strcmp(user, (*l)->user + authlen + offset)
1047 && imap_same_host_auth((*l)->hosts, hostlist, authtype)
1048 && altflag == (*l)->altflag){
1049 if(strcmp(passwd, (*l)->passwd + authlen + offset) ||
1050 (*l)->ok_novalidate != ok_novalidate ||
1051 (*l)->warned != warned)
1052 break;
1053 else
1054 return;
1057 if(!*l){
1058 *l = (MMLOGIN_S *)fs_get(sizeof(MMLOGIN_S));
1059 memset(*l, 0, sizeof(MMLOGIN_S));
1062 len = strlen(passwd);
1063 (*l)->passwd = ps_get(len + authlen + offset + 1);
1065 if(authtype)
1066 sprintf((*l)->passwd, "%s%c%s", authtype, PWDAUTHSEP, passwd);
1067 else
1068 strncpy((*l)->passwd, passwd, len+1);
1070 (*l)->altflag = altflag; (*l)->ok_novalidate = ok_novalidate; (*l)->warned = warned;
1072 if(!(*l)->user){
1073 if(authlen > 0){
1074 (*l)->user = fs_get(strlen(user) + authlen + offset + 1);
1075 sprintf((*l)->user, "%s%c%s", authtype, PWDAUTHSEP, user);
1077 else
1078 (*l)->user = cpystr(user);
1081 dprint((9, "imap_set_passwd: user=%s altflag=%d\n",
1082 (*l)->user ? (*l)->user : "?",
1083 (*l)->altflag));
1085 for( ; hostlist; hostlist = hostlist->next){
1086 for(listp = &(*l)->hosts;
1087 *listp && strucmp((*listp)->name, hostlist->name);
1088 listp = &(*listp)->next)
1091 if(!*listp){
1092 *listp = new_strlist_auth(hostlist->name, authtype, PWDAUTHSEP);
1093 dprint((9, "imap_set_passwd: host=%s\n",
1094 (*listp)->name ? (*listp)->name : "?"));
1098 dprint((10, "imap_set_passwd: passwd=\"%s\"\n",
1099 passwd ? passwd : "?"));
1104 void
1105 imap_flush_passwd_cache(int dumpcache)
1107 size_t len;
1108 volatile char *p;
1110 /* equivalent of memset but can't be optimized away */
1111 len = sizeof(private_store);
1112 p = private_store;
1113 while(len-- > 0)
1114 *p++ = '\0';
1116 if(dumpcache){
1117 MMLOGIN_S *l;
1119 while((l = mm_login_list) != NULL){
1120 mm_login_list = mm_login_list->next;
1121 if(l->user)
1122 fs_give((void **) &l->user);
1124 if(!(l->passwd >= (char *) private_store
1125 && l->passwd <= p))
1126 fs_give((void **) &l->passwd);
1128 free_strlist(&l->hosts);
1130 fs_give((void **) &l);
1133 while((l = cert_failure_list) != NULL){
1134 cert_failure_list = cert_failure_list->next;
1135 if(l->user)
1136 fs_give((void **) &l->user);
1137 if(!(l->passwd >= (char *) private_store
1138 && l->passwd <= p))
1139 fs_give((void **) &l->passwd);
1141 free_strlist(&l->hosts);
1143 fs_give((void **) &l);
1148 void
1149 imap_delete_passwd(MMLOGIN_S **m_list, char *user, STRLIST_S *hostlist, int altflag)
1151 if(m_list == NULL || *m_list == NULL) return;
1152 imap_delete_passwd_auth(m_list, user, hostlist, altflag,
1153 strchr((*m_list)->user, PWDAUTHSEP) != NULL ? OA2NAME : NULL);
1156 void
1157 imap_delete_passwd_auth(MMLOGIN_S **m_list, char *user,
1158 STRLIST_S *hostlist, int altflag, char *authtype)
1160 MMLOGIN_S *l, *p, *q;
1161 int len, offset;
1163 if(m_list == NULL || *m_list == NULL) return;
1165 dprint((9, "imap_delete_password: user=%s, host=%s, authtype=%s.",
1166 user ? user : "no user!?",
1167 hostlist && hostlist->name ? hostlist->name : "unknown host",
1168 authtype ? authtype : "password authentication"));
1170 len = authtype ? strlen(authtype) : 0;
1171 offset = authtype ? 1 : 0;
1172 for(p = *m_list; p; p = p->next){
1173 if(imap_same_host_auth(p->hosts, hostlist, authtype)
1174 && *user
1175 && (len == 0 || (!struncmp(p->user, authtype, len)
1176 && p->user[len] == PWDAUTHSEP))
1177 && !strcmp(user, p->user + len + offset)
1178 && p->altflag == altflag)
1179 break;
1182 dprint((9, "imap_delete_password: %s",
1183 p ? "found a match!" : "did not find a match!"));
1185 if(!p) return;
1187 /* relink *mlist */
1188 if(p == *m_list)
1189 q = (*m_list)->next;
1190 else{
1191 q = *m_list;
1192 for(l = *m_list; l && l->next != p; l = l->next);
1193 l->next = p->next;
1195 /* so now p is out of the list. Free it */
1196 p->next = NULL;
1197 if(p->user) fs_give((void **) &p->user);
1199 if(!(p->passwd >= (char *) private_store
1200 && p->passwd <= (char *) private_store + sizeof(private_store)))
1201 fs_give((void **) &p->passwd);
1203 free_strlist(&p->hosts);
1204 fs_give((void **) &p);
1205 *m_list = q;
1206 dprint((9, "imap_delete_password: done with deletion."));
1210 * Mimics fs_get except it only works for char * (no alignment hacks), it
1211 * stores in a static array so it is easy to zero it out (that's the whole
1212 * purpose), allocations always happen at the end (no free).
1213 * If we go past array limit, we don't break, we just use free storage.
1214 * Should be awfully rare, though.
1216 char *
1217 ps_get(size_t size)
1219 static char *last = (char *) private_store;
1220 char *block = NULL;
1222 /* there is enough space */
1223 if(size <= sizeof(private_store) - (last - (char *) private_store)){
1224 block = last;
1225 last += size;
1227 else{
1228 dprint((2,
1229 "Out of password caching space in private_store\n"));
1230 dprint((2,
1231 "Using free storage instead\n"));
1232 block = fs_get(size);
1235 return(block);
1238 #ifdef PASSFILE
1239 char *
1240 passfile_name(char *pinerc, char *path, size_t len)
1242 struct stat sbuf;
1243 char *p = NULL;
1244 int i, j;
1246 if(!path || !((pinerc && pinerc[0]) || ps_global->passfile))
1247 return(NULL);
1249 if(ps_global->passfile)
1250 strncpy(path, ps_global->passfile, len-1);
1251 else{
1252 if((p = last_cmpnt(pinerc)) && *(p-1) && *(p-1) != PASSFILE[0])
1253 for(i = 0; pinerc < p && i < len; pinerc++, i++)
1254 path[i] = *pinerc;
1255 else
1256 i = 0;
1258 for(j = 0; (i < len) && (path[i] = PASSFILE[j]); i++, j++)
1263 path[len-1] = '\0';
1265 dprint((9, "Looking for passfile \"%s\"\n",
1266 path ? path : "?"));
1268 #if defined(DOS) || defined(OS2)
1269 return((our_stat(path, &sbuf) == 0
1270 && ((sbuf.st_mode & S_IFMT) == S_IFREG))
1271 ? path : NULL);
1272 #else
1273 /* First, make sure it's ours and not sym-linked */
1274 if(our_lstat(path, &sbuf) == 0
1275 && ((sbuf.st_mode & S_IFMT) == S_IFREG)
1276 && sbuf.st_uid == getuid()){
1277 /* if too liberal permissions, fix them */
1278 if((sbuf.st_mode & 077) != 0)
1279 if(our_chmod(path, sbuf.st_mode & ~077) != 0)
1280 return(NULL);
1282 return(path);
1284 else
1285 return(NULL);
1286 #endif
1288 #endif /* PASSFILE */