#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: stream.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2013-2021 Eduardo Chappa * Copyright 2006-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ /*====================================================================== stream.c Implements the Pine mail stream management routines and c-client wrapper functions ====*/ #include "../pith/headers.h" #include "../pith/stream.h" #include "../pith/state.h" #include "../pith/conf.h" #include "../pith/flag.h" #include "../pith/msgno.h" #include "../pith/adrbklib.h" #include "../pith/status.h" #include "../pith/newmail.h" #include "../pith/detach.h" #include "../pith/folder.h" #include "../pith/mailcmd.h" #include "../pith/util.h" #include "../pith/news.h" #include "../pith/sequence.h" #include "../pith/options.h" #include "../pith/mimedesc.h" void (*pith_opt_closing_stream)(MAILSTREAM *); /* * Internal prototypes */ void reset_stream_view_state(MAILSTREAM *); void carefully_reset_sp_flags(MAILSTREAM *, unsigned long); char *partial_text_gets(readfn_t, void *, unsigned long, GETS_DATA *); void mail_list_internal(MAILSTREAM *, char *, char *); int recent_activity(MAILSTREAM *); int hibit_in_searchpgm(SEARCHPGM *); int hibit_in_strlist(STRINGLIST *); int hibit_in_header(SEARCHHEADER *); int hibit_in_sizedtext(SIZEDTEXT *); int sp_nusepool_notperm(void); int sp_add(MAILSTREAM *, int); void sp_delete(MAILSTREAM *); void sp_free(PER_STREAM_S **); static FETCH_READC_S *g_pft_desc; MAILSTATUS *pine_cached_status; /* implement status for #move folder */ /* * Pine wrapper around mail_open. If we have the PREFER_ALT_AUTH flag turned * on, we need to set the TRYALT flag before trying the open. * This routine manages the stream pool, too. It tries to re-use existing * streams instead of opening new ones, or maybe it will leave one open and * use a new one if that seems to make more sense. Pine_mail_close leaves * streams open so that they may be re-used. Each pine_mail_open should have * a matching pine_mail_close (or possible pine_mail_actually_close) somewhere * that goes with it. * * Args: * stream -- A possible stream for recycling. This isn't usually the * way recycling happens. Usually it is automatic. * mailbox -- The mailbox to be opened. * openflags -- Flags passed here to modify the behavior. * retflags -- Flags returned from here. SP_MATCH will be lit if that is * what happened. If SP_MATCH is lit then SP_LOCKED may also * be lit if the matched stream was already locked when * we got here. */ MAILSTREAM * pine_mail_open(MAILSTREAM *stream, char *mailbox, long int openflags, long int *retflags) { MAILSTREAM *retstream = NULL; DRIVER *d; int permlocked = 0, is_inbox = 0, usepool = 0, tempuse = 0, uf = 0; unsigned long flags; char **lock_these, *target = NULL; static unsigned long streamcounter = 0; dprint((7, "pine_mail_open: opening \"%s\"%s openflags=0x%x %s%s%s%s%s%s%s%s%s (%s)\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)", openflags, openflags & OP_HALFOPEN ? " OP_HALFOPEN" : "", openflags & OP_READONLY ? " OP_READONLY" : "", openflags & OP_SILENT ? " OP_SILENT" : "", openflags & OP_DEBUG ? " OP_DEBUG" : "", openflags & SP_PERMLOCKED ? " SP_PERMLOCKED" : "", openflags & SP_INBOX ? " SP_INBOX" : "", openflags & SP_USERFLDR ? " SP_USERFLDR" : "", openflags & SP_USEPOOL ? " SP_USEPOOL" : "", openflags & SP_TEMPUSE ? " SP_TEMPUSE" : "", debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); if(retflags) *retflags = 0L; if(ps_global->user_says_cancel){ dprint((7, "pine_mail_open: cancelled by user\n")); return(retstream); } is_inbox = openflags & SP_INBOX; uf = openflags & SP_USERFLDR; /* inbox is still special, assume that we want to permlock it */ permlocked = (is_inbox || openflags & SP_PERMLOCKED) ? 1 : 0; /* check to see if user wants this folder permlocked */ for(lock_these = ps_global->VAR_PERMLOCKED; uf && !permlocked && lock_these && *lock_these; lock_these++){ char *p = NULL, *dummy = NULL, *lt, *lname, *mname; char tmp1[MAILTMPLEN], tmp2[MAILTMPLEN]; /* there isn't really a pair, it just dequotes for us */ get_pair(*lock_these, &dummy, &p, 0, 0); /* * Check to see if this is an incoming nickname and replace it * with the full name. */ if(!(p && ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), 0)))) lt = p; if(dummy) fs_give((void **) &dummy); if(lt && mailbox && (same_remote_mailboxes(mailbox, lt) || (!IS_REMOTE(mailbox) && (lname=mailboxfile(tmp1, lt)) && (mname=mailboxfile(tmp2, mailbox)) && !strcmp(lname, mname)))) permlocked++; if(p) fs_give((void **) &p); } /* * Only cache if remote, not nntp, not pop, and caller asked us to. * It might make sense to do some caching for nntp and pop, as well, but * we aren't doing it right now. For example, an nntp stream open to * one group could be reused for another group. An open pop stream could * be used for mail_copy_full. * * An implication of doing only imap here is that sp_stream_get will only * be concerned with imap streams. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ usepool = openflags & SP_USEPOOL; tempuse = openflags & SP_TEMPUSE; } else{ if(IS_REMOTE(mailbox)){ dprint((9, "pine_mail_open: not cacheable: %s\n", !d ? "no driver?" : d->name ? d->name : "?" )); } else{ if(permlocked || (openflags & OP_READONLY)){ /* * This is a strange case. We want to allow stay-open local * folders, but they don't fit into the rest of the framework * well. So we'll look for it being already open in this case * and special-case it (the already_open_stream() case * below). */ dprint((9, "pine_mail_open: not cacheable: not remote, but check for local stream\n")); } else{ dprint((9, "pine_mail_open: not cacheable: not remote\n")); } } } /* If driver doesn't support halfopen, just open it. */ if(d && (openflags & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)){ openflags &= ~OP_HALFOPEN; dprint((9, "pine_mail_open: turning off OP_HALFOPEN flag\n")); } /* * Some of the flags are pine's, the rest are meant for mail_open. * We've noted the pine flags, now remove them before we call mail_open. */ openflags &= ~(SP_USEPOOL | SP_TEMPUSE | SP_INBOX | SP_PERMLOCKED | SP_USERFLDR); #ifdef DEBUG if(ps_global->debug_imap > 3 || ps_global->debugmem) openflags |= OP_DEBUG; #endif if(F_ON(F_PREFER_ALT_AUTH, ps_global)){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")) openflags |= OP_TRYALT; } if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)){ char source[MAILTMPLEN]; if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ DRIVER *d; if((d = mail_valid(NIL, source, (char *) NIL)) && (!strcmp(d->name, "news") || !strcmp(d->name, "nntp"))) openflags |= OP_MULNEWSRC; } else if((d = mail_valid(NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "nntp")) openflags |= OP_MULNEWSRC; } /* * One of the problems is that the new-style stream caching (the * sp_stream_get stuff) may conflict with some of the old-style caching * (the passed in argument stream) that is still in the code. We should * probably eliminate the old-style caching, but some of it is still useful, * especially if it deals with something other than IMAP. We want to prevent * mistakes caused by conflicts between the two styles. In particular, we * don't want to have a new-style cached stream re-opened because of the * old-style caching code. This can happen if a stream is passed in that * is not usable, and then a new stream is opened because the passed in * stream causes us to bypass the new caching code. Play it safe. If it * is an IMAP stream, just close it. This should leave it in the new-style * cache anyway, causing no loss. Maybe not if the cache wasn't large * enough to have it in there in the first place, in which case we get * a possibly unnecessary close and open. If it isn't IMAP we still have * to worry about it because it will cause us to bypass the caching code. * So if the stream isn't IMAP but the mailbox we're opening is, close it. * The immediate alternative would be to try to emulate the code in * mail_open that checks whether it is re-usable or not, but that is * dangerous if that code changes on us. */ if(stream){ if(is_imap_stream(stream) || ((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap"))){ if(is_imap_stream(stream)){ dprint((7, "pine_mail_open: closing passed in IMAP stream %s\n", stream->mailbox ? stream->mailbox : "?")); } else{ dprint((7, "pine_mail_open: closing passed in non-IMAP stream %s\n", stream->mailbox ? stream->mailbox : "?")); } pine_mail_close(stream); stream = NULL; } } /* * Maildrops are special. The mailbox name will be a #move name. If the * target of the maildrop is an IMAP folder we want to be sure it isn't * already open in another cached stream, to avoid double opens. This * could have happened if the user opened it manually as the target * instead of as a maildrop. It could also be a side-effect of marking * an answered flag after a reply. */ target = NULL; if(check_for_move_mbox(mailbox, NULL, 0, &target)){ MAILSTREAM *targetstream = NULL; if((d = mail_valid (NIL, target, (char *) NIL)) && !strcmp(d->name, "imap")){ targetstream = sp_stream_get(target, SP_MATCH | SP_RO_OK); if(targetstream){ dprint((9, "pine_mail_open: close previously opened target of maildrop\n")); pine_mail_actually_close(targetstream); } } } if((usepool && !stream && permlocked) || (!usepool && (permlocked || (openflags & OP_READONLY)) && (retstream = already_open_stream(mailbox, AOS_NONE)))){ if(retstream) stream = retstream; else stream = sp_stream_get(mailbox, SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0)); if(stream){ flags = SP_LOCKED | (usepool ? SP_USEPOOL : 0) | (permlocked ? SP_PERMLOCKED : 0) | (is_inbox ? SP_INBOX : 0) | (uf ? SP_USERFLDR : 0) | (tempuse ? SP_TEMPUSE : 0); /* * If the stream wasn't already locked, then we reset it so it * looks like we are reopening it. We have to worry about recent * messages since they will still be recent, if that affects us. */ if(!(sp_flags(stream) & SP_LOCKED)) reset_stream_view_state(stream); if(retflags){ *retflags |= SP_MATCH; if(sp_flags(stream) & SP_LOCKED) *retflags |= SP_LOCKED; } if(sp_flags(stream) & SP_LOCKED && sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR)){ sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); dprint((7, "pine_mail_open: permlocked: ref cnt up to %d\n", sp_ref_cnt(stream))); } else if(sp_ref_cnt(stream) <= 0){ sp_set_ref_cnt(stream, 1); dprint((7, "pine_mail_open: permexact: ref cnt set to %d\n", sp_ref_cnt(stream))); } carefully_reset_sp_flags(stream, flags); if(stream->silent && !(openflags & OP_SILENT)) stream->silent = NIL; dprint((9, "pine_mail_open: stream was already open\n")); if(stream && stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")){ dprint((7, "pine_mail_open: next TAG %08lx\n", stream->gensym)); } return(stream); } } if(usepool && !stream){ /* * First, we look for an exact match, a stream which is already * open to the mailbox we are trying to re-open, and we use that. * Skip permlocked only because we did it above already. */ if(!permlocked) stream = sp_stream_get(mailbox, SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0)); if(stream){ flags = SP_LOCKED | (usepool ? SP_USEPOOL : 0) | (permlocked ? SP_PERMLOCKED : 0) | (is_inbox ? SP_INBOX : 0) | (uf ? SP_USERFLDR : 0) | (tempuse ? SP_TEMPUSE : 0); /* * If the stream wasn't already locked, then we reset it so it * looks like we are reopening it. We have to worry about recent * messages since they will still be recent, if that affects us. */ if(!(sp_flags(stream) & SP_LOCKED)) reset_stream_view_state(stream); if(retflags){ *retflags |= SP_MATCH; if(sp_flags(stream) & SP_LOCKED) *retflags |= SP_LOCKED; } if(sp_flags(stream) & SP_LOCKED && sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR)){ sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); dprint((7, "pine_mail_open: matched: ref cnt up to %d\n", sp_ref_cnt(stream))); } else if(sp_ref_cnt(stream) <= 0){ sp_set_ref_cnt(stream, 1); dprint((7, "pine_mail_open: exact: ref cnt set to %d\n", sp_ref_cnt(stream))); } carefully_reset_sp_flags(stream, flags); /* * We may be re-using a stream that was previously open * with OP_SILENT and now we don't want OP_SILENT. * We don't turn !silent into silent because the !silentness * could be important in the original context (for example, * silent suppresses mm_expunged calls). * * WARNING: we're messing with c-client internals. */ if(stream->silent && !(openflags & OP_SILENT)) stream->silent = NIL; dprint((9, "pine_mail_open: stream already open\n")); if(stream && stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")){ dprint((7, "pine_mail_open: next TAG %08lx\n", stream->gensym)); } return(stream); } /* * No exact match, look for a stream which is open to the same * server and marked for TEMPUSE. */ stream = sp_stream_get(mailbox, SP_SAME | SP_TEMPUSE); if(stream){ dprint((9, "pine_mail_open: attempting to re-use TEMP stream\n")); } /* * No SAME/TEMPUSE stream so we look to see if there is an * open slot available (we're not yet at max_remstream). If there * is an open slot, we'll just open a new stream and put it there. * If not, we'll go inside this conditional. */ else if(!permlocked && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){ dprint((9, "pine_mail_open: no empty slots\n")); /* * No empty remote slots available. See if there is a * TEMPUSE stream that is open but to the wrong server. */ stream = sp_stream_get(mailbox, SP_TEMPUSE); if(stream){ /* * We will close this stream and use the empty slot * that that creates. */ dprint((9, "pine_mail_open: close a TEMPUSE stream and re-use that slot\n")); pine_mail_actually_close(stream); stream = NULL; } else{ /* * Still no luck. Look for a stream open to the same * server that is just not locked. This would be a * stream that might be reusable in the future, but we * need it now instead. */ stream = sp_stream_get(mailbox, SP_SAME | SP_UNLOCKED); if(stream){ dprint((9, "pine_mail_open: attempting to re-use stream\n")); } else{ /* * We'll take any UNLOCKED stream and re-use it. */ stream = sp_stream_get(mailbox, 0); if(stream){ /* * We will close this stream and use the empty slot * that that creates. */ dprint((9, "pine_mail_open: close an UNLOCKED stream and re-use the slot\n")); pine_mail_actually_close(stream); stream = NULL; } else{ if(ps_global->s_pool.max_remstream){ dprint((9, "pine_mail_open: all USEPOOL slots full of LOCKED streams, nothing to use\n")); } else{ dprint((9, "pine_mail_open: no caching, max_remstream == 0\n")); } usepool = 0; tempuse = 0; if(permlocked){ permlocked = 0; dprint((2, "pine_mail_open5: Can't mark folder Stay-Open: at max-remote limit\n")); q_status_message1(SM_ORDER, 3, 5, "Can't mark folder Stay-Open: reached max-remote limit (%s)", comatose((long) ps_global->s_pool.max_remstream)); } } } } } else{ dprint((9, "pine_mail_open: there is an empty slot to use\n")); } /* * We'll make an assumption here. If we were asked to halfopen a * stream then we'll assume that the caller doesn't really care if * the stream is halfopen or if it happens to be open to some mailbox * already. They are just saying halfopen because they don't need to * SELECT a mailbox. That's the assumption, anyway. */ if(openflags & OP_HALFOPEN && stream){ dprint((9, "pine_mail_open: asked for HALFOPEN so returning stream as is\n")); sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1); dprint((7, "pine_mail_open: halfopen: ref cnt up to %d\n", sp_ref_cnt(stream))); if(stream && stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")){ dprint((7, "pine_mail_open: next TAG %08lx\n", stream->gensym)); } if(stream->silent && !(openflags & OP_SILENT)) stream->silent = NIL; return(stream); } /* * We're going to SELECT another folder with this open stream. */ if(stream){ /* * We will have just pinged the stream to make sure it is * still alive. That ping may have discovered some new mail. * Before unselecting the folder, we should process the filters * for that new mail. */ if(!sp_flagged(stream, SP_LOCKED) && !sp_flagged(stream, SP_USERFLDR) && sp_new_mail_count(stream)) process_filter_patterns(stream, sp_msgmap(stream), sp_new_mail_count(stream)); if(stream && stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")){ dprint((7, "pine_mail_open: cancel idle timer: TAG %08lx (%s)\n", stream->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); } /* * We need to reset the counters and everything. * The easiest way to do that is just to delete all of the * sp_s data and let the open fill it in correctly for * the new folder. */ sp_free((PER_STREAM_S **) &stream->sparep); } } /* * When we pass a stream to mail_open, it will either re-use it or * close it. */ retstream = mail_open(stream, mailbox, openflags); if(retstream){ dprint((7, "pine_mail_open: mail_open returns stream:\n original_mailbox=%s\n mailbox=%s\n driver=%s rdonly=%d halfopen=%d secure=%d nmsgs=%ld recent=%ld\n", retstream->original_mailbox ? retstream->original_mailbox : "?", retstream->mailbox ? retstream->mailbox : "?", (retstream->dtb && retstream->dtb->name) ? retstream->dtb->name : "?", retstream->rdonly, retstream->halfopen, retstream->secure, retstream->nmsgs, retstream->recent)); /* * So it is easier to figure out which command goes with which * stream when debugging, change the tag associated with each stream. * Of course, this will come after the SELECT, so the startup IMAP * commands will have confusing tags. */ if(retstream != stream && retstream->dtb && retstream->dtb->name && !strcmp(retstream->dtb->name, "imap")){ retstream->gensym += (streamcounter * 0x1000000); streamcounter = (streamcounter + 1) % (8 * 16); } if(retstream && retstream->dtb && retstream->dtb->name && !strcmp(retstream->dtb->name, "imap")){ dprint((7, "pine_mail_open: next TAG %08lx\n", retstream->gensym)); } /* * Catch the case where our test up above (where usepool was set) * did not notice that this was news, but that we can tell once * we've opened the stream. (One such case would be an imap proxy * to an nntp server.) Remove it from being cached here. There was * a possible penalty for not noticing sooner. If all the usepool * slots were full, we will have closed one of the UNLOCKED streams * above, preventing us from future re-use of that stream. * We could figure out how to do the test better with just the * name. We could open the stream and then close the other one * after the fact. Or we could just not worry about it since it is * rare. */ if(IS_NEWS(retstream)){ usepool = 0; tempuse = 0; } /* make sure the message map has been instantiated */ (void) sp_msgmap(retstream); /* * If a referral during the mail_open above causes a stream * re-use which involves a BYE on an IMAP stream, then that * BYE will have caused an mm_notify which will have * instantiated the sp_data and set dead_stream! Trust that * it is alive up to here and reset it to zero. */ sp_set_dead_stream(retstream, 0); flags = SP_LOCKED | (usepool ? SP_USEPOOL : 0) | (permlocked ? SP_PERMLOCKED : 0) | (is_inbox ? SP_INBOX : 0) | (uf ? SP_USERFLDR : 0) | (tempuse ? SP_TEMPUSE : 0); sp_flag(retstream, flags); sp_set_recent_since_visited(retstream, retstream->recent); /* initialize the reference count */ sp_set_ref_cnt(retstream, 1); dprint((7, "pine_mail_open: reset: ref cnt set to %d\n", sp_ref_cnt(retstream))); if(sp_add(retstream, usepool) != 0 && usepool){ usepool = 0; tempuse = 0; flags = SP_LOCKED | (usepool ? SP_USEPOOL : 0) | (permlocked ? SP_PERMLOCKED : 0) | (is_inbox ? SP_INBOX : 0) | (uf ? SP_USERFLDR : 0) | (tempuse ? SP_TEMPUSE : 0); sp_flag(retstream, flags); (void) sp_add(retstream, usepool); } /* * When opening a newsgroup, c-client marks the messages up to the * last Deleted as Unseen. If the feature news-approximates-new-status * is on, we'd rather they be treated as Seen. That way, selecting New * messages will give us the ones past the last Deleted. So we're going * to change them to Seen. Since Seen is a session flag for news, making * this change won't have any permanent effect. C-client also marks the * messages after the last deleted Recent, which is the bit of * information we'll use to find the messages we want to change. */ if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global) && retstream->nmsgs > 0 && IS_NEWS(retstream)){ char *seq; long i, mflags = ST_SET; MESSAGECACHE *mc; /* * Search for !recent messages to set the searched bit for * those messages we want to change. Then we'll flip the bits. */ (void)count_flagged(retstream, F_UNRECENT); for(i = 1L; i <= retstream->nmsgs; i++) if((mc = mail_elt(retstream,i)) && mc->searched) mc->sequence = 1; else mc->sequence = 0; if(!is_imap_stream(retstream)) mflags |= ST_SILENT; if((seq = build_sequence(retstream, NULL, NULL)) != NULL){ mail_flag(retstream, seq, "\\SEEN", mflags); fs_give((void **)&seq); } } } return(retstream); } void reset_stream_view_state(MAILSTREAM *stream) { MSGNO_S *mm; if(!stream) return; mm = sp_msgmap(stream); if(!mm) return; sp_set_viewing_a_thread(stream, 0); sp_set_need_to_rethread(stream, 0); mn_reset_cur(mm, stream->nmsgs > 0L ? stream->nmsgs : 0L); /* default */ mm->visible_threads = -1L; mm->top = 0L; mm->max_thrdno = 0L; mm->top_after_thrd = 0L; mn_set_mansort(mm, 0); /* * Get rid of zooming and selections, but leave filtering flags. All the * flag counts and everything should still be correct because set_lflag * preserves them correctly. */ if(any_lflagged(mm, MN_SLCT | MN_HIDE)){ long i; for(i = 1L; i <= mn_get_total(mm); i++) set_lflag(stream, mm, i, MN_SLCT | MN_HIDE, 0); } /* * We could try to set up a default sort order, but the caller is going * to re-sort anyway if they are interested in sorting. So we won't do * it here. */ } /* * We have to be careful when we change the flags of an already * open stream, because there may be more than one section of * code actively using the stream. * We allow turning on (but not off) SP_LOCKED * SP_PERMLOCKED * SP_USERFLDR * SP_INBOX * We allow turning off (but not on) SP_TEMPUSE */ void carefully_reset_sp_flags(MAILSTREAM *stream, long unsigned int flags) { if(sp_flags(stream) != flags){ /* allow turning on but not off */ if(sp_flags(stream) & SP_LOCKED && !(flags & SP_LOCKED)) flags |= SP_LOCKED; if(sp_flags(stream) & SP_PERMLOCKED && !(flags & SP_PERMLOCKED)) flags |= SP_PERMLOCKED; if(sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR)) flags |= SP_USERFLDR; if(sp_flags(stream) & SP_INBOX && !(flags & SP_INBOX)) flags |= SP_INBOX; /* allow turning off but not on */ if(!(sp_flags(stream) & SP_TEMPUSE) && flags & SP_TEMPUSE) flags &= ~SP_TEMPUSE; /* leave the way they already are */ if((sp_flags(stream) & SP_FILTERED) != (flags & SP_FILTERED)) flags = (flags & ~SP_FILTERED) | (sp_flags(stream) & SP_FILTERED); if(sp_flags(stream) != flags) sp_flag(stream, flags); } } /* * Pine wrapper around mail_create. If we have the PREFER_ALT_AUTH flag turned * on we don't want to pass a NULL stream to c-client because it will open * a non-ssl connection when we want it to be ssl. */ long pine_mail_create(MAILSTREAM *stream, char *mailbox) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); char source[MAILTMPLEN], *target = NULL; DRIVER *d; dprint((7, "pine_mail_create: creating \"%s\"%s\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)")); if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ mailbox = target; dprint((7, "pine_mail_create: #move special case, creating \"%s\"\n", mailbox ? mailbox : "(NULL)")); } /* * We don't really need this anymore, since we are now using IMAPTRYALT. * We'll leave it since it works. */ if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } if(!stream) stream = sp_stream_get(mailbox, SP_MATCH); if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } return_val = mail_create(stream, mailbox); if(ourstream) pine_mail_close(ourstream); return(return_val); } /* * Pine wrapper around mail_delete. */ long pine_mail_delete(MAILSTREAM *stream, char *mailbox) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); char source[MAILTMPLEN], *target = NULL; DRIVER *d; dprint((7, "pine_mail_delete: deleting \"%s\"%s\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)")); if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ mailbox = target; dprint((7, "pine_mail_delete: #move special case, deleting \"%s\"\n", mailbox ? mailbox : "(NULL)")); } /* * We don't really need this anymore, since we are now using IMAPTRYALT. */ if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } /* oops, we seem to be deleting a selected stream */ if(!stream && (stream = sp_stream_get(mailbox, SP_MATCH))){ pine_mail_actually_close(stream); stream = NULL; } if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } return_val = mail_delete(stream, mailbox); if(ourstream) pine_mail_close(ourstream); return(return_val); } /* * Pine wrapper around mail_append. */ long pine_mail_append_full(MAILSTREAM *stream, char *mailbox, char *flags, char *date, STRING *message) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); char source[MAILTMPLEN], *target = NULL; char mailbox_nodelim[MAILTMPLEN]; int delim; DRIVER *d; dprint((7, "pine_mail_append_full: appending to \"%s\"%s\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)")); /* strip delimiter, it has no meaning in an APPEND and could cause trouble */ if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){ size_t len; len = strlen(mailbox); if(mailbox[len-1] == delim){ strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1)); mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0'; mailbox = mailbox_nodelim; } } if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ mailbox = target; dprint((7, "pine_mail_append_full: #move special case, appending to \"%s\"\n", mailbox ? mailbox : "(NULL)")); } /* * We don't really need this anymore, since we are now using IMAPTRYALT. */ if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } if(!stream) stream = sp_stream_get(mailbox, SP_MATCH); if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } return_val = mail_append_full(stream, mailbox, flags, date, message); if(ourstream) pine_mail_close(ourstream); return(return_val); } /* * Pine wrapper around mail_append. */ long pine_mail_append_multiple(MAILSTREAM *stream, char *mailbox, append_t af, APPENDPACKAGE *data, MAILSTREAM *not_this_stream) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); char source[MAILTMPLEN], *target = NULL; DRIVER *d; int we_blocked_reuse = 0; dprint((7, "pine_mail_append_multiple: appending to \"%s\"%s\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)")); if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ mailbox = target; dprint((7, "pine_mail_append_multiple: #move special case, appending to \"%s\"\n", mailbox ? mailbox : "(NULL)")); } if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } /* * We have to be careful re-using streams for multiappend, because of * the way it works. We call into c-client below but part of the call * is data containing a callback function to us to supply the data to * be appended. That function may need to get the data from the server. * If that uses the same stream as we're trying to append on, we're * in trouble. We can't call back into c-client from c-client on the same * stream. (Just think about it, we're in the middle of an APPEND command. * We can't issue a FETCH before the APPEND completes in order to complete * the APPEND.) We can re-use a stream if it is a different stream from * the one we are reading from, so that's what the not_this_stream * stuff is for. If we mark it !SP_USEPOOL, it won't get reused. */ if(sp_flagged(not_this_stream, SP_USEPOOL)){ we_blocked_reuse++; sp_unflag(not_this_stream, SP_USEPOOL); } if(!stream) stream = sp_stream_get(mailbox, SP_MATCH); if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } if(we_blocked_reuse) sp_set_flags(not_this_stream, sp_flags(not_this_stream) | SP_USEPOOL); return_val = mail_append_multiple(stream, mailbox, af, (void *) data); if(ourstream) pine_mail_close(ourstream); return(return_val); } /* * Pine wrapper around mail_copy. */ long pine_mail_copy_full(MAILSTREAM *stream, char *sequence, char *mailbox, long int options) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); char source[MAILTMPLEN], *target = NULL; char mailbox_nodelim[MAILTMPLEN]; int delim; DRIVER *d; dprint((7, "pine_mail_copy_full: copying to \"%s\"%s\n", mailbox ? mailbox : "(NULL)", stream ? "" : " (stream was NULL)")); /* strip delimiter, it has no meaning in a COPY and could cause trouble */ if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){ size_t len; len = strlen(mailbox); if(mailbox[len-1] == delim){ strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1)); mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0'; mailbox = mailbox_nodelim; } } if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ mailbox = target; dprint((7, "pine_mail_copy_full: #move special case, copying to \"%s\"\n", mailbox ? mailbox : "(NULL)")); } /* * We don't really need this anymore, since we are now using IMAPTRYALT. */ if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } if(!stream) stream = sp_stream_get(mailbox, SP_MATCH); if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. * Actually, mail_copy_full is the case where it might also be * useful to provide a stream in the nntp and pop3 cases. If we * cache such streams, then we will probably want to open one * here so that it gets cached. */ if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } return_val = mail_copy_full(stream, sequence, mailbox, options); if(ourstream) pine_mail_close(ourstream); return(return_val); } /* * Pine wrapper around mail_rename. */ long pine_mail_rename(MAILSTREAM *stream, char *old, char *new) { MAILSTREAM *ourstream = NULL; long return_val; long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); DRIVER *d; dprint((7, "pine_mail_rename(%s,%s)\n", old ? old : "", new ? new : "")); /* * We don't really need this anymore, since we are now using IMAPTRYALT. */ if((F_ON(F_PREFER_ALT_AUTH, ps_global) || (ps_global->debug_imap > 3 || ps_global->debugmem))){ if((d = mail_valid (NIL, old, (char *) NIL)) && !strcmp(d->name, "imap")){ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; } } /* oops, we seem to be renaming a selected stream */ if(!stream && (stream = sp_stream_get(old, SP_MATCH))){ pine_mail_actually_close(stream); stream = NULL; } if(!stream) stream = sp_stream_get(old, SP_SAME); if(!stream){ /* * It is only useful to open a stream in the imap case. */ if((d = mail_valid (NIL, old, (char *) NIL)) && !strcmp(d->name, "imap")){ stream = pine_mail_open(NULL, old, openflags, NULL); ourstream = stream; } } return_val = mail_rename(stream, old, new); if(ourstream) pine_mail_close(ourstream); return(return_val); } /*---------------------------------------------------------------------- Our mail_close wrapper to clean up anything on the mailstream we may have added to it. mostly in the unused bits of the elt's. ----*/ void pine_mail_close(MAILSTREAM *stream) { unsigned long uid_last, last_uid; int refcnt; if(!stream) return; dprint((7, "pine_mail_close: %s (%s)\n", stream && stream->mailbox ? stream->mailbox : "(NULL)", debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); if(sp_flagged(stream, SP_USEPOOL) && !sp_dead_stream(stream)){ refcnt = sp_ref_cnt(stream); dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); /* * Instead of checkpointing here, which takes time that the user * definitely notices, we checkpoint in new_mail at the next * opportune time, hopefully when the user is idle. */ #if 0 if(sp_flagged(stream, SP_LOCKED) && sp_flagged(stream, SP_USERFLDR) && !stream->halfopen && refcnt <= 1){ if(changes_to_checkpoint(stream)) pine_mail_check(stream); else{ dprint((7, "pine_mail_close: dont think we need to checkpoint\n")); } } #endif /* * Uid_last is valid when we first open a stream, but not always * valid after that. So if we know the last uid should be higher * than uid_last (!#%) use that instead. */ uid_last = stream->uid_last; if(stream->nmsgs > 0L && (last_uid=mail_uid(stream,stream->nmsgs)) > uid_last) uid_last = last_uid; sp_set_saved_uid_validity(stream, stream->uid_validity); sp_set_saved_uid_last(stream, uid_last); /* * If the reference count is down to 0, unlock it. * In any case, don't actually do any real closing. */ if(refcnt > 0) sp_set_ref_cnt(stream, refcnt-1); refcnt = sp_ref_cnt(stream); dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); if(refcnt <= 0){ dprint((7, "pine_mail_close: unlocking: start idle timer: TAG %08lx (%s)\n", stream->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_last_use_time(stream, time(0)); /* * Logically, we ought to be unflagging SP_INBOX, too. However, * the filtering code uses SP_INBOX when deciding if it should * filter some things, and we keep filtering after the mailbox * is closed. So leave SP_INBOX alone. This (the closing of INBOX) * usually only happens in goodnight_gracey when we're * shutting everything down. */ sp_unflag(stream, SP_LOCKED | SP_PERMLOCKED | SP_USERFLDR); } else{ dprint((7, "pine_mail_close: ref cnt is now %d\n", refcnt)); } } else{ dprint((7, "pine_mail_close: %s\n", sp_flagged(stream, SP_USEPOOL) ? "dead stream" : "no pool")); refcnt = sp_ref_cnt(stream); dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt)); /* * If the reference count is down to 0, unlock it. * In any case, don't actually do any real closing. */ if(refcnt > 0) sp_set_ref_cnt(stream, refcnt-1); refcnt = sp_ref_cnt(stream); if(refcnt <= 0){ pine_mail_actually_close(stream); } else{ dprint((7, "pine_mail_close: ref cnt is now %d\n", refcnt)); } } } void pine_mail_actually_close(MAILSTREAM *stream) { if(!stream) return; if(!sp_closing(stream)){ dprint((7, "pine_mail_actually_close: %s (%s)\n", stream && stream->mailbox ? stream->mailbox : "(NULL)", debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_closing(stream, 1); if(!sp_flagged(stream, SP_LOCKED) && !sp_flagged(stream, SP_USERFLDR) && !sp_dead_stream(stream) && sp_new_mail_count(stream)) process_filter_patterns(stream, sp_msgmap(stream), sp_new_mail_count(stream)); sp_delete(stream); /* * let sp_free_callback() free the sp_s stuff and the callbacks to * free_pine_elt free the per-elt pine stuff. */ mail_close(stream); } } /* * If we haven't used a stream for a while, we may want to logout. */ void maybe_kill_old_stream(MAILSTREAM *stream) { #define KILL_IF_IDLE_TIME (25 * 60) if(stream && !sp_flagged(stream, SP_LOCKED) && !sp_flagged(stream, SP_USERFLDR) && time(0) - sp_last_use_time(stream) > KILL_IF_IDLE_TIME){ dprint((7, "killing idle stream: %s (%s): idle timer = %ld secs\n", stream && stream->mailbox ? stream->mailbox : "(NULL)", debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress), (long) (time(0)-sp_last_use_time(stream)))); /* * Another thing we could do here instead is to unselect the * mailbox, leaving the stream open to the server. */ pine_mail_actually_close(stream); } } /* * Catch searches that don't need to go to the server. * (Not anymore, now c-client does this for us.) */ long pine_mail_search_full(MAILSTREAM *stream, char *charset, SEARCHPGM *pgm, long int flags) { /* * The charset should either be UTF-8 or NULL for alpine. * If it is UTF-8 we may be able to change it to NULL if * everything in the search is actually ascii. We try to * do this because not all servers understand the CHARSET * parameter. */ if(charset && !strucmp(charset, "utf-8") && is_imap_stream(stream) && !hibit_in_searchpgm(pgm)) charset = NULL; if(F_ON(F_NNTP_SEARCH_USES_OVERVIEW, ps_global)) flags |= SO_OVERVIEW; return(stream ? mail_search_full(stream, charset, pgm, flags) : NIL); } int hibit_in_searchpgm(SEARCHPGM *pgm) { SEARCHOR *or; SEARCHPGMLIST *not; if(!pgm) return 0; if((pgm->subject && hibit_in_strlist(pgm->subject)) || (pgm->text && hibit_in_strlist(pgm->text)) || (pgm->body && hibit_in_strlist(pgm->body)) || (pgm->cc && hibit_in_strlist(pgm->cc)) || (pgm->from && hibit_in_strlist(pgm->from)) || (pgm->to && hibit_in_strlist(pgm->to)) || (pgm->bcc && hibit_in_strlist(pgm->bcc)) || (pgm->keyword && hibit_in_strlist(pgm->keyword)) || (pgm->unkeyword && hibit_in_strlist(pgm->return_path)) || (pgm->sender && hibit_in_strlist(pgm->sender)) || (pgm->reply_to && hibit_in_strlist(pgm->reply_to)) || (pgm->in_reply_to && hibit_in_strlist(pgm->in_reply_to)) || (pgm->message_id && hibit_in_strlist(pgm->message_id)) || (pgm->newsgroups && hibit_in_strlist(pgm->newsgroups)) || (pgm->followup_to && hibit_in_strlist(pgm->followup_to)) || (pgm->references && hibit_in_strlist(pgm->references)) || (pgm->header && hibit_in_header(pgm->header))) return 1; for(or = pgm->or; or; or = or->next) if(hibit_in_searchpgm(or->first) || hibit_in_searchpgm(or->second)) return 1; for(not = pgm->not; not; not = not->next) if(hibit_in_searchpgm(not->pgm)) return 1; return 0; } int hibit_in_strlist(STRINGLIST *sl) { for(; sl; sl = sl->next) if(hibit_in_sizedtext(&sl->text)) return 1; return 0; } int hibit_in_header(SEARCHHEADER *header) { SEARCHHEADER *hdr; for(hdr = header; hdr; hdr = hdr->next) if(hibit_in_sizedtext(&hdr->line) || hibit_in_sizedtext(&hdr->text)) return 1; return 0; } int hibit_in_sizedtext(SIZEDTEXT *st) { unsigned char *p, *end; p = st ? st->data : NULL; if(p) for(end = p + st->size; p < end; p++) if(*p & 0x80) return 1; return 0; } void pine_mail_fetch_flags(MAILSTREAM *stream, char *sequence, long int flags) { ps_global->dont_count_flagchanges = 1; mail_fetch_flags(stream, sequence, flags); ps_global->dont_count_flagchanges = 0; } ENVELOPE * pine_mail_fetchenvelope(MAILSTREAM *stream, long unsigned int msgno) { ENVELOPE *env = NULL; ps_global->dont_count_flagchanges = 1; if(stream && msgno > 0L && msgno <= stream->nmsgs) env = mail_fetchenvelope(stream, msgno); ps_global->dont_count_flagchanges = 0; return(env); } ENVELOPE * pine_mail_fetch_structure(MAILSTREAM *stream, long unsigned int msgno, struct mail_bodystruct **body, long int flags) { ENVELOPE *env = NULL; ps_global->dont_count_flagchanges = 1; if(stream && (flags & FT_UID || (msgno > 0L && msgno <= stream->nmsgs))) env = mail_fetch_structure(stream, msgno, body, flags); ps_global->dont_count_flagchanges = 0; return(env); } ENVELOPE * pine_mail_fetchstructure(MAILSTREAM *stream, long unsigned int msgno, struct mail_bodystruct **body) { ENVELOPE *env = NULL; ps_global->dont_count_flagchanges = 1; if(stream && msgno > 0L && msgno <= stream->nmsgs) env = mail_fetchstructure(stream, msgno, body); ps_global->dont_count_flagchanges = 0; return(env); } /* * Wrapper around mail_fetch_body. * Currently only used to turn on partial fetching if trying * to work around the microsoft ssl bug. */ char * pine_mail_fetch_body(MAILSTREAM *stream, long unsigned int msgno, char *section, long unsigned int *len, long int flags) { #ifdef _WINDOWS if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)) return(pine_mail_partial_fetch_wrapper(stream, msgno, section, len, flags, 0, NULL, 0)); else #endif return(mail_fetch_body(stream, msgno, section, len, flags)); } /* * Wrapper around mail_fetch_text. * Currently the only purpose this wrapper serves is to turn * on partial fetching for quell-ssl-largeblocks. */ char * pine_mail_fetch_text(MAILSTREAM *stream, long unsigned int msgno, char *section, long unsigned int *len, long int flags) { #ifdef _WINDOWS if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)) return(pine_mail_partial_fetch_wrapper(stream, msgno, section, len, flags, 0, NULL, 1)); else #endif return(mail_fetch_text(stream, msgno, section, len, flags)); } /* * Determine whether to do partial-fetching or not, and do it * args - same as c-client functions being wrapped around * get_n_bytes - try to partial fetch for the first n bytes. * makes no guarantees, might wind up fetching * the entire text anyway. * str_to_free - pointer to string to free if we only get * (non-cachable) partial text (required for * get_n_bytes). * is_text_fetch - * set, tells us to do mail_fetch_text and mail_partial_text * unset, tells us to do mail_fetch_body and mail_partial_body */ char * pine_mail_partial_fetch_wrapper(MAILSTREAM *stream, long unsigned int msgno, char *section, long unsigned int *len, long int flags, long unsigned int get_n_bytes, char **str_to_free, int is_text_fetch) { BODY *body; unsigned long size, firstbyte, lastbyte; void *old_gets; FETCH_READC_S *pftc; char imap_cache_section[MAILTMPLEN]; SIZEDTEXT new_text; MESSAGECACHE *mc; char *(*fetch_full)(MAILSTREAM *, unsigned long, char *, unsigned long *, long); long (*fetch_partial)(MAILSTREAM *, unsigned long, char *, unsigned long, unsigned long, long); fetch_full = is_text_fetch ? mail_fetch_text : mail_fetch_body; fetch_partial = is_text_fetch ? mail_partial_text : mail_partial_body; if(str_to_free) *str_to_free = NULL; #ifdef _WINDOWS if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global) || get_n_bytes){ #else if(get_n_bytes){ #endif /* _WINDOWS */ if(section && *section) body = mail_body(stream, msgno, (unsigned char *) section); else pine_mail_fetch_structure(stream, msgno, &body, flags); if(!body) return NULL; if(body->type != TYPEMULTIPART) size = body->size.bytes; else if((!section || !*section) && msgno > 0L && stream && msgno <= stream->nmsgs && (mc = mail_elt(stream, msgno))) size = mc->rfc822_size; /* upper bound */ else /* just a guess, can't get actual size */ size = fcc_size_guess(body) + 2048; /* * imap_cache, originally intended for c-client internal use, * takes a section argument that is different from one we * would pass to mail_body. Typically in this function * section is NULL, which translates to "TEXT", but in other * cases we would want to append ".TEXT" to the section */ if(is_text_fetch) snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10, section && *section ? section : "", section && *section ? "." : ""); else snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10, section && *section ? section : ""); if(modern_imap_stream(stream) #ifdef _WINDOWS && ((size > AVOID_MICROSOFT_SSL_CHUNKING_BUG) || (get_n_bytes && size > get_n_bytes)) #else && (get_n_bytes && size > get_n_bytes) #endif /* _WINDOWS */ && !imap_cache(stream, msgno, imap_cache_section, NULL, NULL)){ if(get_n_bytes == 0){ dprint((8, "fetch_wrapper: doing partial fetching to work around microsoft bug\n")); } else{ dprint((8, "fetch_wrapper: partial fetching due to %lu get_n_bytes\n", get_n_bytes)); } pftc = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S)); memset(g_pft_desc = pftc, 0, sizeof(FETCH_READC_S)); #ifdef _WINDOWS if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)){ if(get_n_bytes) pftc->chunksize = MIN(get_n_bytes, AVOID_MICROSOFT_SSL_CHUNKING_BUG); else pftc->chunksize = AVOID_MICROSOFT_SSL_CHUNKING_BUG; } else #endif /* _WINDOWS */ pftc->chunksize = get_n_bytes; pftc->chunk = (char *) fs_get((pftc->chunksize+1) * sizeof(char)); pftc->cache = so_get(CharStar, NULL, EDIT_ACCESS); pftc->read = 0L; so_truncate(pftc->cache, size + 1); old_gets = mail_parameters(stream, GET_GETS, (void *)NULL); mail_parameters(stream, SET_GETS, (void *) partial_text_gets); /* start fetching */ do{ firstbyte = pftc->read ; lastbyte = firstbyte + pftc->chunksize; if(get_n_bytes > firstbyte && get_n_bytes < lastbyte){ pftc->chunksize = get_n_bytes - firstbyte; lastbyte = get_n_bytes; } (*fetch_partial)(stream, msgno, section, firstbyte, pftc->chunksize, flags); if(pftc->read != lastbyte) break; } while((pftc->read == lastbyte) && (!get_n_bytes || (pftc->read < get_n_bytes))); dprint((8, "fetch_wrapper: anticipated size=%lu read=%lu\n", size, pftc->read)); mail_parameters(stream, SET_GETS, old_gets); new_text.size = pftc->read; new_text.data = (unsigned char *)so_text(pftc->cache); if(get_n_bytes && pftc->read == get_n_bytes){ /* * don't write to cache if we're only dealing with * partial text. */ if(!str_to_free) alpine_panic("Programmer botch: partial fetch attempt w/o string pointer"); else *str_to_free = (char *) new_text.data; } else { /* ugh, rewrite string in case it got stomped on last call */ if(is_text_fetch) snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10, section && *section ? section : "", section && *section ? "." : ""); else snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10, section && *section ? section : ""); imap_cache(stream, msgno, imap_cache_section, NULL, &new_text); } pftc->cache->txt = (void *)NULL; so_give(&pftc->cache); fs_give((void **)&pftc->chunk); fs_give((void **)&pftc); g_pft_desc = NULL; if(len) *len = new_text.size; return ((char *)new_text.data); } else return((*fetch_full)(stream, msgno, section, len, flags)); } else return((*fetch_full)(stream, msgno, section, len, flags)); } /* * c-client callback that handles getting the text */ char * partial_text_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md) { unsigned long n; n = MIN(g_pft_desc->chunksize, size); g_pft_desc->read +=n; (*f) (stream, n, g_pft_desc->chunk); if(g_pft_desc->cache) so_nputs(g_pft_desc->cache, g_pft_desc->chunk, (long) n); return(NULL); } /* * Pings the stream. Returns 0 if the stream is dead, non-zero otherwise. */ long pine_mail_ping(MAILSTREAM *stream) { time_t now; long ret = 0L; if(!sp_dead_stream(stream)){ ret = mail_ping(stream); if(ret && sp_dead_stream(stream)) ret = 0L; } if(ret){ now = time(0); sp_set_last_ping(stream, now); sp_set_last_expunged_reaper(stream, now); } return(ret); } void pine_mail_check(MAILSTREAM *stream) { reset_check_point(stream); mail_check(stream); } /* * Unlike mail_list, this version returns a value. The returned value is * TRUE if the stream is opened ok, and FALSE if we failed opening the * stream. This allows us to differentiate between a LIST which returns * no matches and a failure opening the stream. We do this by pre-opening * the stream ourselves. */ int pine_mail_list(MAILSTREAM *stream, char *ref, char *pat, unsigned int *options) { int we_open = 0; char *halfopen_target; char source[MAILTMPLEN], *target = NULL; MAILSTREAM *ourstream = NULL; dprint((7, "pine_mail_list: ref=%s pat=%s%s\n", ref ? ref : "(NULL)", pat ? pat : "(NULL)", stream ? "" : " (stream was NULL)")); if((!ref && check_for_move_mbox(pat, source, sizeof(source), &target)) || check_for_move_mbox(ref, source, sizeof(source), &target)){ ref = NIL; pat = target; if(options) *options |= PML_IS_MOVE_MBOX; dprint((7, "pine_mail_list: #move special case, listing \"%s\"%s\n", pat ? pat : "(NULL)", stream ? "" : " (stream was NULL)")); } if(!stream && ((pat && *pat == '{') || (ref && *ref == '{'))){ we_open++; if(pat && *pat == '{'){ ref = NIL; halfopen_target = pat; } else halfopen_target = ref; } if(we_open){ long flags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE); stream = sp_stream_get(halfopen_target, SP_MATCH); if(!stream) stream = sp_stream_get(halfopen_target, SP_SAME); if(!stream){ DRIVER *d; /* * POP is a special case. We don't need to have a stream * to call mail_list for a POP name. The else part is the * POP part. */ if((d = mail_valid(NIL, halfopen_target, (char *) NIL)) && (d->flags & DR_HALFOPEN)){ stream = pine_mail_open(NIL, halfopen_target, flags, NULL); ourstream = stream; if(!stream) return(FALSE); } else stream = NULL; } mail_list_internal(stream, ref, pat); } else mail_list_internal(stream, ref, pat); if(ourstream) pine_mail_close(ourstream); return(TRUE); } /* * mail_list_internal -- A monument to software religion and those who * would force it upon us. */ void mail_list_internal(MAILSTREAM *s, char *r, char *p) { if(F_ON(F_FIX_BROKEN_LIST, ps_global) && ((s && s->mailbox && *s->mailbox == '{') || (!s && ((r && *r == '{') || (p && *p == '{'))))){ char tmp[2*MAILTMPLEN]; /* MAILTMPLEN = sizeof(tmp)/2 */ snprintf(tmp, sizeof(tmp), "%.*s%.*s", MAILTMPLEN-1, r ? r : "", MAILTMPLEN-1, p); mail_list(s, "", tmp); } else mail_list(s, r, p); } long pine_mail_status(MAILSTREAM *stream, char *mailbox, long int flags) { return(pine_mail_status_full(stream, mailbox, flags, NULL, NULL)); } long pine_mail_status_full(MAILSTREAM *stream, char *mailbox, long int flags, imapuid_t *uidvalidity, imapuid_t *uidnext) { char source[MAILTMPLEN], *target = NULL; long ret = NIL; MAILSTATUS cache, status; MAILSTREAM *ourstream = NULL; if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){ memset(&status, 0, sizeof(status)); memset(&cache, 0, sizeof(cache)); /* tell mm_status() to write partial return here */ pine_cached_status = &status; flags |= (SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES); /* do status of destination */ stream = sp_stream_get(target, SP_SAME); /* should never be news, don't worry about mulnewrsc flag*/ if((ret = pine_mail_status_full(stream, target, flags, uidvalidity, uidnext)) && !status.recent){ /* do status of source */ pine_cached_status = &cache; stream = sp_stream_get(source, SP_SAME); if(!stream){ DRIVER *d; if((d = mail_valid (NIL, source, (char *) NIL)) && !strcmp(d->name, "imap")){ long openflags =OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE; if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; stream = pine_mail_open(NULL, source, openflags, NULL); ourstream = stream; } else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global) && d && (!strucmp(d->name, "news") || !strucmp(d->name, "nntp"))) flags |= SA_MULNEWSRC; } if(!ps_global->user_says_cancel && mail_status(stream, source, flags)){ DRIVER *d; int is_news = 0; /* * If the target has recent messages, then we don't come * through here. We just use the answer from the target. * * If not, then we leave the target results in "status" and * run a mail_status on the source that puts its results * in "cache". Combine the results from cache with the * results that were already in status. * * We count all messages in the source mailbox as recent and * unseen, even if they are not recent or unseen in the source, * because they will be recent and unseen in the target * when we open it. (Not quite true. It is possible that some * messages from a POP server will end up seen instead * of unseen. * It is also possible that it is correct. If we add unseen, or * if we add messages, we could get it wrong. As far as I * can tell, Pine doesn't ever even use status.unseen, so it * is currently academic anyway.) Hubert 2003-03-05 * (Does now 2004-06-02 in next_folder.) * * However, we don't want to count all messages as recent if * the source mailbox is NNTP or NEWS, because we won't be * deleting those messages from the source. * We only count recent. * * There are other cases that are trouble. One in particular * is an IMAP-to-NNTP proxy, where the messages can't be removed * from the mailbox but they can be deleted. If we count * messages in the source as being recent and it turns out they * were all deleted already, then we incorrectly say the folder * has recent messages when it doesn't. We can recover from that * case at some cost by actually opening the folder the first * time if there are not recents, and then checking to see if * everything is deleted. Subsequently, we store the uid values * (which are returned by status) so that we can know if the * mailbox changed or not. The problem being solved is that * the TAB command indicates new messages in a folder when there * really aren't any. An alternative would be to use the is_news * half of the if-else in all cases. A problem with that is * that there could be non-recent messages sitting in the * source mailbox that we never discover. Hubert 2003-03-28 */ if((d = mail_valid (NIL, source, (char *) NIL)) && (!strcmp(d->name, "nntp") || !strcmp(d->name, "news"))) is_news++; if(is_news && cache.flags & SA_RECENT){ status.messages += cache.recent; status.recent += cache.recent; status.unseen += cache.recent; status.uidnext += cache.recent; } else{ if(uidvalidity && *uidvalidity && uidnext && *uidnext && cache.flags & SA_UIDVALIDITY && cache.uidvalidity == *uidvalidity && cache.flags & SA_UIDNEXT && cache.uidnext == *uidnext){ ; /* nothing changed in source mailbox */ } else if(cache.flags & SA_RECENT && cache.recent){ status.messages += cache.recent; status.recent += cache.recent; status.unseen += cache.recent; status.uidnext += cache.recent; } else if(!(cache.flags & SA_MESSAGES) || cache.messages){ long openflags = OP_SILENT | SP_USEPOOL | SP_TEMPUSE; long delete_count, not_deleted = 0L; /* Actually open it up and check */ if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; if(!ourstream) stream = NULL; if(ourstream && !same_stream_and_mailbox(source, ourstream)){ pine_mail_close(ourstream); ourstream = stream = NULL; } if(!stream){ stream = pine_mail_open(stream, source, openflags, NULL); ourstream = stream; } if(stream){ delete_count = count_flagged(stream, F_DEL); not_deleted = stream->nmsgs - delete_count; } status.messages += not_deleted; status.recent += not_deleted; status.unseen += not_deleted; status.uidnext += not_deleted; } if(uidvalidity && cache.flags & SA_UIDVALIDITY) *uidvalidity = cache.uidvalidity; if(uidnext && cache.flags & SA_UIDNEXT) *uidnext = cache.uidnext; } } } /* * Do the regular mm_status callback which we've been intercepting * into different locations above. */ pine_cached_status = NIL; if(ret) mm_status(NULL, mailbox, &status); } else{ if(!stream){ DRIVER *d; if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){ long openflags = OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE; if(F_ON(F_PREFER_ALT_AUTH, ps_global)) openflags |= OP_TRYALT; /* * We just use this to find the answer. * We're asking for trouble if we do a STATUS on a * selected mailbox. I don't believe this happens in pine. * It does now (2004-06-02) in next_folder if the * F_TAB_USES_UNSEEN option is set and the folder was * already opened. */ stream = sp_stream_get(mailbox, SP_MATCH); if(stream){ memset(&status, 0, sizeof(status)); if(flags & SA_MESSAGES){ status.flags |= SA_MESSAGES; status.messages = stream->nmsgs; } if(flags & SA_RECENT){ status.flags |= SA_RECENT; status.recent = stream->recent; } if(flags & SA_UNSEEN){ long i; SEARCHPGM *srchpgm; MESSAGECACHE *mc; srchpgm = mail_newsearchpgm(); srchpgm->unseen = 1; pine_mail_search_full(stream, NULL, srchpgm, SE_NOPREFETCH | SE_FREE); status.flags |= SA_UNSEEN; status.unseen = 0L; for(i = 1L; i <= stream->nmsgs; i++) if((mc = mail_elt(stream, i)) && mc->searched) status.unseen++; } if(flags & SA_UIDVALIDITY){ status.flags |= SA_UIDVALIDITY; status.uidvalidity = stream->uid_validity; } if(flags & SA_UIDNEXT){ status.flags |= SA_UIDNEXT; status.uidnext = stream->uid_last + 1L; } mm_status(NULL, mailbox, &status); return T; /* that's what c-client returns when success */ } if(!stream) stream = sp_stream_get(mailbox, SP_SAME); if(!stream){ stream = pine_mail_open(NULL, mailbox, openflags, NULL); ourstream = stream; } } else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global) && d && (!strucmp(d->name, "news") || !strucmp(d->name, "nntp"))) flags |= SA_MULNEWSRC; } if(!ps_global->user_says_cancel) ret = mail_status(stream, mailbox, flags); /* non #move case */ } if(ourstream) pine_mail_close(ourstream); return ret; } /* * Check for a mailbox name that is a legitimate #move mailbox. * * Args mbox -- The mailbox name to check * sourcebuf -- Copy the source mailbox name into this buffer * sbuflen -- Length of sourcebuf * targetptr -- Set the pointer this points to to point to the * target mailbox name in the original string * * Returns 1 - is a #move mailbox * 0 - not */ int check_for_move_mbox(char *mbox, char *sourcebuf, size_t sbuflen, char **targetptr) { char delim, *s; int i; if(mbox && (mbox[0] == '#') && ((mbox[1] == 'M') || (mbox[1] == 'm')) && ((mbox[2] == 'O') || (mbox[2] == 'o')) && ((mbox[3] == 'V') || (mbox[3] == 'v')) && ((mbox[4] == 'E') || (mbox[4] == 'e')) && (delim = mbox[5]) && (s = strchr(mbox+6, delim)) && (i = s++ - (mbox + 6)) && (!sourcebuf || i < sbuflen)){ if(sourcebuf){ strncpy(sourcebuf, mbox+6, i); /* copy source mailbox name */ sourcebuf[i] = '\0'; } if(targetptr) *targetptr = s; return 1; } return 0; } /* * Checks through stream cache for a stream pointer already open to * this mailbox, read/write. Very similar to sp_stream_get, but we want * to look at all streams, not just imap streams. * Right now it is very specialized. If we want to use it more generally, * generalize it or combine it with sp_stream_get somehow. */ MAILSTREAM * already_open_stream(char *mailbox, int flags) { int i; MAILSTREAM *m; if(!mailbox) return(NULL); if(*mailbox == '{'){ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && !(flags & AOS_RW_ONLY && m->rdonly) && (*m->mailbox == '{') && !sp_dead_stream(m) && same_stream_and_mailbox(mailbox, m)) return(m); } } else{ char *cn, tmp[MAILTMPLEN]; cn = mailboxfile(tmp, mailbox); for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && !(flags & AOS_RW_ONLY && m->rdonly) && (*m->mailbox != '{') && !sp_dead_stream(m) && ((cn && *cn && !strcmp(cn, m->mailbox)) || !strcmp(mailbox, m->original_mailbox) || !strcmp(mailbox, m->mailbox))) return(m); } } return(NULL); } void pine_imap_cmd_happened(MAILSTREAM *stream, char *cmd, long int flags) { dprint((9, "imap_cmd(%s, %s, 0x%lx)\n", STREAMNAME(stream), cmd ? cmd : "?", flags)); if(cmd && !strucmp(cmd, "CHECK")) reset_check_point(stream); ps_global->can_interrupt = 0; /* never interrupt anything */ if(is_imap_stream(stream)){ time_t now; now = time(0); sp_set_last_ping(stream, now); sp_set_last_activity(stream, now); if(!(flags & SC_EXPUNGEDEFERRED)) sp_set_last_expunged_reaper(stream, now); if(cmd && !strucmp(cmd, "NOOP")) /* but can interrupt this one */ ps_global->can_interrupt = 1; } } /* * Tells us whether we ought to check for a dead stream or not. * We assume that we ought to check if it is not IMAP and if it is IMAP we * don't need to check if the last activity was within the last 5 minutes. */ int recent_activity(MAILSTREAM *stream) { if(is_imap_stream(stream) && !sp_dead_stream(stream) && (time(0) - sp_last_activity(stream) < 5L * 60L)) return 1; else return 0; } void sp_cleanup_dead_streams(void) { int i; MAILSTREAM *m; (void) streams_died(); /* tell user in case they don't know yet */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_dead_stream(m)) pine_mail_close(m); } } /* * Returns 0 if stream flags not set, non-zero if they are. */ int sp_flagged(MAILSTREAM *stream, long unsigned int flags) { return(sp_flags(stream) & flags); } void sp_set_fldr(MAILSTREAM *stream, char *folder) { PER_STREAM_S **pss; pss = sp_data(stream); if(pss && *pss){ if((*pss)->fldr) fs_give((void **) &(*pss)->fldr); if(folder) (*pss)->fldr = cpystr(folder); } } void sp_set_saved_cur_msg_id(MAILSTREAM *stream, char *id) { PER_STREAM_S **pss; pss = sp_data(stream); if(pss && *pss){ if((*pss)->saved_cur_msg_id) fs_give((void **) &(*pss)->saved_cur_msg_id); if(id) (*pss)->saved_cur_msg_id = cpystr(id); } } /* * Sets flags absolutely, erasing old flags. */ void sp_flag(MAILSTREAM *stream, long unsigned int flags) { if(!stream) return; dprint((9, "sp_flag(%s, 0x%x): %s%s%s%s%s%s%s%s\n", (stream && stream->mailbox) ? stream->mailbox : "?", flags, flags ? "set" : "clear", (flags & SP_LOCKED) ? " SP_LOCKED" : "", (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", (flags & SP_INBOX) ? " SP_INBOX" : "", (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "", !flags ? " ALL" : "")); sp_set_flags(stream, flags); } /* * Clear individual stream flags. */ void sp_unflag(MAILSTREAM *stream, long unsigned int flags) { if(!stream || !flags) return; dprint((9, "sp_unflag(%s, 0x%x): unset%s%s%s%s%s%s\n", (stream && stream->mailbox) ? stream->mailbox : "?", flags, (flags & SP_LOCKED) ? " SP_LOCKED" : "", (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", (flags & SP_INBOX) ? " SP_INBOX" : "", (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); sp_set_flags(stream, sp_flags(stream) & ~flags); flags = sp_flags(stream); dprint((9, "sp_unflag(%s, 0x%x): result:%s%s%s%s%s%s\n", (stream && stream->mailbox) ? stream->mailbox : "?", flags, (flags & SP_LOCKED) ? " SP_LOCKED" : "", (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "", (flags & SP_INBOX) ? " SP_INBOX" : "", (flags & SP_USERFLDR) ? " SP_USERFLDR" : "", (flags & SP_USEPOOL) ? " SP_USEPOOL" : "", (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); } /* * Set dead stream indicator and close if not locked. */ void sp_mark_stream_dead(MAILSTREAM *stream) { if(!stream) return; dprint((9, "sp_mark_stream_dead(%s)\n", (stream && stream->mailbox) ? stream->mailbox : "?")); /* * If the stream isn't locked, it is no longer useful. Get rid of it. */ if(!sp_flagged(stream, SP_LOCKED)) pine_mail_actually_close(stream); else{ /* * If it is locked, then we have to worry about references to it * that still exist. For example, it might be a permlocked stream * or it might be the current stream. We need to let it be discovered * by those referencers instead of just eliminating it, so that they * can clean up the mess they need to clean up. */ sp_set_dead_stream(stream, 1); } } /* * Returns the number of streams in the stream pool which are * SP_USEPOOL but not SP_PERMLOCKED. */ int sp_nusepool_notperm(void) { int i, cnt = 0; MAILSTREAM *m; for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_PERMLOCKED)) cnt++; } return(cnt); } /* * Returns the number of folders that the user has marked to be PERMLOCKED * folders (plus INBOX) that are remote IMAP folders. * * This routine depends on the fact that VAR_INBOX_PATH, VAR_PERMLOCKED, * and the ps_global->context_list are correctly set. */ int sp_nremote_permlocked(void) { int cnt = 0; char **lock_these, *p = NULL, *dummy = NULL, *lt; DRIVER *d; /* first check if INBOX is remote */ lt = ps_global->VAR_INBOX_PATH; if(lt && (d=mail_valid(NIL, lt, (char *) NIL)) && !strcmp(d->name, "imap")) cnt++; /* then count the user-specified permlocked folders */ for(lock_these = ps_global->VAR_PERMLOCKED; lock_these && *lock_these; lock_these++){ /* * Skip inbox, already done above. Should do this better so that we * catch the case where the user puts the technical spec of the inbox * in the list, or where the user lists one folder twice. */ if(*lock_these && !strucmp(*lock_these, ps_global->inbox_name)) continue; /* there isn't really a pair, it just dequotes for us */ get_pair(*lock_these, &dummy, &p, 0, 0); /* * Check to see if this is an incoming nickname and replace it * with the full name. */ if(!(p && ps_global->context_list && ps_global->context_list->use & CNTXT_INCMNG && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), FN_WHOLE_NAME)))) lt = p; if(dummy) fs_give((void **) &dummy); if(lt && (d=mail_valid(NIL, lt, (char *) NIL)) && !strcmp(d->name, "imap")) cnt++; if(p) fs_give((void **) &p); } return(cnt); } /* * Look for an already open stream that can be used for a new purpose. * (Note that we only look through streams flagged SP_USEPOOL.) * * Args: mailbox * flags * * Flags is a set of values or'd together which tells us what the request * is looking for. * * Returns: a live stream from the stream pool or NULL. */ MAILSTREAM * sp_stream_get(char *mailbox, long unsigned int flags) { int i; MAILSTREAM *m; dprint((7, "sp_stream_get(%s):%s%s%s%s%s\n", mailbox ? mailbox : "?", (flags & SP_MATCH) ? " SP_MATCH" : "", (flags & SP_RO_OK) ? " SP_RO_OK" : "", (flags & SP_SAME) ? " SP_SAME" : "", (flags & SP_UNLOCKED) ? " SP_UNLOCKED" : "", (flags & SP_TEMPUSE) ? " SP_TEMPUSE" : "")); /* look for stream already open to this mailbox */ if(flags & SP_MATCH){ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && (!m->rdonly || (flags & SP_RO_OK)) && !sp_dead_stream(m) && same_stream_and_mailbox(mailbox, m)){ if((sp_flagged(m, SP_LOCKED) && recent_activity(m)) || pine_mail_ping(m)){ dprint((7, "sp_stream_get: found exact match, slot %d\n", i)); if(!sp_flagged(m, SP_LOCKED)){ dprint((7, "reset idle timer1: next TAG %08lx (%s)\n", m->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_last_use_time(m, time(0)); } return(m); } sp_mark_stream_dead(m); } } } /* * SP_SAME will not match if an SP_MATCH match would have worked. * If the caller is interested in SP_MATCH streams as well as SP_SAME * streams then the caller should make two separate calls to this * routine. */ if(flags & SP_SAME){ /* * If the flags arg does not have either SP_TEMPUSE or SP_UNLOCKED * set, then we'll accept any stream, even if locked. * We want to prefer the LOCKED case so that we don't have to ping. */ if(!(flags & SP_UNLOCKED) && !(flags & SP_TEMPUSE)){ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) && same_stream(mailbox, m) && !same_stream_and_mailbox(mailbox, m)){ if(recent_activity(m) || pine_mail_ping(m)){ dprint((7, "sp_stream_get: found SAME match, slot %d\n", i)); return(m); } sp_mark_stream_dead(m); } } /* consider the unlocked streams */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) && same_stream(mailbox, m) && !same_stream_and_mailbox(mailbox, m)){ /* always ping unlocked streams */ if(pine_mail_ping(m)){ dprint((7, "sp_stream_get: found SAME match, slot %d\n", i)); dprint((7, "reset idle timer4: next TAG %08lx (%s)\n", m->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_last_use_time(m, time(0)); return(m); } sp_mark_stream_dead(m); } } } /* * Prefer streams marked SP_TEMPUSE and not LOCKED. * If SP_TEMPUSE is set in the flags arg then this is the * only loop we try. */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE) && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) && same_stream(mailbox, m) && !same_stream_and_mailbox(mailbox, m)){ if(pine_mail_ping(m)){ dprint((7, "sp_stream_get: found SAME/TEMPUSE match, slot %d\n", i)); dprint((7, "reset idle timer2: next TAG %08lx (%s)\n", m->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_last_use_time(m, time(0)); return(m); } sp_mark_stream_dead(m); } } /* * If SP_TEMPUSE is not set in the flags arg but SP_UNLOCKED is, * then we will consider * streams which are not marked SP_TEMPUSE (but are still not * locked). We go through these in reverse order so that we'll get * the last one added instead of the first one. It's not clear if * that is a good idea or if a more complex search would somehow * be better. Maybe we should use a round-robin sort of search * here so that we don't leave behind unused streams. Or maybe * we should keep track of when we used it and look for the LRU stream. */ if(!(flags & SP_TEMPUSE)){ for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m) && same_stream(mailbox, m) && !same_stream_and_mailbox(mailbox, m)){ if(pine_mail_ping(m)){ dprint((7, "sp_stream_get: found SAME/UNLOCKED match, slot %d\n", i)); dprint((7, "reset idle timer3: next TAG %08lx (%s)\n", m->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress))); sp_set_last_use_time(m, time(0)); return(m); } sp_mark_stream_dead(m); } } } } /* * If we can't find a useful stream to use in pine_mail_open, we may * want to re-use one that is not actively being used even though it * is not on the same server. We'll have to close it and then re-use * the slot. */ if(!(flags & (SP_SAME | SP_MATCH))){ /* * Prefer streams marked SP_TEMPUSE and not LOCKED. * If SP_TEMPUSE is set in the flags arg then this is the * only loop we try. */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE) && !sp_flagged(m, SP_LOCKED)){ dprint((7, "sp_stream_get: found Not-SAME/TEMPUSE match, slot %d\n", i)); /* * We ping it in case there is new mail that we should * pass through our filters. Pine_mail_actually_close will * do that. */ (void) pine_mail_ping(m); return(m); } } /* * If SP_TEMPUSE is not set in the flags arg, then we will consider * streams which are not marked SP_TEMPUSE (but are still not * locked). Maybe we should use a round-robin sort of search * here so that we don't leave behind unused streams. Or maybe * we should keep track of when we used it and look for the LRU stream. */ if(!(flags & SP_TEMPUSE)){ for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED)){ dprint((7, "sp_stream_get: found Not-SAME/UNLOCKED match, slot %d\n", i)); /* * We ping it in case there is new mail that we should * pass through our filters. Pine_mail_actually_close will * do that. */ (void) pine_mail_ping(m); return(m); } } } } dprint((7, "sp_stream_get: no match found\n")); return(NULL); } void sp_end(void) { int i; MAILSTREAM *m; dprint((7, "sp_end\n")); for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m) pine_mail_actually_close(m); } if(ps_global->s_pool.streams) fs_give((void **) &ps_global->s_pool.streams); ps_global->s_pool.nstream = 0; } /* * Find a vacant slot to put this new stream in. * We are willing to close and kick out another stream as long as it isn't * LOCKED. However, we may find that there is no place to put this one * because all the slots are used and locked. For now, we'll return -1 * in that case and leave the new stream out of the pool. */ int sp_add(MAILSTREAM *stream, int usepool) { int i, slot = -1; MAILSTREAM *m; dprint((7, "sp_add(%s, %d)\n", (stream && stream->mailbox) ? stream->mailbox : "?", usepool)); if(!stream){ dprint((7, "sp_add: NULL stream\n")); return -1; } /* If this stream is already there, don't add it again */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m == stream){ slot = i; dprint((7, "sp_add: stream was already in slot %d\n", slot)); return 0; } } if(usepool && !sp_flagged(stream, SP_PERMLOCKED) && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){ dprint((7, "sp_add: reached max implicit SP_USEPOOL of %d\n", ps_global->s_pool.max_remstream)); return -1; } /* Look for an unused slot */ for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(!m){ slot = i; dprint((7, "sp_add: using empty slot %d\n", slot)); break; } } /* else, allocate more space */ if(slot < 0){ ps_global->s_pool.nstream++; slot = ps_global->s_pool.nstream - 1; if(ps_global->s_pool.streams){ fs_resize((void **) &ps_global->s_pool.streams, ps_global->s_pool.nstream * sizeof(*ps_global->s_pool.streams)); ps_global->s_pool.streams[slot] = NULL; } else{ ps_global->s_pool.streams = (MAILSTREAM **) fs_get(ps_global->s_pool.nstream * sizeof(*ps_global->s_pool.streams)); memset(ps_global->s_pool.streams, 0, ps_global->s_pool.nstream * sizeof(*ps_global->s_pool.streams)); } dprint((7, "sp_add: allocate more space, using new slot %d\n", slot)); } if(slot >= 0 && slot < ps_global->s_pool.nstream){ ps_global->s_pool.streams[slot] = stream; return 0; } else{ dprint((7, "sp_add: failed to find a slot!\n")); return -1; } } /* * Simply remove this stream from the stream pool. */ void sp_delete(MAILSTREAM *stream) { int i; MAILSTREAM *m; if(!stream) return; dprint((7, "sp_delete(%s)\n", (stream && stream->mailbox) ? stream->mailbox : "?")); /* * There are some global stream pointers that we have to worry * about before deleting the stream. */ /* first, mail_stream is the global currently open folder */ if(ps_global->mail_stream == stream) ps_global->mail_stream = NULL; /* remote address books may have open stream pointers */ note_closed_adrbk_stream(stream); if(pith_opt_closing_stream) (*pith_opt_closing_stream)(stream); for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m == stream){ ps_global->s_pool.streams[i] = NULL; dprint((7, "sp_delete: stream removed from slot %d\n", i)); return; } } } /* * Returns 1 if any locked userfldr is dead, 0 if all alive. */ int sp_a_locked_stream_is_dead(void) { int i, ret = 0; MAILSTREAM *m; for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR) && sp_dead_stream(m)) ret++; } return(ret); } /* * Returns 1 if any locked stream is changed, 0 otherwise */ int sp_a_locked_stream_changed(void) { int i, ret = 0; MAILSTREAM *m; for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR) && sp_mail_box_changed(m)) ret++; } return(ret); } /* * Returns the inbox stream or NULL. */ MAILSTREAM * sp_inbox_stream(void) { int i; MAILSTREAM *m, *ret = NULL; for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_flagged(m, SP_INBOX)) ret = m; } return(ret); } /* * Make sure that the sp_data per-stream data storage area exists. * * Returns a handle to the sp_data data unless stream is NULL, * in which case NULL is returned */ PER_STREAM_S ** sp_data(MAILSTREAM *stream) { PER_STREAM_S **pss = NULL; if(stream){ if(*(pss = (PER_STREAM_S **) &stream->sparep) == NULL){ *pss = (PER_STREAM_S *) fs_get(sizeof(PER_STREAM_S)); memset(*pss, 0, sizeof(PER_STREAM_S)); reset_check_point(stream); } } return(pss); } /* * Returns a pointer to the msgmap associated with the argument stream. * * If the PER_STREAM_S data or the msgmap does not already exist, it will be * created. */ MSGNO_S * sp_msgmap(MAILSTREAM *stream) { MSGNO_S **msgmap = NULL; PER_STREAM_S **pss = NULL; pss = sp_data(stream); if(pss && *pss && (*(msgmap = (MSGNO_S **) &(*pss)->msgmap) == NULL)) mn_init(msgmap, stream->nmsgs); return(msgmap ? *msgmap : NULL); } void sp_free_callback(void **sparep) { PER_STREAM_S **pss; MAILSTREAM *stream = NULL, *m; int i; pss = (PER_STREAM_S **) sparep; if(pss && *pss){ /* * It is possible that this has been called from c-client when * we weren't expecting it. We need to clean up the stream pool * entries if the stream that goes with this pointer is in the * stream pool somewhere. */ for(i = 0; !stream && i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(sparep && *sparep && m && m->sparep == *sparep) stream = m; } if(stream){ if(ps_global->mail_stream == stream) ps_global->mail_stream = NULL; sp_delete(stream); } sp_free(pss); } } /* * Free the data but don't mess with the stream pool. */ void sp_free(PER_STREAM_S **pss) { if(pss && *pss){ if((*pss)->msgmap){ if(ps_global->msgmap == (*pss)->msgmap) ps_global->msgmap = NULL; mn_give(&(*pss)->msgmap); } if((*pss)->fldr) fs_give((void **) &(*pss)->fldr); fs_give((void **) pss); } } /*---------------------------------------------------------------------- See if stream can be used for a mailbox name Accepts: mailbox name candidate stream Returns: stream if it can be used, else NIL This is called to weed out unnecessary use of c-client streams. In other words, to help facilitate re-use of streams. This code is very similar to the same_remote_mailboxes code below, which is used in pine_mail_open. That code compares two mailbox names. One is usually from the config file and the other is either from the config file or is typed in. Here and in same_stream_and_mailbox below, we're comparing an open stream to a name instead of two names. We could conceivably use same_remote_mailboxes to compare stream->mailbox to name, but it isn't exactly the same and the differences may be important. Some stuff that happens here seems wrong, but it isn't easy to fix. Having !mb_n.port count as a match to any mb_s.port isn't right. It should only match if mb_s.port is equal to the default, but the default isn't something that is available to us. The same thing is done in c-client in the mail_usable_network_stream() routine, and it isn't right there, either. The semantics of a missing user are also suspect, because just like with port, a default is used. ----*/ MAILSTREAM * same_stream(char *name, MAILSTREAM *stream) { NETMBX mb_s, mb_n, mb_o; if(stream && stream->mailbox && *stream->mailbox && name && *name && !(sp_dead_stream(stream)) && mail_valid_net_parse(stream->mailbox, &mb_s) && mail_valid_net_parse(stream->original_mailbox, &mb_o) && mail_valid_net_parse(name, &mb_n) && !strucmp(mb_n.service, mb_s.service) && (!strucmp(mb_n.host, mb_o.host) /* s is already canonical */ || !strucmp(canonical_name(mb_n.host), mb_s.host)) && (!mb_n.port || mb_n.port == mb_s.port) && mb_n.anoflag == stream->anonymous && ((mb_n.user && *mb_n.user && mb_s.user && !strcmp(mb_n.user, mb_s.user)) || ((!mb_n.user || !*mb_n.user) && mb_s.user && ((ps_global->VAR_USER_ID && !strcmp(ps_global->VAR_USER_ID, mb_s.user)) || (!ps_global->VAR_USER_ID && ps_global->ui.login[0] && !strcmp(ps_global->ui.login, mb_s.user)))) || (!((mb_n.user && *mb_n.user) || (mb_s.user && *mb_s.user)) && stream->anonymous)) && (struncmp(mb_n.service, "imap", 4) ? 1 : strcmp(imap_host(stream), ".NO-IMAP-CONNECTION."))){ dprint((7, "same_stream: name->%s == stream->%s: yes\n", name ? name : "?", (stream && stream->mailbox) ? stream->mailbox : "NULL")); return(stream); } dprint((7, "same_stream: name->%s == stream->%s: no dice\n", name ? name : "?", (stream && stream->mailbox) ? stream->mailbox : "NULL")); return(NULL); } /*---------------------------------------------------------------------- See if this stream has the named mailbox selected. Accepts: mailbox name candidate stream Returns: stream if it can be used, else NIL ----*/ MAILSTREAM * same_stream_and_mailbox(char *name, MAILSTREAM *stream) { NETMBX mb_s, mb_n; if(same_stream(name, stream) && mail_valid_net_parse(stream->mailbox, &mb_s) && mail_valid_net_parse(name, &mb_n) && (mb_n.mailbox && mb_s.mailbox && (!strcmp(mb_n.mailbox,mb_s.mailbox) /* case depend except INBOX */ || (!strucmp(mb_n.mailbox,"INBOX") && !strucmp(mb_s.mailbox,"INBOX"))))){ dprint((7, "same_stream_and_mailbox: name->%s == stream->%s: yes\n", name ? name : "?", (stream && stream->mailbox) ? stream->mailbox : "NULL")); return(stream); } dprint((7, "same_stream_and_mailbox: name->%s == stream->%s: no dice\n", name ? name : "?", (stream && stream->mailbox) ? stream->mailbox : "NULL")); return(NULL); } /* * Args -- name1 and name2 are remote mailbox names. * * Returns -- True if names refer to same mailbox accessed in same way * False if not * * This has some very similar code to same_stream_and_mailbox but we're not * quite ready to discard the differences. * The treatment of the port and the user is not quite the same. */ int same_remote_mailboxes(char *name1, char *name2) { NETMBX mb1, mb2; char *cn1; /* * Probably we should allow !port equal to default port, but we don't * know how to get the default port. To match what c-client does we * allow !port to be equal to anything. */ return(name1 && IS_REMOTE(name1) && name2 && IS_REMOTE(name2) && mail_valid_net_parse(name1, &mb1) && mail_valid_net_parse(name2, &mb2) && !strucmp(mb1.service, mb2.service) && (!strucmp(mb1.host, mb2.host) /* just to save DNS lookups */ || !strucmp(cn1=canonical_name(mb1.host), mb2.host) || !strucmp(cn1, canonical_name(mb2.host))) && (!mb1.port || !mb2.port || mb1.port == mb2.port) && mb1.anoflag == mb2.anoflag && mb1.mailbox && mb2.mailbox && (!strcmp(mb1.mailbox, mb2.mailbox) || (!strucmp(mb1.mailbox,"INBOX") && !strucmp(mb2.mailbox,"INBOX"))) && ((mb1.user && *mb1.user && mb2.user && *mb2.user && !strcmp(mb1.user, mb2.user)) || (!(mb1.user && *mb1.user) && !(mb2.user && *mb2.user)) || (!(mb1.user && *mb1.user) && ((ps_global->VAR_USER_ID && !strcmp(ps_global->VAR_USER_ID, mb2.user)) || (!ps_global->VAR_USER_ID && ps_global->ui.login[0] && !strcmp(ps_global->ui.login, mb2.user)))) || (!(mb2.user && *mb2.user) && ((ps_global->VAR_USER_ID && !strcmp(ps_global->VAR_USER_ID, mb1.user)) || (!ps_global->VAR_USER_ID && ps_global->ui.login[0] && !strcmp(ps_global->ui.login, mb1.user)))))); } int is_imap_stream(MAILSTREAM *stream) { return(stream && stream->dtb && stream->dtb->name && !strcmp(stream->dtb->name, "imap")); } int modern_imap_stream(MAILSTREAM *stream) { return(is_imap_stream(stream) && LEVELIMAP4rev1(stream)); } /*---------------------------------------------------------------------- Check and see if all the stream are alive Returns: 0 if there was no change >0 if streams have died since last call Also outputs a message that the streams have died ----*/ int streams_died(void) { int rv = 0; int i; MAILSTREAM *m; unsigned char *folder; for(i = 0; i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && sp_dead_stream(m)){ if(sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)){ if(!sp_noticed_dead_stream(m)){ rv++; sp_set_noticed_dead_stream(m, 1); folder = folder_name_decoded((unsigned char *)STREAMNAME(m)); q_status_message1(SM_ORDER | SM_DING, 3, 3, _("MAIL FOLDER \"%s\" CLOSED DUE TO ACCESS ERROR"), short_str(pretty_fn((char *) folder) ? pretty_fn((char *) folder) : "?", tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots)); dprint((6, "streams_died: locked: \"%s\"\n", folder)); if(rv == 1){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Folder \"%s\" is Closed"), short_str(pretty_fn((char *)folder) ? pretty_fn((char *)folder) : "?", tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots)); if(pith_opt_icon_text) (*pith_opt_icon_text)(tmp_20k_buf, IT_MCLOSED); } if(folder) fs_give((void **)&folder); } } else{ if(!sp_noticed_dead_stream(m)){ sp_set_noticed_dead_stream(m, 1); folder = (unsigned char *) STREAMNAME(m); /* * If a cached stream died and then we tried to use it * it could cause problems. We could warn about it here * but it may be confusing because it might be * unrelated to what the user is doing and not cause * any problem at all. */ #if 0 if(sp_flagged(m, SP_USEPOOL)) q_status_message(SM_ORDER, 3, 3, "Warning: Possible problem accessing remote data, connection died."); #endif dprint((6, "streams_died: not locked: \"%s\"\n", folder)); } pine_mail_actually_close(m); } } } return(rv); } /* Some stream is locked checks to see if there is any stream for which we * are in a callback from c-client */ int some_stream_is_locked(void) { int rv = 0, i; MAILSTREAM *m; for(i = 0; rv == 0 && i < ps_global->s_pool.nstream; i++){ m = ps_global->s_pool.streams[i]; if(m && m->lock) rv++; } return(rv); } /* * Very simple version of appenduid_cb until we need something * more complex. */ static imapuid_t last_append_uid; void appenduid_cb(char *mailbox,unsigned long uidvalidity, SEARCHSET *set) { last_append_uid = set ? set->first : 0L; } imapuid_t get_last_append_uid(void) { return last_append_uid; } /* * mail_cmd_stream - return a stream suitable for mail_lsub, * mail_subscribe, and mail_unsubscribe * */ MAILSTREAM * mail_cmd_stream(CONTEXT_S *context, int *closeit) { char tmp[MAILTMPLEN]; *closeit = 1; (void) context_apply(tmp, context, "x", sizeof(tmp)); return(pine_mail_open(NULL, tmp, OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE, NULL)); } /* * This is so we can replace the old rfc822_ routines like rfc822_header_line * with the new version that checks bounds, like rfc822_output_header_line. * This routine is called when would be a bounds overflow, which we simply log * and go on with the truncated data. */ long dummy_soutr(void *stream, char *string) { dprint((2, "dummy_soutr unexpected call, caught overflow\n")); return LONGT; }