1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: stream.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
3 #endif
4 
5 /*
6  * ========================================================================
7  * Copyright 2013-2021 Eduardo Chappa
8  * Copyright 2006-2008 University of Washington
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *     http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * ========================================================================
17  */
18 
19 /*======================================================================
20      stream.c
21      Implements the Pine mail stream management routines
22      and c-client wrapper functions
23   ====*/
24 
25 
26 #include "../pith/headers.h"
27 #include "../pith/stream.h"
28 #include "../pith/state.h"
29 #include "../pith/conf.h"
30 #include "../pith/flag.h"
31 #include "../pith/msgno.h"
32 #include "../pith/adrbklib.h"
33 #include "../pith/status.h"
34 #include "../pith/newmail.h"
35 #include "../pith/detach.h"
36 #include "../pith/folder.h"
37 #include "../pith/mailcmd.h"
38 #include "../pith/util.h"
39 #include "../pith/news.h"
40 #include "../pith/sequence.h"
41 #include "../pith/options.h"
42 #include "../pith/mimedesc.h"
43 
44 
45 void     (*pith_opt_closing_stream)(MAILSTREAM *);
46 
47 
48 /*
49  * Internal prototypes
50  */
51 void	 reset_stream_view_state(MAILSTREAM *);
52 void	 carefully_reset_sp_flags(MAILSTREAM *, unsigned long);
53 char	*partial_text_gets(readfn_t, void *, unsigned long, GETS_DATA *);
54 void	 mail_list_internal(MAILSTREAM *, char *, char *);
55 int	 recent_activity(MAILSTREAM *);
56 int	 hibit_in_searchpgm(SEARCHPGM *);
57 int	 hibit_in_strlist(STRINGLIST *);
58 int	 hibit_in_header(SEARCHHEADER *);
59 int	 hibit_in_sizedtext(SIZEDTEXT *);
60 int      sp_nusepool_notperm(void);
61 int      sp_add(MAILSTREAM *, int);
62 void     sp_delete(MAILSTREAM *);
63 void     sp_free(PER_STREAM_S **);
64 
65 
66 static FETCH_READC_S *g_pft_desc;
67 
68 
69 MAILSTATUS *pine_cached_status;  /* implement status for #move folder */
70 
71 
72 
73 /*
74  * Pine wrapper around mail_open. If we have the PREFER_ALT_AUTH flag turned
75  * on, we need to set the TRYALT flag before trying the open.
76  * This routine manages the stream pool, too. It tries to re-use existing
77  * streams instead of opening new ones, or maybe it will leave one open and
78  * use a new one if that seems to make more sense. Pine_mail_close leaves
79  * streams open so that they may be re-used. Each pine_mail_open should have
80  * a matching pine_mail_close (or possible pine_mail_actually_close) somewhere
81  * that goes with it.
82  *
83  * Args:
84  *      stream -- A possible stream for recycling. This isn't usually the
85  *                way recycling happens. Usually it is automatic.
86  *     mailbox -- The mailbox to be opened.
87  *   openflags -- Flags passed here to modify the behavior.
88  *    retflags -- Flags returned from here. SP_MATCH will be lit if that is
89  *                what happened. If SP_MATCH is lit then SP_LOCKED may also
90  *                be lit if the matched stream was already locked when
91  *                we got here.
92  */
93 MAILSTREAM *
pine_mail_open(MAILSTREAM * stream,char * mailbox,long int openflags,long int * retflags)94 pine_mail_open(MAILSTREAM *stream, char *mailbox, long int openflags, long int *retflags)
95 {
96     MAILSTREAM *retstream = NULL;
97     DRIVER     *d;
98     int         permlocked = 0, is_inbox = 0, usepool = 0, tempuse = 0, uf = 0;
99     unsigned long flags;
100     char      **lock_these, *target = NULL;
101     static unsigned long streamcounter = 0;
102 
103     dprint((7,
104     "pine_mail_open: opening \"%s\"%s openflags=0x%x %s%s%s%s%s%s%s%s%s (%s)\n",
105 	   mailbox ? mailbox : "(NULL)",
106 	   stream ? "" : " (stream was NULL)",
107 	   openflags,
108 	   openflags & OP_HALFOPEN   ? " OP_HALFOPEN"   : "",
109 	   openflags & OP_READONLY   ? " OP_READONLY"   : "",
110 	   openflags & OP_SILENT     ? " OP_SILENT"     : "",
111 	   openflags & OP_DEBUG      ? " OP_DEBUG"      : "",
112 	   openflags & SP_PERMLOCKED ? " SP_PERMLOCKED" : "",
113 	   openflags & SP_INBOX      ? " SP_INBOX"      : "",
114 	   openflags & SP_USERFLDR   ? " SP_USERFLDR"   : "",
115 	   openflags & SP_USEPOOL    ? " SP_USEPOOL"    : "",
116 	   openflags & SP_TEMPUSE    ? " SP_TEMPUSE"    : "",
117 	   debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
118 
119     if(retflags)
120       *retflags = 0L;
121 
122     if(ps_global->user_says_cancel){
123 	dprint((7, "pine_mail_open: cancelled by user\n"));
124 	return(retstream);
125     }
126 
127     is_inbox    = openflags & SP_INBOX;
128     uf          = openflags & SP_USERFLDR;
129 
130     /* inbox is still special, assume that we want to permlock it */
131     permlocked  = (is_inbox || openflags & SP_PERMLOCKED) ? 1 : 0;
132 
133     /* check to see if user wants this folder permlocked */
134     for(lock_these = ps_global->VAR_PERMLOCKED;
135 	uf && !permlocked && lock_these && *lock_these; lock_these++){
136 	char *p = NULL, *dummy = NULL, *lt, *lname, *mname;
137 	char  tmp1[MAILTMPLEN], tmp2[MAILTMPLEN];
138 
139 	/* there isn't really a pair, it just dequotes for us */
140 	get_pair(*lock_these, &dummy, &p, 0, 0);
141 
142 	/*
143 	 * Check to see if this is an incoming nickname and replace it
144 	 * with the full name.
145 	 */
146 	if(!(p && ps_global->context_list
147 	     && ps_global->context_list->use & CNTXT_INCMNG
148 	     && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list), 0))))
149 	  lt = p;
150 
151 	if(dummy)
152 	  fs_give((void **) &dummy);
153 
154 	if(lt && mailbox
155 	   && (same_remote_mailboxes(mailbox, lt)
156 	       ||
157 	       (!IS_REMOTE(mailbox)
158 	        && (lname=mailboxfile(tmp1, lt))
159 	        && (mname=mailboxfile(tmp2, mailbox))
160 	        && !strcmp(lname, mname))))
161 	  permlocked++;
162 
163 	if(p)
164 	  fs_give((void **) &p);
165     }
166 
167     /*
168      * Only cache if remote, not nntp, not pop, and caller asked us to.
169      * It might make sense to do some caching for nntp and pop, as well, but
170      * we aren't doing it right now. For example, an nntp stream open to
171      * one group could be reused for another group. An open pop stream could
172      * be used for mail_copy_full.
173      *
174      * An implication of doing only imap here is that sp_stream_get will only
175      * be concerned with imap streams.
176      */
177     if((d = mail_valid (NIL, mailbox, (char *) NIL)) && !strcmp(d->name, "imap")){
178 	usepool = openflags & SP_USEPOOL;
179 	tempuse = openflags & SP_TEMPUSE;
180     }
181     else{
182 	if(IS_REMOTE(mailbox)){
183 	    dprint((9, "pine_mail_open: not cacheable: %s\n", !d ? "no driver?" : d->name ? d->name : "?" ));
184 	}
185 	else{
186 	    if(permlocked || (openflags & OP_READONLY)){
187 		/*
188 		 * This is a strange case. We want to allow stay-open local
189 		 * folders, but they don't fit into the rest of the framework
190 		 * well. So we'll look for it being already open in this case
191 		 * and special-case it (the already_open_stream() case
192 		 * below).
193 		 */
194 		dprint((9,
195    "pine_mail_open: not cacheable: not remote, but check for local stream\n"));
196 	    }
197 	    else{
198 		dprint((9,
199 		       "pine_mail_open: not cacheable: not remote\n"));
200 	    }
201 	}
202     }
203 
204     /* If driver doesn't support halfopen, just open it. */
205     if(d && (openflags & OP_HALFOPEN) && !(d->flags & DR_HALFOPEN)){
206 	openflags &= ~OP_HALFOPEN;
207 	dprint((9,
208 	       "pine_mail_open: turning off OP_HALFOPEN flag\n"));
209     }
210 
211     /*
212      * Some of the flags are pine's, the rest are meant for mail_open.
213      * We've noted the pine flags, now remove them before we call mail_open.
214      */
215     openflags &= ~(SP_USEPOOL | SP_TEMPUSE | SP_INBOX
216 		   | SP_PERMLOCKED | SP_USERFLDR);
217 
218 #ifdef	DEBUG
219     if(ps_global->debug_imap > 3 || ps_global->debugmem)
220       openflags |= OP_DEBUG;
221 #endif
222 
223     if(F_ON(F_PREFER_ALT_AUTH, ps_global)){
224 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
225 	   && !strcmp(d->name, "imap"))
226 	  openflags |= OP_TRYALT;
227     }
228 
229     if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)){
230 	char source[MAILTMPLEN];
231 	if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
232 	    DRIVER *d;
233 	    if((d = mail_valid(NIL, source, (char *) NIL))
234 	       && (!strcmp(d->name, "news")
235 		   || !strcmp(d->name, "nntp")))
236 	      openflags |= OP_MULNEWSRC;
237 	}
238 	else if((d = mail_valid(NIL, mailbox, (char *) NIL))
239 	   && !strcmp(d->name, "nntp"))
240 	  openflags |= OP_MULNEWSRC;
241     }
242 
243     /*
244      * One of the problems is that the new-style stream caching (the
245      * sp_stream_get stuff) may conflict with some of the old-style caching
246      * (the passed in argument stream) that is still in the code. We should
247      * probably eliminate the old-style caching, but some of it is still useful,
248      * especially if it deals with something other than IMAP. We want to prevent
249      * mistakes caused by conflicts between the two styles. In particular, we
250      * don't want to have a new-style cached stream re-opened because of the
251      * old-style caching code. This can happen if a stream is passed in that
252      * is not usable, and then a new stream is opened because the passed in
253      * stream causes us to bypass the new caching code. Play it safe. If it
254      * is an IMAP stream, just close it. This should leave it in the new-style
255      * cache anyway, causing no loss. Maybe not if the cache wasn't large
256      * enough to have it in there in the first place, in which case we get
257      * a possibly unnecessary close and open. If it isn't IMAP we still have
258      * to worry about it because it will cause us to bypass the caching code.
259      * So if the stream isn't IMAP but the mailbox we're opening is, close it.
260      * The immediate alternative would be to try to emulate the code in
261      * mail_open that checks whether it is re-usable or not, but that is
262      * dangerous if that code changes on us.
263      */
264     if(stream){
265 	if(is_imap_stream(stream)
266 	   || ((d = mail_valid (NIL, mailbox, (char *) NIL))
267 	       && !strcmp(d->name, "imap"))){
268 	    if(is_imap_stream(stream)){
269 		dprint((7,
270 		       "pine_mail_open: closing passed in IMAP stream %s\n",
271 		       stream->mailbox ? stream->mailbox : "?"));
272 	    }
273 	    else{
274 		dprint((7,
275 		       "pine_mail_open: closing passed in non-IMAP stream %s\n",
276 		       stream->mailbox ? stream->mailbox : "?"));
277 	    }
278 
279 	    pine_mail_close(stream);
280 	    stream = NULL;
281 	}
282     }
283 
284     /*
285      * Maildrops are special. The mailbox name will be a #move name. If the
286      * target of the maildrop is an IMAP folder we want to be sure it isn't
287      * already open in another cached stream, to avoid double opens. This
288      * could have happened if the user opened it manually as the target
289      * instead of as a maildrop. It could also be a side-effect of marking
290      * an answered flag after a reply.
291      */
292     target = NULL;
293     if(check_for_move_mbox(mailbox, NULL, 0, &target)){
294 	MAILSTREAM *targetstream = NULL;
295 
296 	if((d = mail_valid (NIL, target, (char *) NIL)) && !strcmp(d->name, "imap")){
297 	    targetstream = sp_stream_get(target, SP_MATCH | SP_RO_OK);
298 	    if(targetstream){
299 		dprint((9, "pine_mail_open: close previously opened target of maildrop\n"));
300 		pine_mail_actually_close(targetstream);
301 	    }
302 	}
303     }
304 
305     if((usepool && !stream && permlocked)
306        || (!usepool && (permlocked || (openflags & OP_READONLY))
307 	   && (retstream = already_open_stream(mailbox, AOS_NONE)))){
308 	if(retstream)
309 	  stream = retstream;
310 	else
311 	  stream = sp_stream_get(mailbox,
312 			SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0));
313 	if(stream){
314 	    flags = SP_LOCKED
315 		    | (usepool    ? SP_USEPOOL    : 0)
316 		    | (permlocked ? SP_PERMLOCKED : 0)
317 		    | (is_inbox   ? SP_INBOX      : 0)
318 		    | (uf         ? SP_USERFLDR   : 0)
319 		    | (tempuse    ? SP_TEMPUSE    : 0);
320 
321 	    /*
322 	     * If the stream wasn't already locked, then we reset it so it
323 	     * looks like we are reopening it. We have to worry about recent
324 	     * messages since they will still be recent, if that affects us.
325 	     */
326 	    if(!(sp_flags(stream) & SP_LOCKED))
327 	      reset_stream_view_state(stream);
328 
329 	    if(retflags){
330 		*retflags |= SP_MATCH;
331 		if(sp_flags(stream) & SP_LOCKED)
332 		  *retflags |= SP_LOCKED;
333 	    }
334 
335 	    if(sp_flags(stream) & SP_LOCKED
336 	       && sp_flags(stream) & SP_USERFLDR
337 	       && !(flags & SP_USERFLDR)){
338 		sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
339 		dprint((7,
340 		       "pine_mail_open: permlocked: ref cnt up to %d\n",
341 		       sp_ref_cnt(stream)));
342 	    }
343 	    else if(sp_ref_cnt(stream) <= 0){
344 		sp_set_ref_cnt(stream, 1);
345 		dprint((7,
346 		       "pine_mail_open: permexact: ref cnt set to %d\n",
347 		       sp_ref_cnt(stream)));
348 	    }
349 
350 	    carefully_reset_sp_flags(stream, flags);
351 
352 	    if(stream->silent && !(openflags & OP_SILENT))
353 	      stream->silent = NIL;
354 
355 	    dprint((9, "pine_mail_open: stream was already open\n"));
356 	    if(stream && stream->dtb && stream->dtb->name
357 	       && !strcmp(stream->dtb->name, "imap")){
358 		dprint((7, "pine_mail_open: next TAG %08lx\n",
359 		       stream->gensym));
360 	    }
361 
362 	    return(stream);
363 	}
364     }
365 
366     if(usepool && !stream){
367 	/*
368 	 * First, we look for an exact match, a stream which is already
369 	 * open to the mailbox we are trying to re-open, and we use that.
370 	 * Skip permlocked only because we did it above already.
371 	 */
372 	if(!permlocked)
373 	  stream = sp_stream_get(mailbox,
374 			SP_MATCH | ((openflags & OP_READONLY) ? SP_RO_OK : 0));
375 
376 	if(stream){
377 	    flags = SP_LOCKED
378 		    | (usepool    ? SP_USEPOOL    : 0)
379 		    | (permlocked ? SP_PERMLOCKED : 0)
380 		    | (is_inbox   ? SP_INBOX      : 0)
381 		    | (uf         ? SP_USERFLDR   : 0)
382 		    | (tempuse    ? SP_TEMPUSE    : 0);
383 
384 	    /*
385 	     * If the stream wasn't already locked, then we reset it so it
386 	     * looks like we are reopening it. We have to worry about recent
387 	     * messages since they will still be recent, if that affects us.
388 	     */
389 	    if(!(sp_flags(stream) & SP_LOCKED))
390 	      reset_stream_view_state(stream);
391 
392 	    if(retflags){
393 		*retflags |= SP_MATCH;
394 		if(sp_flags(stream) & SP_LOCKED)
395 		  *retflags |= SP_LOCKED;
396 	    }
397 
398 	    if(sp_flags(stream) & SP_LOCKED
399 	       && sp_flags(stream) & SP_USERFLDR
400 	       && !(flags & SP_USERFLDR)){
401 		sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
402 		dprint((7,
403 		       "pine_mail_open: matched: ref cnt up to %d\n",
404 		       sp_ref_cnt(stream)));
405 	    }
406 	    else if(sp_ref_cnt(stream) <= 0){
407 		sp_set_ref_cnt(stream, 1);
408 		dprint((7,
409 		       "pine_mail_open: exact: ref cnt set to %d\n",
410 		       sp_ref_cnt(stream)));
411 	    }
412 
413 	    carefully_reset_sp_flags(stream, flags);
414 
415 	    /*
416 	     * We may be re-using a stream that was previously open
417 	     * with OP_SILENT and now we don't want OP_SILENT.
418 	     * We don't turn !silent into silent because the !silentness
419 	     * could be important in the original context (for example,
420 	     * silent suppresses mm_expunged calls).
421 	     *
422 	     * WARNING: we're messing with c-client internals.
423 	     */
424 	    if(stream->silent && !(openflags & OP_SILENT))
425 	      stream->silent = NIL;
426 
427 	    dprint((9, "pine_mail_open: stream already open\n"));
428 	    if(stream && stream->dtb && stream->dtb->name
429 	       && !strcmp(stream->dtb->name, "imap")){
430 		dprint((7, "pine_mail_open: next TAG %08lx\n",
431 		       stream->gensym));
432 	    }
433 
434 	    return(stream);
435 	}
436 
437 	/*
438 	 * No exact match, look for a stream which is open to the same
439 	 * server and marked for TEMPUSE.
440 	 */
441 	stream = sp_stream_get(mailbox, SP_SAME | SP_TEMPUSE);
442 	if(stream){
443 	    dprint((9,
444 		    "pine_mail_open: attempting to re-use TEMP stream\n"));
445 	}
446 	/*
447 	 * No SAME/TEMPUSE stream so we look to see if there is an
448 	 * open slot available (we're not yet at max_remstream). If there
449 	 * is an open slot, we'll just open a new stream and put it there.
450 	 * If not, we'll go inside this conditional.
451 	 */
452 	else if(!permlocked
453 		&& sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){
454 	    dprint((9,
455 		    "pine_mail_open: no empty slots\n"));
456 	    /*
457 	     * No empty remote slots available. See if there is a
458 	     * TEMPUSE stream that is open but to the wrong server.
459 	     */
460 	    stream = sp_stream_get(mailbox, SP_TEMPUSE);
461 	    if(stream){
462 		/*
463 		 * We will close this stream and use the empty slot
464 		 * that that creates.
465 		 */
466 		dprint((9,
467 	    "pine_mail_open: close a TEMPUSE stream and re-use that slot\n"));
468 		pine_mail_actually_close(stream);
469 		stream = NULL;
470 	    }
471 	    else{
472 
473 		/*
474 		 * Still no luck. Look for a stream open to the same
475 		 * server that is just not locked. This would be a
476 		 * stream that might be reusable in the future, but we
477 		 * need it now instead.
478 		 */
479 		stream = sp_stream_get(mailbox, SP_SAME | SP_UNLOCKED);
480 		if(stream){
481 		    dprint((9,
482 			    "pine_mail_open: attempting to re-use stream\n"));
483 		}
484 		else{
485 		    /*
486 		     * We'll take any UNLOCKED stream and re-use it.
487 		     */
488 		    stream = sp_stream_get(mailbox, 0);
489 		    if(stream){
490 			/*
491 			 * We will close this stream and use the empty slot
492 			 * that that creates.
493 			 */
494 			dprint((9,
495 	    "pine_mail_open: close an UNLOCKED stream and re-use the slot\n"));
496 			pine_mail_actually_close(stream);
497 			stream = NULL;
498 		    }
499 		    else{
500 			if(ps_global->s_pool.max_remstream){
501 			  dprint((9, "pine_mail_open: all USEPOOL slots full of LOCKED streams, nothing to use\n"));
502 			}
503 			else{
504 			  dprint((9, "pine_mail_open: no caching, max_remstream == 0\n"));
505 			}
506 
507 			usepool = 0;
508 			tempuse = 0;
509 			if(permlocked){
510 			    permlocked = 0;
511 			    dprint((2,
512 		    "pine_mail_open5: Can't mark folder Stay-Open: at max-remote limit\n"));
513 			    q_status_message1(SM_ORDER, 3, 5,
514 		  "Can't mark folder Stay-Open: reached max-remote limit (%s)",
515 		  comatose((long) ps_global->s_pool.max_remstream));
516 			}
517 		    }
518 		}
519 	    }
520 	}
521 	else{
522 	    dprint((9,
523 		    "pine_mail_open: there is an empty slot to use\n"));
524 	}
525 
526 	/*
527 	 * We'll make an assumption here. If we were asked to halfopen a
528 	 * stream then we'll assume that the caller doesn't really care if
529 	 * the stream is halfopen or if it happens to be open to some mailbox
530 	 * already. They are just saying halfopen because they don't need to
531 	 * SELECT a mailbox. That's the assumption, anyway.
532 	 */
533 	if(openflags & OP_HALFOPEN && stream){
534 	    dprint((9,
535 	     "pine_mail_open: asked for HALFOPEN so returning stream as is\n"));
536 	    sp_set_ref_cnt(stream, sp_ref_cnt(stream)+1);
537 	    dprint((7,
538 		   "pine_mail_open: halfopen: ref cnt up to %d\n",
539 		   sp_ref_cnt(stream)));
540 	    if(stream && stream->dtb && stream->dtb->name
541 	       && !strcmp(stream->dtb->name, "imap")){
542 		dprint((7, "pine_mail_open: next TAG %08lx\n",
543 		       stream->gensym));
544 	    }
545 
546 	    if(stream->silent && !(openflags & OP_SILENT))
547 	      stream->silent = NIL;
548 
549 	    return(stream);
550 	}
551 
552 	/*
553 	 * We're going to SELECT another folder with this open stream.
554 	 */
555 	if(stream){
556 	    /*
557 	     * We will have just pinged the stream to make sure it is
558 	     * still alive. That ping may have discovered some new mail.
559 	     * Before unselecting the folder, we should process the filters
560 	     * for that new mail.
561 	     */
562 	    if(!sp_flagged(stream, SP_LOCKED)
563 	       && !sp_flagged(stream, SP_USERFLDR)
564 	       && sp_new_mail_count(stream))
565 	      process_filter_patterns(stream, sp_msgmap(stream),
566 				      sp_new_mail_count(stream));
567 
568 	    if(stream && stream->dtb && stream->dtb->name
569 	       && !strcmp(stream->dtb->name, "imap")){
570 		dprint((7,
571 		   "pine_mail_open: cancel idle timer: TAG %08lx (%s)\n",
572 		   stream->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
573 	    }
574 
575 	    /*
576 	     * We need to reset the counters and everything.
577 	     * The easiest way to do that is just to delete all of the
578 	     * sp_s data and let the open fill it in correctly for
579 	     * the new folder.
580 	     */
581 	    sp_free((PER_STREAM_S **) &stream->sparep);
582 	}
583     }
584 
585     /*
586      * When we pass a stream to mail_open, it will either re-use it or
587      * close it.
588      */
589     retstream = mail_open(stream, mailbox, openflags);
590 
591     if(retstream){
592 
593 	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));
594 
595 	/*
596 	 * So it is easier to figure out which command goes with which
597 	 * stream when debugging, change the tag associated with each stream.
598 	 * Of course, this will come after the SELECT, so the startup IMAP
599 	 * commands will have confusing tags.
600 	 */
601 	if(retstream != stream && retstream->dtb && retstream->dtb->name
602 	   && !strcmp(retstream->dtb->name, "imap")){
603 	    retstream->gensym += (streamcounter * 0x1000000);
604 	    streamcounter = (streamcounter + 1) % (8 * 16);
605 	}
606 
607 	if(retstream && retstream->dtb && retstream->dtb->name
608 	   && !strcmp(retstream->dtb->name, "imap")){
609 	    dprint((7, "pine_mail_open: next TAG %08lx\n",
610 		   retstream->gensym));
611 	}
612 
613 	/*
614 	 * Catch the case where our test up above (where usepool was set)
615 	 * did not notice that this was news, but that we can tell once
616 	 * we've opened the stream. (One such case would be an imap proxy
617 	 * to an nntp server.)  Remove it from being cached here. There was
618 	 * a possible penalty for not noticing sooner. If all the usepool
619 	 * slots were full, we will have closed one of the UNLOCKED streams
620 	 * above, preventing us from future re-use of that stream.
621 	 * We could figure out how to do the test better with just the
622 	 * name. We could open the stream and then close the other one
623 	 * after the fact. Or we could just not worry about it since it is
624 	 * rare.
625 	 */
626 	if(IS_NEWS(retstream)){
627 	    usepool = 0;
628 	    tempuse = 0;
629 	}
630 
631 	/* make sure the message map has been instantiated */
632 	(void) sp_msgmap(retstream);
633 
634 	/*
635 	 * If a referral during the mail_open above causes a stream
636 	 * re-use which involves a BYE on an IMAP stream, then that
637 	 * BYE will have caused an mm_notify which will have
638 	 * instantiated the sp_data and set dead_stream! Trust that
639 	 * it is alive up to here and reset it to zero.
640 	 */
641 	sp_set_dead_stream(retstream, 0);
642 
643 	flags = SP_LOCKED
644 		| (usepool    ? SP_USEPOOL    : 0)
645 		| (permlocked ? SP_PERMLOCKED : 0)
646 		| (is_inbox   ? SP_INBOX      : 0)
647 		| (uf         ? SP_USERFLDR   : 0)
648 		| (tempuse    ? SP_TEMPUSE    : 0);
649 
650 	sp_flag(retstream, flags);
651 	sp_set_recent_since_visited(retstream, retstream->recent);
652 
653 	/* initialize the reference count */
654 	sp_set_ref_cnt(retstream, 1);
655 	dprint((7, "pine_mail_open: reset: ref cnt set to %d\n",
656 		   sp_ref_cnt(retstream)));
657 
658 	if(sp_add(retstream, usepool) != 0 && usepool){
659 	    usepool = 0;
660 	    tempuse = 0;
661 	    flags = SP_LOCKED
662 		    | (usepool    ? SP_USEPOOL    : 0)
663 		    | (permlocked ? SP_PERMLOCKED : 0)
664 		    | (is_inbox   ? SP_INBOX      : 0)
665 		    | (uf         ? SP_USERFLDR   : 0)
666 		    | (tempuse    ? SP_TEMPUSE    : 0);
667 
668 	    sp_flag(retstream, flags);
669 	    (void) sp_add(retstream, usepool);
670 	}
671 
672 
673 	/*
674 	 * When opening a newsgroup, c-client marks the messages up to the
675 	 * last Deleted as Unseen. If the feature news-approximates-new-status
676 	 * is on, we'd rather they be treated as Seen. That way, selecting New
677 	 * messages will give us the ones past the last Deleted. So we're going
678 	 * to change them to Seen. Since Seen is a session flag for news, making
679 	 * this change won't have any permanent effect. C-client also marks the
680 	 * messages after the last deleted Recent, which is the bit of
681 	 * information we'll use to find the  messages we want to change.
682 	 */
683 	if(F_ON(F_FAKE_NEW_IN_NEWS, ps_global) &&
684 	   retstream->nmsgs > 0 && IS_NEWS(retstream)){
685 	    char         *seq;
686 	    long          i, mflags = ST_SET;
687 	    MESSAGECACHE *mc;
688 
689 	    /*
690 	     * Search for !recent messages to set the searched bit for
691 	     * those messages we want to change. Then we'll flip the bits.
692 	     */
693 	    (void)count_flagged(retstream, F_UNRECENT);
694 
695 	    for(i = 1L; i <= retstream->nmsgs; i++)
696 	      if((mc = mail_elt(retstream,i)) && mc->searched)
697 		mc->sequence = 1;
698 	      else
699 		mc->sequence = 0;
700 
701 	    if(!is_imap_stream(retstream))
702 		mflags |= ST_SILENT;
703 	    if((seq = build_sequence(retstream, NULL, NULL)) != NULL){
704 		mail_flag(retstream, seq, "\\SEEN", mflags);
705 		fs_give((void **)&seq);
706 	    }
707 	}
708     }
709 
710     return(retstream);
711 }
712 
713 
714 void
reset_stream_view_state(MAILSTREAM * stream)715 reset_stream_view_state(MAILSTREAM *stream)
716 {
717     MSGNO_S *mm;
718 
719     if(!stream)
720       return;
721 
722     mm = sp_msgmap(stream);
723 
724     if(!mm)
725       return;
726 
727     sp_set_viewing_a_thread(stream, 0);
728     sp_set_need_to_rethread(stream, 0);
729 
730     mn_reset_cur(mm, stream->nmsgs > 0L ? stream->nmsgs : 0L);  /* default */
731 
732     mm->visible_threads = -1L;
733     mm->top             = 0L;
734     mm->max_thrdno      = 0L;
735     mm->top_after_thrd  = 0L;
736 
737     mn_set_mansort(mm, 0);
738 
739     /*
740      * Get rid of zooming and selections, but leave filtering flags. All the
741      * flag counts and everything should still be correct because set_lflag
742      * preserves them correctly.
743      */
744     if(any_lflagged(mm, MN_SLCT | MN_HIDE)){
745 	long i;
746 
747 	for(i = 1L; i <= mn_get_total(mm); i++)
748 	  set_lflag(stream, mm, i, MN_SLCT | MN_HIDE, 0);
749     }
750 
751     /*
752      * We could try to set up a default sort order, but the caller is going
753      * to re-sort anyway if they are interested in sorting. So we won't do
754      * it here.
755      */
756 }
757 
758 
759 /*
760  * We have to be careful when we change the flags of an already
761  * open stream, because there may be more than one section of
762  * code actively using the stream.
763  * We allow turning on (but not off) SP_LOCKED
764  *                                   SP_PERMLOCKED
765  *                                   SP_USERFLDR
766  *                                   SP_INBOX
767  * We allow turning off (but not on) SP_TEMPUSE
768  */
769 void
carefully_reset_sp_flags(MAILSTREAM * stream,long unsigned int flags)770 carefully_reset_sp_flags(MAILSTREAM *stream, long unsigned int flags)
771 {
772     if(sp_flags(stream) != flags){
773 	/* allow turning on but not off */
774 	if(sp_flags(stream) & SP_LOCKED && !(flags & SP_LOCKED))
775 	  flags |= SP_LOCKED;
776 
777 	if(sp_flags(stream) & SP_PERMLOCKED && !(flags & SP_PERMLOCKED))
778 	  flags |= SP_PERMLOCKED;
779 
780 	if(sp_flags(stream) & SP_USERFLDR && !(flags & SP_USERFLDR))
781 	  flags |= SP_USERFLDR;
782 
783 	if(sp_flags(stream) & SP_INBOX && !(flags & SP_INBOX))
784 	  flags |= SP_INBOX;
785 
786 
787 	/* allow turning off but not on */
788 	if(!(sp_flags(stream) & SP_TEMPUSE) && flags & SP_TEMPUSE)
789 	  flags &= ~SP_TEMPUSE;
790 
791 
792 	/* leave the way they already are */
793 	if((sp_flags(stream) & SP_FILTERED) != (flags & SP_FILTERED))
794 	  flags = (flags & ~SP_FILTERED) | (sp_flags(stream) & SP_FILTERED);
795 
796 
797 	if(sp_flags(stream) != flags)
798 	  sp_flag(stream, flags);
799     }
800 }
801 
802 
803 /*
804  * Pine wrapper around mail_create. If we have the PREFER_ALT_AUTH flag turned
805  * on we don't want to pass a NULL stream to c-client because it will open
806  * a non-ssl connection when we want it to be ssl.
807  */
808 long
pine_mail_create(MAILSTREAM * stream,char * mailbox)809 pine_mail_create(MAILSTREAM *stream, char *mailbox)
810 {
811     MAILSTREAM *ourstream = NULL;
812     long        return_val;
813     long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
814     char        source[MAILTMPLEN], *target = NULL;
815     DRIVER     *d;
816 
817     dprint((7, "pine_mail_create: creating \"%s\"%s\n",
818 	       mailbox ? mailbox : "(NULL)",
819 	       stream ? "" : " (stream was NULL)"));
820 
821     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
822 	mailbox = target;
823 	dprint((7,
824 		   "pine_mail_create: #move special case, creating \"%s\"\n",
825 		   mailbox ? mailbox : "(NULL)"));
826     }
827 
828     /*
829      * We don't really need this anymore, since we are now using IMAPTRYALT.
830      * We'll leave it since it works.
831      */
832     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
833        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
834 
835 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
836 	   && !strcmp(d->name, "imap")){
837 
838 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
839 	      openflags |= OP_TRYALT;
840 	}
841     }
842 
843     if(!stream)
844       stream = sp_stream_get(mailbox, SP_MATCH);
845     if(!stream)
846       stream = sp_stream_get(mailbox, SP_SAME);
847 
848     if(!stream){
849 	/*
850 	 * It is only useful to open a stream in the imap case.
851 	 */
852 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
853 	   && !strcmp(d->name, "imap")){
854 
855 	    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
856 	    ourstream = stream;
857 	}
858     }
859 
860     return_val = mail_create(stream, mailbox);
861 
862     if(ourstream)
863       pine_mail_close(ourstream);
864 
865     return(return_val);
866 }
867 
868 
869 /*
870  * Pine wrapper around mail_delete.
871  */
872 long
pine_mail_delete(MAILSTREAM * stream,char * mailbox)873 pine_mail_delete(MAILSTREAM *stream, char *mailbox)
874 {
875     MAILSTREAM *ourstream = NULL;
876     long        return_val;
877     long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
878     char        source[MAILTMPLEN], *target = NULL;
879     DRIVER     *d;
880 
881     dprint((7, "pine_mail_delete: deleting \"%s\"%s\n",
882 	       mailbox ? mailbox : "(NULL)",
883 	       stream ? "" : " (stream was NULL)"));
884 
885     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
886 	mailbox = target;
887 	dprint((7,
888 		   "pine_mail_delete: #move special case, deleting \"%s\"\n",
889 		   mailbox ? mailbox : "(NULL)"));
890     }
891 
892     /*
893      * We don't really need this anymore, since we are now using IMAPTRYALT.
894      */
895     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
896        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
897 
898 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
899 	   && !strcmp(d->name, "imap")){
900 
901 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
902 	      openflags |= OP_TRYALT;
903 	}
904     }
905 
906     /* oops, we seem to be deleting a selected stream */
907     if(!stream && (stream = sp_stream_get(mailbox, SP_MATCH))){
908 	pine_mail_actually_close(stream);
909 	stream = NULL;
910     }
911 
912     if(!stream)
913       stream = sp_stream_get(mailbox, SP_SAME);
914 
915     if(!stream){
916 	/*
917 	 * It is only useful to open a stream in the imap case.
918 	 */
919 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
920 	   && !strcmp(d->name, "imap")){
921 
922 	    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
923 	    ourstream = stream;
924 	}
925     }
926 
927     return_val = mail_delete(stream, mailbox);
928 
929     if(ourstream)
930       pine_mail_close(ourstream);
931 
932     return(return_val);
933 }
934 
935 
936 /*
937  * Pine wrapper around mail_append.
938  */
939 long
pine_mail_append_full(MAILSTREAM * stream,char * mailbox,char * flags,char * date,STRING * message)940 pine_mail_append_full(MAILSTREAM *stream, char *mailbox, char *flags, char *date, STRING *message)
941 {
942     MAILSTREAM *ourstream = NULL;
943     long        return_val;
944     long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
945     char        source[MAILTMPLEN], *target = NULL;
946     char        mailbox_nodelim[MAILTMPLEN];
947     int         delim;
948     DRIVER     *d;
949 
950     dprint((7, "pine_mail_append_full: appending to \"%s\"%s\n",
951 	       mailbox ? mailbox : "(NULL)",
952 	       stream ? "" : " (stream was NULL)"));
953 
954     /* strip delimiter, it has no meaning in an APPEND and could cause trouble */
955     if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){
956 	size_t len;
957 
958 	len = strlen(mailbox);
959 	if(mailbox[len-1] == delim){
960 	    strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1));
961 	    mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0';
962 	    mailbox = mailbox_nodelim;
963 	}
964     }
965 
966     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
967 	mailbox = target;
968 	dprint((7,
969 	   "pine_mail_append_full: #move special case, appending to \"%s\"\n",
970 		   mailbox ? mailbox : "(NULL)"));
971     }
972 
973     /*
974      * We don't really need this anymore, since we are now using IMAPTRYALT.
975      */
976     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
977        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
978 
979 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
980 	   && !strcmp(d->name, "imap")){
981 
982 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
983 	      openflags |= OP_TRYALT;
984 	}
985     }
986 
987     if(!stream)
988       stream = sp_stream_get(mailbox, SP_MATCH);
989     if(!stream)
990       stream = sp_stream_get(mailbox, SP_SAME);
991 
992     if(!stream){
993 	/*
994 	 * It is only useful to open a stream in the imap case.
995 	 */
996 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
997 	   && !strcmp(d->name, "imap")){
998 
999 	    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
1000 	    ourstream = stream;
1001 	}
1002     }
1003 
1004     return_val = mail_append_full(stream, mailbox, flags, date, message);
1005 
1006     if(ourstream)
1007       pine_mail_close(ourstream);
1008 
1009     return(return_val);
1010 }
1011 
1012 
1013 /*
1014  * Pine wrapper around mail_append.
1015  */
1016 long
pine_mail_append_multiple(MAILSTREAM * stream,char * mailbox,append_t af,APPENDPACKAGE * data,MAILSTREAM * not_this_stream)1017 pine_mail_append_multiple(MAILSTREAM *stream, char *mailbox, append_t af,
1018 			  APPENDPACKAGE *data, MAILSTREAM *not_this_stream)
1019 {
1020     MAILSTREAM *ourstream = NULL;
1021     long        return_val;
1022     long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
1023     char        source[MAILTMPLEN], *target = NULL;
1024     DRIVER     *d;
1025     int         we_blocked_reuse = 0;
1026 
1027     dprint((7, "pine_mail_append_multiple: appending to \"%s\"%s\n",
1028 	       mailbox ? mailbox : "(NULL)",
1029 	       stream ? "" : " (stream was NULL)"));
1030 
1031     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
1032 	mailbox = target;
1033 	dprint((7,
1034          "pine_mail_append_multiple: #move special case, appending to \"%s\"\n",
1035 		   mailbox ? mailbox : "(NULL)"));
1036     }
1037 
1038     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
1039        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
1040 
1041 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
1042 	   && !strcmp(d->name, "imap")){
1043 
1044 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
1045 	      openflags |= OP_TRYALT;
1046 	}
1047     }
1048 
1049     /*
1050      * We have to be careful re-using streams for multiappend, because of
1051      * the way it works. We call into c-client below but part of the call
1052      * is data containing a callback function to us to supply the data to
1053      * be appended. That function may need to get the data from the server.
1054      * If that uses the same stream as we're trying to append on, we're
1055      * in trouble. We can't call back into c-client from c-client on the same
1056      * stream. (Just think about it, we're in the middle of an APPEND command.
1057      * We can't issue a FETCH before the APPEND completes in order to complete
1058      * the APPEND.) We can re-use a stream if it is a different stream from
1059      * the one we are reading from, so that's what the not_this_stream
1060      * stuff is for. If we mark it !SP_USEPOOL, it won't get reused.
1061      */
1062     if(sp_flagged(not_this_stream, SP_USEPOOL)){
1063 	we_blocked_reuse++;
1064 	sp_unflag(not_this_stream, SP_USEPOOL);
1065     }
1066 
1067     if(!stream)
1068       stream = sp_stream_get(mailbox, SP_MATCH);
1069     if(!stream)
1070       stream = sp_stream_get(mailbox, SP_SAME);
1071 
1072     if(!stream){
1073 	/*
1074 	 * It is only useful to open a stream in the imap case.
1075 	 */
1076 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
1077 	   && !strcmp(d->name, "imap")){
1078 
1079 	    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
1080 	    ourstream = stream;
1081 	}
1082     }
1083 
1084     if(we_blocked_reuse)
1085       sp_set_flags(not_this_stream, sp_flags(not_this_stream) | SP_USEPOOL);
1086 
1087     return_val = mail_append_multiple(stream, mailbox, af, (void *) data);
1088 
1089     if(ourstream)
1090       pine_mail_close(ourstream);
1091 
1092     return(return_val);
1093 }
1094 
1095 
1096 /*
1097  * Pine wrapper around mail_copy.
1098  */
1099 long
pine_mail_copy_full(MAILSTREAM * stream,char * sequence,char * mailbox,long int options)1100 pine_mail_copy_full(MAILSTREAM *stream, char *sequence, char *mailbox, long int options)
1101 {
1102     MAILSTREAM *ourstream = NULL;
1103     long        return_val;
1104     long openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
1105     char        source[MAILTMPLEN], *target = NULL;
1106     char        mailbox_nodelim[MAILTMPLEN];
1107     int         delim;
1108     DRIVER     *d;
1109 
1110     dprint((7, "pine_mail_copy_full: copying to \"%s\"%s\n",
1111 	       mailbox ? mailbox : "(NULL)",
1112 	       stream ? "" : " (stream was NULL)"));
1113 
1114     /* strip delimiter, it has no meaning in a COPY and could cause trouble */
1115     if(mailbox && (delim = get_folder_delimiter(mailbox)) != '\0'){
1116 	size_t len;
1117 
1118 	len = strlen(mailbox);
1119 	if(mailbox[len-1] == delim){
1120 	    strncpy(mailbox_nodelim, mailbox, MIN(len-1,sizeof(mailbox_nodelim)-1));
1121 	    mailbox_nodelim[MIN(len-1,sizeof(mailbox_nodelim)-1)] = '\0';
1122 	    mailbox = mailbox_nodelim;
1123 	}
1124     }
1125 
1126     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
1127 	mailbox = target;
1128 	dprint((7,
1129 	   "pine_mail_copy_full: #move special case, copying to \"%s\"\n",
1130 		   mailbox ? mailbox : "(NULL)"));
1131     }
1132 
1133     /*
1134      * We don't really need this anymore, since we are now using IMAPTRYALT.
1135      */
1136     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
1137        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
1138 
1139 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
1140 	   && !strcmp(d->name, "imap")){
1141 
1142 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
1143 	      openflags |= OP_TRYALT;
1144 	}
1145     }
1146 
1147     if(!stream)
1148       stream = sp_stream_get(mailbox, SP_MATCH);
1149     if(!stream)
1150       stream = sp_stream_get(mailbox, SP_SAME);
1151 
1152     if(!stream){
1153 	/*
1154 	 * It is only useful to open a stream in the imap case.
1155 	 * Actually, mail_copy_full is the case where it might also be
1156 	 * useful to provide a stream in the nntp and pop3 cases. If we
1157 	 * cache such streams, then we will probably want to open one
1158 	 * here so that it gets cached.
1159 	 */
1160 	if((d = mail_valid (NIL, mailbox, (char *) NIL))
1161 	   && !strcmp(d->name, "imap")){
1162 
1163 	    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
1164 	    ourstream = stream;
1165 	}
1166     }
1167 
1168     return_val = mail_copy_full(stream, sequence, mailbox, options);
1169 
1170     if(ourstream)
1171       pine_mail_close(ourstream);
1172 
1173     return(return_val);
1174 }
1175 
1176 
1177 /*
1178  * Pine wrapper around mail_rename.
1179  */
1180 long
pine_mail_rename(MAILSTREAM * stream,char * old,char * new)1181 pine_mail_rename(MAILSTREAM *stream, char *old, char *new)
1182 {
1183     MAILSTREAM *ourstream = NULL;
1184     long        return_val;
1185     long        openflags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
1186     DRIVER     *d;
1187 
1188     dprint((7, "pine_mail_rename(%s,%s)\n", old ? old : "",
1189 		new ? new : ""));
1190 
1191     /*
1192      * We don't really need this anymore, since we are now using IMAPTRYALT.
1193      */
1194     if((F_ON(F_PREFER_ALT_AUTH, ps_global)
1195        || (ps_global->debug_imap > 3 || ps_global->debugmem))){
1196 
1197 	if((d = mail_valid (NIL, old, (char *) NIL))
1198 	   && !strcmp(d->name, "imap")){
1199 
1200 	    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
1201 	      openflags |= OP_TRYALT;
1202 	}
1203     }
1204 
1205     /* oops, we seem to be renaming a selected stream */
1206     if(!stream && (stream = sp_stream_get(old, SP_MATCH))){
1207 	pine_mail_actually_close(stream);
1208 	stream = NULL;
1209     }
1210 
1211     if(!stream)
1212       stream = sp_stream_get(old, SP_SAME);
1213 
1214     if(!stream){
1215 	/*
1216 	 * It is only useful to open a stream in the imap case.
1217 	 */
1218 	if((d = mail_valid (NIL, old, (char *) NIL))
1219 	   && !strcmp(d->name, "imap")){
1220 
1221 	    stream = pine_mail_open(NULL, old, openflags, NULL);
1222 	    ourstream = stream;
1223 	}
1224     }
1225 
1226     return_val = mail_rename(stream, old, new);
1227 
1228     if(ourstream)
1229       pine_mail_close(ourstream);
1230 
1231     return(return_val);
1232 }
1233 
1234 
1235 /*----------------------------------------------------------------------
1236   Our mail_close wrapper to clean up anything on the mailstream we may have
1237   added to it.  mostly in the unused bits of the elt's.
1238   ----*/
1239 void
pine_mail_close(MAILSTREAM * stream)1240 pine_mail_close(MAILSTREAM *stream)
1241 {
1242     unsigned long uid_last, last_uid;
1243     int refcnt;
1244 
1245     if(!stream)
1246       return;
1247 
1248     dprint((7, "pine_mail_close: %s (%s)\n",
1249 	       stream && stream->mailbox ? stream->mailbox : "(NULL)",
1250 	       debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
1251 
1252     if(sp_flagged(stream, SP_USEPOOL) && !sp_dead_stream(stream)){
1253 
1254 	refcnt = sp_ref_cnt(stream);
1255 	dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
1256 
1257 	/*
1258 	 * Instead of checkpointing here, which takes time that the user
1259 	 * definitely notices, we checkpoint in new_mail at the next
1260 	 * opportune time, hopefully when the user is idle.
1261 	 */
1262 #if 0
1263 	if(sp_flagged(stream, SP_LOCKED) && sp_flagged(stream, SP_USERFLDR)
1264 	   && !stream->halfopen && refcnt <= 1){
1265 	    if(changes_to_checkpoint(stream))
1266 	      pine_mail_check(stream);
1267 	    else{
1268 	      dprint((7,
1269 		     "pine_mail_close: dont think we need to checkpoint\n"));
1270 	    }
1271 	}
1272 #endif
1273 
1274 	/*
1275 	 * Uid_last is valid when we first open a stream, but not always
1276 	 * valid after that. So if we know the last uid should be higher
1277 	 * than uid_last (!#%) use that instead.
1278 	 */
1279 	uid_last = stream->uid_last;
1280 	if(stream->nmsgs > 0L
1281 	   && (last_uid=mail_uid(stream,stream->nmsgs)) > uid_last)
1282 	  uid_last = last_uid;
1283 
1284 	sp_set_saved_uid_validity(stream, stream->uid_validity);
1285 	sp_set_saved_uid_last(stream, uid_last);
1286 
1287 	/*
1288 	 * If the reference count is down to 0, unlock it.
1289 	 * In any case, don't actually do any real closing.
1290 	 */
1291 	if(refcnt > 0)
1292 	  sp_set_ref_cnt(stream, refcnt-1);
1293 
1294 	refcnt = sp_ref_cnt(stream);
1295 	dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
1296 	if(refcnt <= 0){
1297 	    dprint((7,
1298 	       "pine_mail_close: unlocking: start idle timer: TAG %08lx (%s)\n",
1299 	       stream->gensym, debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
1300 	    sp_set_last_use_time(stream, time(0));
1301 
1302 	    /*
1303 	     * Logically, we ought to be unflagging SP_INBOX, too. However,
1304 	     * the filtering code uses SP_INBOX when deciding if it should
1305 	     * filter some things, and we keep filtering after the mailbox
1306 	     * is closed. So leave SP_INBOX alone. This (the closing of INBOX)
1307 	     * usually only happens in goodnight_gracey when we're
1308 	     * shutting everything down.
1309 	     */
1310 	    sp_unflag(stream, SP_LOCKED | SP_PERMLOCKED | SP_USERFLDR);
1311 	}
1312 	else{
1313 	    dprint((7, "pine_mail_close: ref cnt is now %d\n",
1314 		       refcnt));
1315 	}
1316     }
1317     else{
1318 	dprint((7, "pine_mail_close: %s\n",
1319 		   sp_flagged(stream, SP_USEPOOL) ? "dead stream" : "no pool"));
1320 
1321 	refcnt = sp_ref_cnt(stream);
1322 	dprint((7, "pine_mail_close: ref cnt is %d\n", refcnt));
1323 
1324 	/*
1325 	 * If the reference count is down to 0, unlock it.
1326 	 * In any case, don't actually do any real closing.
1327 	 */
1328 	if(refcnt > 0)
1329 	  sp_set_ref_cnt(stream, refcnt-1);
1330 
1331 	refcnt = sp_ref_cnt(stream);
1332 	if(refcnt <= 0){
1333 	    pine_mail_actually_close(stream);
1334 	}
1335 	else{
1336 	    dprint((7, "pine_mail_close: ref cnt is now %d\n",
1337 		       refcnt));
1338 	}
1339 
1340     }
1341 }
1342 
1343 
1344 void
pine_mail_actually_close(MAILSTREAM * stream)1345 pine_mail_actually_close(MAILSTREAM *stream)
1346 {
1347     if(!stream)
1348       return;
1349 
1350     if(!sp_closing(stream)){
1351 	dprint((7, "pine_mail_actually_close: %s (%s)\n",
1352 		   stream && stream->mailbox ? stream->mailbox : "(NULL)",
1353 		   debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
1354 
1355 	sp_set_closing(stream, 1);
1356 
1357 	if(!sp_flagged(stream, SP_LOCKED)
1358 	   && !sp_flagged(stream, SP_USERFLDR)
1359 	   && !sp_dead_stream(stream)
1360 	   && sp_new_mail_count(stream))
1361 	  process_filter_patterns(stream, sp_msgmap(stream),
1362 				  sp_new_mail_count(stream));
1363 	sp_delete(stream);
1364 
1365 	/*
1366 	 * let sp_free_callback() free the sp_s stuff and the callbacks to
1367 	 * free_pine_elt free the per-elt pine stuff.
1368 	 */
1369 	mail_close(stream);
1370     }
1371 }
1372 
1373 
1374 /*
1375  * If we haven't used a stream for a while, we may want to logout.
1376  */
1377 void
maybe_kill_old_stream(MAILSTREAM * stream)1378 maybe_kill_old_stream(MAILSTREAM *stream)
1379 {
1380 #define KILL_IF_IDLE_TIME (25 * 60)
1381     if(stream
1382        && !sp_flagged(stream, SP_LOCKED)
1383        && !sp_flagged(stream, SP_USERFLDR)
1384        && time(0) - sp_last_use_time(stream) > KILL_IF_IDLE_TIME){
1385 
1386 	dprint((7,
1387 		"killing idle stream: %s (%s): idle timer = %ld secs\n",
1388 		stream && stream->mailbox ? stream->mailbox : "(NULL)",
1389 		debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress),
1390 		(long) (time(0)-sp_last_use_time(stream))));
1391 
1392 	/*
1393 	 * Another thing we could do here instead is to unselect the
1394 	 * mailbox, leaving the stream open to the server.
1395 	 */
1396 	pine_mail_actually_close(stream);
1397     }
1398 }
1399 
1400 
1401 /*
1402  * Catch searches that don't need to go to the server.
1403  * (Not anymore, now c-client does this for us.)
1404  */
1405 long
pine_mail_search_full(MAILSTREAM * stream,char * charset,SEARCHPGM * pgm,long int flags)1406 pine_mail_search_full(MAILSTREAM *stream, char *charset,
1407 		      SEARCHPGM *pgm, long int flags)
1408 {
1409     /*
1410      * The charset should either be UTF-8 or NULL for alpine.
1411      * If it is UTF-8 we may be able to change it to NULL if
1412      * everything in the search is actually ascii. We try to
1413      * do this because not all servers understand the CHARSET
1414      * parameter.
1415      */
1416     if(charset && !strucmp(charset, "utf-8")
1417        && is_imap_stream(stream) && !hibit_in_searchpgm(pgm))
1418       charset = NULL;
1419 
1420     if(F_ON(F_NNTP_SEARCH_USES_OVERVIEW, ps_global))
1421       flags |= SO_OVERVIEW;
1422 
1423     return(stream ? mail_search_full(stream, charset, pgm, flags) : NIL);
1424 }
1425 
1426 
1427 int
hibit_in_searchpgm(SEARCHPGM * pgm)1428 hibit_in_searchpgm(SEARCHPGM *pgm)
1429 {
1430     SEARCHOR *or;
1431     SEARCHPGMLIST *not;
1432 
1433     if(!pgm)
1434       return 0;
1435 
1436     if((pgm->subject && hibit_in_strlist(pgm->subject))
1437        || (pgm->text && hibit_in_strlist(pgm->text))
1438        || (pgm->body && hibit_in_strlist(pgm->body))
1439        || (pgm->cc && hibit_in_strlist(pgm->cc))
1440        || (pgm->from && hibit_in_strlist(pgm->from))
1441        || (pgm->to && hibit_in_strlist(pgm->to))
1442        || (pgm->bcc && hibit_in_strlist(pgm->bcc))
1443        || (pgm->keyword && hibit_in_strlist(pgm->keyword))
1444        || (pgm->unkeyword && hibit_in_strlist(pgm->return_path))
1445        || (pgm->sender && hibit_in_strlist(pgm->sender))
1446        || (pgm->reply_to && hibit_in_strlist(pgm->reply_to))
1447        || (pgm->in_reply_to && hibit_in_strlist(pgm->in_reply_to))
1448        || (pgm->message_id && hibit_in_strlist(pgm->message_id))
1449        || (pgm->newsgroups && hibit_in_strlist(pgm->newsgroups))
1450        || (pgm->followup_to && hibit_in_strlist(pgm->followup_to))
1451        || (pgm->references && hibit_in_strlist(pgm->references))
1452        || (pgm->header && hibit_in_header(pgm->header)))
1453       return 1;
1454 
1455     for(or = pgm->or; or; or = or->next)
1456       if(hibit_in_searchpgm(or->first) || hibit_in_searchpgm(or->second))
1457         return 1;
1458 
1459     for(not = pgm->not; not; not = not->next)
1460       if(hibit_in_searchpgm(not->pgm))
1461         return 1;
1462 
1463     return 0;
1464 }
1465 
1466 
1467 int
hibit_in_strlist(STRINGLIST * sl)1468 hibit_in_strlist(STRINGLIST *sl)
1469 {
1470     for(; sl; sl = sl->next)
1471       if(hibit_in_sizedtext(&sl->text))
1472         return 1;
1473 
1474     return 0;
1475 }
1476 
1477 
1478 int
hibit_in_header(SEARCHHEADER * header)1479 hibit_in_header(SEARCHHEADER *header)
1480 {
1481     SEARCHHEADER *hdr;
1482 
1483     for(hdr = header; hdr; hdr = hdr->next)
1484       if(hibit_in_sizedtext(&hdr->line) || hibit_in_sizedtext(&hdr->text))
1485         return 1;
1486 
1487     return 0;
1488 }
1489 
1490 
1491 int
hibit_in_sizedtext(SIZEDTEXT * st)1492 hibit_in_sizedtext(SIZEDTEXT *st)
1493 {
1494     unsigned char *p, *end;
1495 
1496     p = st ? st->data : NULL;
1497     if(p)
1498       for(end = p + st->size; p < end; p++)
1499         if(*p & 0x80)
1500 	  return 1;
1501 
1502     return 0;
1503 }
1504 
1505 
1506 void
pine_mail_fetch_flags(MAILSTREAM * stream,char * sequence,long int flags)1507 pine_mail_fetch_flags(MAILSTREAM *stream, char *sequence, long int flags)
1508 {
1509     ps_global->dont_count_flagchanges = 1;
1510     mail_fetch_flags(stream, sequence, flags);
1511     ps_global->dont_count_flagchanges = 0;
1512 }
1513 
1514 
1515 ENVELOPE *
pine_mail_fetchenvelope(MAILSTREAM * stream,long unsigned int msgno)1516 pine_mail_fetchenvelope(MAILSTREAM *stream, long unsigned int msgno)
1517 {
1518     ENVELOPE *env = NULL;
1519 
1520     ps_global->dont_count_flagchanges = 1;
1521     if(stream && msgno > 0L && msgno <= stream->nmsgs)
1522       env = mail_fetchenvelope(stream, msgno);
1523 
1524     ps_global->dont_count_flagchanges = 0;
1525     return(env);
1526 }
1527 
1528 
1529 ENVELOPE *
pine_mail_fetch_structure(MAILSTREAM * stream,long unsigned int msgno,struct mail_bodystruct ** body,long int flags)1530 pine_mail_fetch_structure(MAILSTREAM *stream, long unsigned int msgno,
1531 			  struct mail_bodystruct **body, long int flags)
1532 {
1533     ENVELOPE *env = NULL;
1534 
1535     ps_global->dont_count_flagchanges = 1;
1536     if(stream && (flags & FT_UID || (msgno > 0L && msgno <= stream->nmsgs)))
1537       env = mail_fetch_structure(stream, msgno, body, flags);
1538 
1539     ps_global->dont_count_flagchanges = 0;
1540     return(env);
1541 }
1542 
1543 
1544 ENVELOPE *
pine_mail_fetchstructure(MAILSTREAM * stream,long unsigned int msgno,struct mail_bodystruct ** body)1545 pine_mail_fetchstructure(MAILSTREAM *stream, long unsigned int msgno, struct mail_bodystruct **body)
1546 {
1547     ENVELOPE *env = NULL;
1548 
1549     ps_global->dont_count_flagchanges = 1;
1550     if(stream && msgno > 0L && msgno <= stream->nmsgs)
1551       env = mail_fetchstructure(stream, msgno, body);
1552 
1553     ps_global->dont_count_flagchanges = 0;
1554     return(env);
1555 }
1556 
1557 
1558 /*
1559  * Wrapper around mail_fetch_body.
1560  * Currently only used to turn on partial fetching if trying
1561  * to work around the microsoft ssl bug.
1562  */
1563 char *
pine_mail_fetch_body(MAILSTREAM * stream,long unsigned int msgno,char * section,long unsigned int * len,long int flags)1564 pine_mail_fetch_body(MAILSTREAM *stream, long unsigned int msgno, char *section,
1565 		     long unsigned int *len, long int flags)
1566 {
1567 #ifdef _WINDOWS
1568     if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
1569       return(pine_mail_partial_fetch_wrapper(stream, msgno,
1570 			     section, len, flags, 0, NULL, 0));
1571     else
1572 #endif
1573     return(mail_fetch_body(stream, msgno, section, len, flags));
1574 }
1575 
1576 /*
1577  * Wrapper around mail_fetch_text.
1578  * Currently the only purpose this wrapper serves is to turn
1579  * on partial fetching for quell-ssl-largeblocks.
1580  */
1581 char *
pine_mail_fetch_text(MAILSTREAM * stream,long unsigned int msgno,char * section,long unsigned int * len,long int flags)1582 pine_mail_fetch_text(MAILSTREAM *stream, long unsigned int msgno, char *section,
1583 		     long unsigned int *len, long int flags)
1584 {
1585 #ifdef _WINDOWS
1586     if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global))
1587       return(pine_mail_partial_fetch_wrapper(stream, msgno,
1588 					     section, len, flags,
1589 					     0, NULL, 1));
1590     else
1591 #endif
1592       return(mail_fetch_text(stream, msgno, section, len, flags));
1593 }
1594 
1595 
1596 /*
1597  * Determine whether to do partial-fetching or not, and do it
1598  *    args - same as c-client functions being wrapped around
1599  *    get_n_bytes - try to partial fetch for the first n bytes.
1600  *                  makes no guarantees, might wind up fetching
1601  *                  the entire text anyway.
1602  *    str_to_free - pointer to string to free if we only get
1603  *                  (non-cachable) partial text (required for
1604  *                  get_n_bytes).
1605  *    is_text_fetch -
1606  *      set, tells us to do mail_fetch_text and mail_partial_text
1607  *      unset, tells us to do mail_fetch_body and mail_partial_body
1608  */
1609 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)1610 pine_mail_partial_fetch_wrapper(MAILSTREAM *stream, long unsigned int msgno,
1611 				char *section, long unsigned int *len,
1612 				long int flags, long unsigned int get_n_bytes,
1613 				char **str_to_free, int is_text_fetch)
1614 {
1615     BODY *body;
1616     unsigned long size, firstbyte, lastbyte;
1617     void *old_gets;
1618     FETCH_READC_S *pftc;
1619     char imap_cache_section[MAILTMPLEN];
1620     SIZEDTEXT new_text;
1621     MESSAGECACHE *mc;
1622     char *(*fetch_full)(MAILSTREAM *, unsigned long, char *,
1623 		       unsigned long *, long);
1624     long (*fetch_partial)(MAILSTREAM *, unsigned long, char *,
1625 			   unsigned long, unsigned long, long);
1626 
1627     fetch_full = is_text_fetch ? mail_fetch_text : mail_fetch_body;
1628     fetch_partial = is_text_fetch ? mail_partial_text : mail_partial_body;
1629     if(str_to_free)
1630       *str_to_free = NULL;
1631 #ifdef _WINDOWS
1632     if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global) || get_n_bytes){
1633 #else
1634     if(get_n_bytes){
1635 #endif /* _WINDOWS */
1636 	if(section && *section)
1637 	  body = mail_body(stream, msgno, (unsigned char *) section);
1638 	else
1639 	  pine_mail_fetch_structure(stream, msgno, &body, flags);
1640 	if(!body)
1641 	  return NULL;
1642 	if(body->type != TYPEMULTIPART)
1643 	  size = body->size.bytes;
1644 	else if((!section || !*section) && msgno > 0L
1645 	        && stream && msgno <= stream->nmsgs
1646 		&& (mc = mail_elt(stream, msgno)))
1647 	  size = mc->rfc822_size; /* upper bound */
1648 	else      /* just a guess, can't get actual size */
1649 	  size = fcc_size_guess(body) + 2048;
1650 
1651 	/*
1652 	 * imap_cache, originally intended for c-client internal use,
1653 	 * takes a section argument that is different from one we
1654 	 * would pass to mail_body.  Typically in this function
1655 	 * section is NULL, which translates to "TEXT", but in other
1656 	 * cases we would want to append ".TEXT" to the section
1657 	 */
1658 	if(is_text_fetch)
1659 	  snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10,
1660 		  section && *section ? section : "",
1661 		  section && *section ? "." : "");
1662 	else
1663 	  snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10,
1664 		  section && *section ? section : "");
1665 
1666 	if(modern_imap_stream(stream)
1667 #ifdef _WINDOWS
1668 	   && ((size > AVOID_MICROSOFT_SSL_CHUNKING_BUG)
1669 	       || (get_n_bytes && size > get_n_bytes))
1670 #else
1671 	   && (get_n_bytes && size > get_n_bytes)
1672 #endif /* _WINDOWS */
1673 	   && !imap_cache(stream, msgno, imap_cache_section,
1674 			  NULL, NULL)){
1675 	    if(get_n_bytes == 0){
1676 	      dprint((8,
1677     "fetch_wrapper: doing partial fetching to work around microsoft bug\n"));
1678 	    }
1679 	    else{
1680 	      dprint((8,
1681     "fetch_wrapper: partial fetching due to %lu get_n_bytes\n", get_n_bytes));
1682 	    }
1683 	    pftc = (FETCH_READC_S *)fs_get(sizeof(FETCH_READC_S));
1684 	    memset(g_pft_desc = pftc, 0, sizeof(FETCH_READC_S));
1685 #ifdef _WINDOWS
1686 	    if(F_ON(F_QUELL_SSL_LARGEBLOCKS, ps_global)){
1687 		if(get_n_bytes)
1688 		  pftc->chunksize = MIN(get_n_bytes,
1689 					AVOID_MICROSOFT_SSL_CHUNKING_BUG);
1690 		else
1691 		  pftc->chunksize = AVOID_MICROSOFT_SSL_CHUNKING_BUG;
1692 	    }
1693 	    else
1694 #endif /* _WINDOWS */
1695 	    pftc->chunksize = get_n_bytes;
1696 
1697 	    pftc->chunk = (char *) fs_get((pftc->chunksize+1)
1698 					  * sizeof(char));
1699 	    pftc->cache = so_get(CharStar, NULL, EDIT_ACCESS);
1700 	    pftc->read = 0L;
1701 	    so_truncate(pftc->cache, size + 1);
1702 	    old_gets = mail_parameters(stream, GET_GETS, (void *)NULL);
1703 	    mail_parameters(stream, SET_GETS, (void *) partial_text_gets);
1704 	    /* start fetching */
1705 	    do{
1706 		firstbyte = pftc->read ;
1707 		lastbyte = firstbyte + pftc->chunksize;
1708 		if(get_n_bytes > firstbyte && get_n_bytes < lastbyte){
1709 		    pftc->chunksize = get_n_bytes - firstbyte;
1710 		    lastbyte = get_n_bytes;
1711 		}
1712 		(*fetch_partial)(stream, msgno, section, firstbyte,
1713 				 pftc->chunksize, flags);
1714 
1715 		if(pftc->read != lastbyte)
1716 		  break;
1717 	    } while((pftc->read ==  lastbyte)
1718 		    && (!get_n_bytes || (pftc->read < get_n_bytes)));
1719 	    dprint((8,
1720 		       "fetch_wrapper: anticipated size=%lu read=%lu\n",
1721 		       size, pftc->read));
1722 	    mail_parameters(stream, SET_GETS, old_gets);
1723 	    new_text.size = pftc->read;
1724 	    new_text.data = (unsigned char *)so_text(pftc->cache);
1725 
1726 	    if(get_n_bytes && pftc->read == get_n_bytes){
1727 		/*
1728 		 * don't write to cache if we're only dealing with
1729 		 * partial text.
1730 		 */
1731 		if(!str_to_free)
1732 		  alpine_panic("Programmer botch: partial fetch attempt w/o string pointer");
1733 		else
1734 		  *str_to_free = (char *) new_text.data;
1735 	    }
1736 	    else {
1737 		/* ugh, rewrite string in case it got stomped on last call */
1738 		if(is_text_fetch)
1739 		  snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s%sTEXT", MAILTMPLEN - 10,
1740 			  section && *section ? section : "",
1741 			  section && *section ? "." : "");
1742 		else
1743 		  snprintf(imap_cache_section, sizeof(imap_cache_section), "%.*s", MAILTMPLEN - 10,
1744 			  section && *section ? section : "");
1745 
1746 		imap_cache(stream, msgno, imap_cache_section, NULL, &new_text);
1747 	    }
1748 
1749 	    pftc->cache->txt = (void *)NULL;
1750 	    so_give(&pftc->cache);
1751 	    fs_give((void **)&pftc->chunk);
1752 	    fs_give((void **)&pftc);
1753 	    g_pft_desc = NULL;
1754 	    if(len)
1755 	      *len = new_text.size;
1756 	    return ((char *)new_text.data);
1757 	}
1758 	else
1759 	  return((*fetch_full)(stream, msgno, section, len, flags));
1760     }
1761     else
1762       return((*fetch_full)(stream, msgno, section, len, flags));
1763 }
1764 
1765 /*
1766  * c-client callback that handles getting the text
1767  */
1768 char *
1769 partial_text_gets(readfn_t f, void *stream, long unsigned int size, GETS_DATA *md)
1770 {
1771     unsigned long n;
1772 
1773     n = MIN(g_pft_desc->chunksize, size);
1774     g_pft_desc->read +=n;
1775 
1776     (*f) (stream, n, g_pft_desc->chunk);
1777 
1778     if(g_pft_desc->cache)
1779       so_nputs(g_pft_desc->cache, g_pft_desc->chunk, (long) n);
1780 
1781 
1782     return(NULL);
1783 }
1784 
1785 
1786 /*
1787  * Pings the stream. Returns 0 if the stream is dead, non-zero otherwise.
1788  */
1789 long
1790 pine_mail_ping(MAILSTREAM *stream)
1791 {
1792     time_t now;
1793     long   ret = 0L;
1794 
1795     if(!sp_dead_stream(stream)){
1796 	ret = mail_ping(stream);
1797 	if(ret && sp_dead_stream(stream))
1798 	  ret = 0L;
1799     }
1800 
1801     if(ret){
1802 	now = time(0);
1803 	sp_set_last_ping(stream, now);
1804 	sp_set_last_expunged_reaper(stream, now);
1805     }
1806 
1807     return(ret);
1808 }
1809 
1810 
1811 void
1812 pine_mail_check(MAILSTREAM *stream)
1813 {
1814     reset_check_point(stream);
1815     mail_check(stream);
1816 }
1817 
1818 
1819 /*
1820  * Unlike mail_list, this version returns a value. The returned value is
1821  * TRUE if the stream is opened ok, and FALSE if we failed opening the
1822  * stream. This allows us to differentiate between a LIST which returns
1823  * no matches and a failure opening the stream. We do this by pre-opening
1824  * the stream ourselves.
1825  */
1826 int
1827 pine_mail_list(MAILSTREAM *stream, char *ref, char *pat, unsigned int *options)
1828 {
1829     int   we_open = 0;
1830     char *halfopen_target;
1831     char  source[MAILTMPLEN], *target = NULL;
1832     MAILSTREAM *ourstream = NULL;
1833 
1834     dprint((7, "pine_mail_list: ref=%s pat=%s%s\n",
1835 	       ref ? ref : "(NULL)",
1836 	       pat ? pat : "(NULL)",
1837 	       stream ? "" : " (stream was NULL)"));
1838 
1839     if((!ref && check_for_move_mbox(pat, source, sizeof(source), &target))
1840        ||
1841        check_for_move_mbox(ref, source, sizeof(source), &target)){
1842 	ref = NIL;
1843 	pat = target;
1844 	if(options)
1845 	  *options |= PML_IS_MOVE_MBOX;
1846 
1847 	dprint((7,
1848 		   "pine_mail_list: #move special case, listing \"%s\"%s\n",
1849 		   pat ? pat : "(NULL)",
1850 		   stream ? "" : " (stream was NULL)"));
1851     }
1852 
1853     if(!stream && ((pat && *pat == '{') || (ref && *ref == '{'))){
1854 	we_open++;
1855 	if(pat && *pat == '{'){
1856 	    ref = NIL;
1857 	    halfopen_target = pat;
1858 	}
1859 	else
1860 	  halfopen_target = ref;
1861     }
1862 
1863     if(we_open){
1864 	long flags = (OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE);
1865 
1866 	stream = sp_stream_get(halfopen_target, SP_MATCH);
1867 	if(!stream)
1868 	  stream = sp_stream_get(halfopen_target, SP_SAME);
1869 
1870 	if(!stream){
1871 	    DRIVER *d;
1872 
1873 	    /*
1874 	     * POP is a special case. We don't need to have a stream
1875 	     * to call mail_list for a POP name. The else part is the
1876 	     * POP part.
1877 	     */
1878 	    if((d = mail_valid(NIL, halfopen_target, (char *) NIL))
1879 	       && (d->flags & DR_HALFOPEN)){
1880 		stream = pine_mail_open(NIL, halfopen_target, flags, NULL);
1881 		ourstream = stream;
1882 		if(!stream)
1883 		  return(FALSE);
1884 	    }
1885 	    else
1886 	      stream = NULL;
1887 	}
1888 
1889 	mail_list_internal(stream, ref, pat);
1890     }
1891     else
1892       mail_list_internal(stream, ref, pat);
1893 
1894     if(ourstream)
1895       pine_mail_close(ourstream);
1896 
1897     return(TRUE);
1898 }
1899 
1900 
1901 /*
1902  * mail_list_internal -- A monument to software religion and those who
1903  *			 would force it upon us.
1904  */
1905 void
1906 mail_list_internal(MAILSTREAM *s, char *r, char *p)
1907 {
1908     if(F_ON(F_FIX_BROKEN_LIST, ps_global)
1909        && ((s && s->mailbox && *s->mailbox == '{')
1910 	   || (!s && ((r && *r == '{') || (p && *p == '{'))))){
1911 	char tmp[2*MAILTMPLEN];
1912 				/* MAILTMPLEN = sizeof(tmp)/2 */
1913 	snprintf(tmp, sizeof(tmp), "%.*s%.*s", MAILTMPLEN-1, r ? r : "",
1914 		MAILTMPLEN-1, p);
1915 	mail_list(s, "", tmp);
1916     }
1917     else
1918       mail_list(s, r, p);
1919 }
1920 
1921 
1922 long
1923 pine_mail_status(MAILSTREAM *stream, char *mailbox, long int flags)
1924 {
1925     return(pine_mail_status_full(stream, mailbox, flags, NULL, NULL));
1926 }
1927 
1928 
1929 long
1930 pine_mail_status_full(MAILSTREAM *stream, char *mailbox, long int flags,
1931 		      imapuid_t *uidvalidity, imapuid_t *uidnext)
1932 {
1933     char        source[MAILTMPLEN], *target = NULL;
1934     long        ret = NIL;
1935     MAILSTATUS  cache, status;
1936     MAILSTREAM *ourstream = NULL;
1937 
1938     if(check_for_move_mbox(mailbox, source, sizeof(source), &target)){
1939 	memset(&status, 0, sizeof(status));
1940 	memset(&cache,  0, sizeof(cache));
1941 
1942 	/* tell mm_status() to write partial return here */
1943 	pine_cached_status = &status;
1944 
1945 	flags |= (SA_UIDVALIDITY | SA_UIDNEXT | SA_MESSAGES);
1946 
1947 	/* do status of destination */
1948 
1949 	stream = sp_stream_get(target, SP_SAME);
1950 
1951 	/* should never be news, don't worry about mulnewrsc flag*/
1952 	if((ret = pine_mail_status_full(stream, target, flags, uidvalidity,
1953 					uidnext))
1954 	   && !status.recent){
1955 
1956 	    /* do status of source */
1957 	    pine_cached_status = &cache;
1958 	    stream = sp_stream_get(source, SP_SAME);
1959 
1960 	    if(!stream){
1961 		DRIVER *d;
1962 
1963 		if((d = mail_valid (NIL, source, (char *) NIL))
1964 		   && !strcmp(d->name, "imap")){
1965 		    long openflags =OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE;
1966 
1967 		    if(F_ON(F_PREFER_ALT_AUTH, ps_global))
1968 		      openflags |= OP_TRYALT;
1969 
1970 		    stream = pine_mail_open(NULL, source, openflags, NULL);
1971 		    ourstream = stream;
1972 		}
1973 		else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)
1974 			&& d && (!strucmp(d->name, "news")
1975 				 || !strucmp(d->name, "nntp")))
1976 		  flags |= SA_MULNEWSRC;
1977 
1978 	    }
1979 
1980 	    if(!ps_global->user_says_cancel && mail_status(stream, source, flags)){
1981 		DRIVER *d;
1982 		int     is_news = 0;
1983 
1984 		/*
1985 		 * If the target has recent messages, then we don't come
1986 		 * through here. We just use the answer from the target.
1987 		 *
1988 		 * If not, then we leave the target results in "status" and
1989 		 * run a mail_status on the source that puts its results
1990 		 * in "cache". Combine the results from cache with the
1991 		 * results that were already in status.
1992 		 *
1993 		 * We count all messages in the source mailbox as recent and
1994 		 * unseen, even if they are not recent or unseen in the source,
1995 		 * because they will be recent and unseen in the target
1996 		 * when we open it. (Not quite true. It is possible that some
1997 		 * messages from a POP server will end up seen instead
1998 		 * of unseen.
1999 		 * It is also possible that it is correct. If we add unseen, or
2000 		 * if we add messages, we could get it wrong. As far as I
2001 		 * can tell, Pine doesn't ever even use status.unseen, so it
2002 		 * is currently academic anyway.)  Hubert 2003-03-05
2003 		 * (Does now 2004-06-02 in next_folder.)
2004 		 *
2005 		 * However, we don't want to count all messages as recent if
2006 		 * the source mailbox is NNTP or NEWS, because we won't be
2007 		 * deleting those messages from the source.
2008 		 * We only count recent.
2009 		 *
2010 		 * There are other cases that are trouble. One in particular
2011 		 * is an IMAP-to-NNTP proxy, where the messages can't be removed
2012 		 * from the mailbox but they can be deleted. If we count
2013 		 * messages in the source as being recent and it turns out they
2014 		 * were all deleted already, then we incorrectly say the folder
2015 		 * has recent messages when it doesn't. We can recover from that
2016 		 * case at some cost by actually opening the folder the first
2017 		 * time if there are not recents, and then checking to see if
2018 		 * everything is deleted. Subsequently, we store the uid values
2019 		 * (which are returned by status) so that we can know if the
2020 		 * mailbox changed or not. The problem being solved is that
2021 		 * the TAB command indicates new messages in a folder when there
2022 		 * really aren't any. An alternative would be to use the is_news
2023 		 * half of the if-else in all cases. A problem with that is
2024 		 * that there could be non-recent messages sitting in the
2025 		 * source mailbox that we never discover. Hubert 2003-03-28
2026 		 */
2027 
2028 		if((d = mail_valid (NIL, source, (char *) NIL))
2029 		   && (!strcmp(d->name, "nntp") || !strcmp(d->name, "news")))
2030 		  is_news++;
2031 
2032 		if(is_news && cache.flags & SA_RECENT){
2033 		    status.messages += cache.recent;
2034 		    status.recent   += cache.recent;
2035 		    status.unseen   += cache.recent;
2036 		    status.uidnext  += cache.recent;
2037 		}
2038 		else{
2039 		    if(uidvalidity && *uidvalidity
2040 		       && uidnext && *uidnext
2041 		       && cache.flags & SA_UIDVALIDITY
2042 		       && cache.uidvalidity == *uidvalidity
2043 		       && cache.flags & SA_UIDNEXT
2044 		       && cache.uidnext == *uidnext){
2045 			; /* nothing changed in source mailbox */
2046 		    }
2047 		    else if(cache.flags & SA_RECENT && cache.recent){
2048 			status.messages += cache.recent;
2049 			status.recent   += cache.recent;
2050 			status.unseen   += cache.recent;
2051 			status.uidnext  += cache.recent;
2052 		    }
2053 		    else if(!(cache.flags & SA_MESSAGES) || cache.messages){
2054 			long openflags = OP_SILENT | SP_USEPOOL | SP_TEMPUSE;
2055 			long delete_count, not_deleted = 0L;
2056 
2057 			/* Actually open it up and check */
2058 			if(F_ON(F_PREFER_ALT_AUTH, ps_global))
2059 			  openflags |= OP_TRYALT;
2060 
2061 			if(!ourstream)
2062 			  stream = NULL;
2063 
2064 			if(ourstream
2065 			   && !same_stream_and_mailbox(source, ourstream)){
2066 			    pine_mail_close(ourstream);
2067 			    ourstream = stream = NULL;
2068 			}
2069 
2070 			if(!stream){
2071 			    stream = pine_mail_open(stream, source, openflags,
2072 						    NULL);
2073 			    ourstream = stream;
2074 			}
2075 
2076 			if(stream){
2077 			    delete_count = count_flagged(stream, F_DEL);
2078 			    not_deleted = stream->nmsgs - delete_count;
2079 			}
2080 
2081 			status.messages += not_deleted;
2082 			status.recent   += not_deleted;
2083 			status.unseen   += not_deleted;
2084 			status.uidnext  += not_deleted;
2085 		    }
2086 
2087 		    if(uidvalidity && cache.flags & SA_UIDVALIDITY)
2088 		      *uidvalidity = cache.uidvalidity;
2089 
2090 		    if(uidnext && cache.flags & SA_UIDNEXT)
2091 		      *uidnext = cache.uidnext;
2092 		}
2093 	    }
2094 	}
2095 
2096 	/*
2097 	 * Do the regular mm_status callback which we've been intercepting
2098 	 * into different locations above.
2099 	 */
2100 	pine_cached_status = NIL;
2101 	if(ret)
2102 	  mm_status(NULL, mailbox, &status);
2103     }
2104     else{
2105 	if(!stream){
2106 	    DRIVER *d;
2107 
2108 	    if((d = mail_valid (NIL, mailbox, (char *) NIL))
2109 	       && !strcmp(d->name, "imap")){
2110 		long openflags = OP_HALFOPEN|OP_SILENT|SP_USEPOOL|SP_TEMPUSE;
2111 
2112 		if(F_ON(F_PREFER_ALT_AUTH, ps_global))
2113 		  openflags |= OP_TRYALT;
2114 
2115 		/*
2116 		 * We just use this to find the answer.
2117 		 * We're asking for trouble if we do a STATUS on a
2118 		 * selected mailbox. I don't believe this happens in pine.
2119 		 * It does now (2004-06-02) in next_folder if the
2120 		 * F_TAB_USES_UNSEEN option is set and the folder was
2121 		 * already opened.
2122 		 */
2123 		stream = sp_stream_get(mailbox, SP_MATCH);
2124 		if(stream){
2125 		    memset(&status, 0, sizeof(status));
2126 		    if(flags & SA_MESSAGES){
2127 			status.flags |= SA_MESSAGES;
2128 			status.messages = stream->nmsgs;
2129 		    }
2130 
2131 		    if(flags & SA_RECENT){
2132 			status.flags |= SA_RECENT;
2133 			status.recent = stream->recent;
2134 		    }
2135 
2136 		    if(flags & SA_UNSEEN){
2137 		        long i;
2138 			SEARCHPGM *srchpgm;
2139 			MESSAGECACHE *mc;
2140 
2141 			srchpgm = mail_newsearchpgm();
2142 			srchpgm->unseen = 1;
2143 
2144 			pine_mail_search_full(stream, NULL, srchpgm,
2145 					      SE_NOPREFETCH | SE_FREE);
2146 			status.flags |= SA_UNSEEN;
2147 			status.unseen = 0L;
2148 			for(i = 1L; i <= stream->nmsgs; i++)
2149 			  if((mc = mail_elt(stream, i)) && mc->searched)
2150 			    status.unseen++;
2151 		    }
2152 
2153 		    if(flags & SA_UIDVALIDITY){
2154 			status.flags |= SA_UIDVALIDITY;
2155 			status.uidvalidity = stream->uid_validity;
2156 		    }
2157 
2158 		    if(flags & SA_UIDNEXT){
2159 			status.flags |= SA_UIDNEXT;
2160 			status.uidnext = stream->uid_last + 1L;
2161 		    }
2162 
2163 		    mm_status(NULL, mailbox, &status);
2164 		    return T;  /* that's what c-client returns when success */
2165 		}
2166 
2167 		if(!stream)
2168 		  stream = sp_stream_get(mailbox, SP_SAME);
2169 
2170 		if(!stream){
2171 		    stream = pine_mail_open(NULL, mailbox, openflags, NULL);
2172 		    ourstream = stream;
2173 		}
2174 	    }
2175 	    else if(F_ON(F_ENABLE_MULNEWSRCS, ps_global)
2176 		    && d && (!strucmp(d->name, "news")
2177 			     || !strucmp(d->name, "nntp")))
2178 	      flags |= SA_MULNEWSRC;
2179 	}
2180 
2181 	if(!ps_global->user_says_cancel)
2182 	  ret = mail_status(stream, mailbox, flags);	/* non #move case */
2183     }
2184 
2185     if(ourstream)
2186       pine_mail_close(ourstream);
2187 
2188     return ret;
2189 }
2190 
2191 
2192 /*
2193  * Check for a mailbox name that is a legitimate #move mailbox.
2194  *
2195  * Args   mbox     -- The mailbox name to check
2196  *      sourcebuf  -- Copy the source mailbox name into this buffer
2197  *      sbuflen    -- Length of sourcebuf
2198  *      targetptr  -- Set the pointer this points to to point to the
2199  *                      target mailbox name in the original string
2200  *
2201  * Returns  1 - is a #move mailbox
2202  *          0 - not
2203  */
2204 int
2205 check_for_move_mbox(char *mbox, char *sourcebuf, size_t sbuflen, char **targetptr)
2206 {
2207     char delim, *s;
2208     int  i;
2209 
2210     if(mbox && (mbox[0] == '#')
2211        && ((mbox[1] == 'M') || (mbox[1] == 'm'))
2212        && ((mbox[2] == 'O') || (mbox[2] == 'o'))
2213        && ((mbox[3] == 'V') || (mbox[3] == 'v'))
2214        && ((mbox[4] == 'E') || (mbox[4] == 'e'))
2215        && (delim = mbox[5])
2216        && (s = strchr(mbox+6, delim))
2217        && (i = s++ - (mbox + 6))
2218        && (!sourcebuf || i < sbuflen)){
2219 
2220 	if(sourcebuf){
2221 	    strncpy(sourcebuf, mbox+6, i);	/* copy source mailbox name */
2222 	    sourcebuf[i] = '\0';
2223 	}
2224 
2225 	if(targetptr)
2226 	  *targetptr = s;
2227 
2228 	return 1;
2229     }
2230 
2231     return 0;
2232 }
2233 
2234 
2235 /*
2236  * Checks through stream cache for a stream pointer already open to
2237  * this mailbox, read/write. Very similar to sp_stream_get, but we want
2238  * to look at all streams, not just imap streams.
2239  * Right now it is very specialized. If we want to use it more generally,
2240  * generalize it or combine it with sp_stream_get somehow.
2241  */
2242 MAILSTREAM *
2243 already_open_stream(char *mailbox, int flags)
2244 {
2245     int         i;
2246     MAILSTREAM *m;
2247 
2248     if(!mailbox)
2249       return(NULL);
2250 
2251     if(*mailbox == '{'){
2252 	for(i = 0; i < ps_global->s_pool.nstream; i++){
2253 	    m = ps_global->s_pool.streams[i];
2254 	    if(m && !(flags & AOS_RW_ONLY && m->rdonly)
2255 	       && (*m->mailbox == '{') && !sp_dead_stream(m)
2256 	       && same_stream_and_mailbox(mailbox, m))
2257 	      return(m);
2258 	}
2259     }
2260     else{
2261 	char *cn, tmp[MAILTMPLEN];
2262 
2263 	cn = mailboxfile(tmp, mailbox);
2264 	for(i = 0; i < ps_global->s_pool.nstream; i++){
2265 	    m = ps_global->s_pool.streams[i];
2266 	    if(m && !(flags & AOS_RW_ONLY && m->rdonly)
2267 	       && (*m->mailbox != '{') && !sp_dead_stream(m)
2268 	       && ((cn && *cn && !strcmp(cn, m->mailbox))
2269 	           || !strcmp(mailbox, m->original_mailbox)
2270 		   || !strcmp(mailbox, m->mailbox)))
2271 	      return(m);
2272 	}
2273     }
2274 
2275     return(NULL);
2276 }
2277 
2278 
2279 void
2280 pine_imap_cmd_happened(MAILSTREAM *stream, char *cmd, long int flags)
2281 {
2282     dprint((9, "imap_cmd(%s, %s, 0x%lx)\n",
2283 	    STREAMNAME(stream), cmd ? cmd : "?", flags));
2284 
2285     if(cmd && !strucmp(cmd, "CHECK"))
2286       reset_check_point(stream);
2287 
2288     ps_global->can_interrupt = 0;	/* never interrupt anything */
2289 
2290     if(is_imap_stream(stream)){
2291 	time_t now;
2292 
2293 	now = time(0);
2294 	sp_set_last_ping(stream, now);
2295 	sp_set_last_activity(stream, now);
2296 	if(!(flags & SC_EXPUNGEDEFERRED))
2297 	  sp_set_last_expunged_reaper(stream, now);
2298 
2299 	if(cmd && !strucmp(cmd, "NOOP")) /* but can interrupt this one */
2300 	  ps_global->can_interrupt = 1;
2301     }
2302 }
2303 
2304 
2305 /*
2306  * Tells us whether we ought to check for a dead stream or not.
2307  * We assume that we ought to check if it is not IMAP and if it is IMAP we
2308  * don't need to check if the last activity was within the last 5 minutes.
2309  */
2310 int
2311 recent_activity(MAILSTREAM *stream)
2312 {
2313     if(is_imap_stream(stream) && !sp_dead_stream(stream)
2314        && (time(0) - sp_last_activity(stream) < 5L * 60L))
2315       return 1;
2316     else
2317       return 0;
2318 }
2319 
2320 void
2321 sp_cleanup_dead_streams(void)
2322 {
2323     int         i;
2324     MAILSTREAM *m;
2325 
2326     (void) streams_died();	/* tell user in case they don't know yet */
2327 
2328     for(i = 0; i < ps_global->s_pool.nstream; i++){
2329 	m = ps_global->s_pool.streams[i];
2330 	if(m && sp_dead_stream(m))
2331 	  pine_mail_close(m);
2332     }
2333 }
2334 
2335 
2336 /*
2337  * Returns 0 if stream flags not set, non-zero if they are.
2338  */
2339 int
2340 sp_flagged(MAILSTREAM *stream, long unsigned int flags)
2341 {
2342     return(sp_flags(stream) & flags);
2343 }
2344 
2345 
2346 void
2347 sp_set_fldr(MAILSTREAM *stream, char *folder)
2348 {
2349     PER_STREAM_S **pss;
2350 
2351     pss = sp_data(stream);
2352     if(pss && *pss){
2353 	if((*pss)->fldr)
2354 	  fs_give((void **) &(*pss)->fldr);
2355 
2356 	if(folder)
2357 	  (*pss)->fldr = cpystr(folder);
2358     }
2359 }
2360 
2361 
2362 void
2363 sp_set_saved_cur_msg_id(MAILSTREAM *stream, char *id)
2364 {
2365     PER_STREAM_S **pss;
2366 
2367     pss = sp_data(stream);
2368     if(pss && *pss){
2369 	if((*pss)->saved_cur_msg_id)
2370 	  fs_give((void **) &(*pss)->saved_cur_msg_id);
2371 
2372 	if(id)
2373 	  (*pss)->saved_cur_msg_id = cpystr(id);
2374     }
2375 }
2376 
2377 
2378 /*
2379  * Sets flags absolutely, erasing old flags.
2380  */
2381 void
2382 sp_flag(MAILSTREAM *stream, long unsigned int flags)
2383 {
2384     if(!stream)
2385       return;
2386 
2387     dprint((9, "sp_flag(%s, 0x%x): %s%s%s%s%s%s%s%s\n",
2388 	    (stream && stream->mailbox) ? stream->mailbox : "?",
2389 	    flags,
2390 	    flags ? "set" : "clear",
2391 	    (flags & SP_LOCKED)     ? " SP_LOCKED" : "",
2392 	    (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
2393 	    (flags & SP_INBOX)      ? " SP_INBOX"      : "",
2394 	    (flags & SP_USERFLDR)   ? " SP_USERFLDR"   : "",
2395 	    (flags & SP_USEPOOL)    ? " SP_USEPOOL"    : "",
2396 	    (flags & SP_TEMPUSE)    ? " SP_TEMPUSE"    : "",
2397 	    !flags                  ? " ALL" : ""));
2398 
2399     sp_set_flags(stream, flags);
2400 }
2401 
2402 
2403 /*
2404  * Clear individual stream flags.
2405  */
2406 void
2407 sp_unflag(MAILSTREAM *stream, long unsigned int flags)
2408 {
2409     if(!stream || !flags)
2410       return;
2411 
2412     dprint((9, "sp_unflag(%s, 0x%x): unset%s%s%s%s%s%s\n",
2413 	    (stream && stream->mailbox) ? stream->mailbox : "?",
2414 	    flags,
2415 	    (flags & SP_LOCKED)     ? " SP_LOCKED" : "",
2416 	    (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
2417 	    (flags & SP_INBOX)      ? " SP_INBOX"      : "",
2418 	    (flags & SP_USERFLDR)   ? " SP_USERFLDR"   : "",
2419 	    (flags & SP_USEPOOL)    ? " SP_USEPOOL"    : "",
2420 	    (flags & SP_TEMPUSE)    ? " SP_TEMPUSE"    : ""));
2421 
2422     sp_set_flags(stream, sp_flags(stream) & ~flags);
2423 
2424     flags = sp_flags(stream);
2425     dprint((9, "sp_unflag(%s, 0x%x): result:%s%s%s%s%s%s\n",
2426 	    (stream && stream->mailbox) ? stream->mailbox : "?",
2427 	    flags,
2428 	    (flags & SP_LOCKED)     ? " SP_LOCKED" : "",
2429 	    (flags & SP_PERMLOCKED) ? " SP_PERMLOCKED" : "",
2430 	    (flags & SP_INBOX)      ? " SP_INBOX"      : "",
2431 	    (flags & SP_USERFLDR)   ? " SP_USERFLDR"   : "",
2432 	    (flags & SP_USEPOOL)    ? " SP_USEPOOL"    : "",
2433 	    (flags & SP_TEMPUSE)    ? " SP_TEMPUSE"    : ""));
2434 }
2435 
2436 
2437 /*
2438  * Set dead stream indicator and close if not locked.
2439  */
2440 void
2441 sp_mark_stream_dead(MAILSTREAM *stream)
2442 {
2443     if(!stream)
2444       return;
2445 
2446     dprint((9, "sp_mark_stream_dead(%s)\n",
2447 	    (stream && stream->mailbox) ? stream->mailbox : "?"));
2448 
2449     /*
2450      * If the stream isn't locked, it is no longer useful. Get rid of it.
2451      */
2452     if(!sp_flagged(stream, SP_LOCKED))
2453       pine_mail_actually_close(stream);
2454     else{
2455 	/*
2456 	 * If it is locked, then we have to worry about references to it
2457 	 * that still exist. For example, it might be a permlocked stream
2458 	 * or it might be the current stream. We need to let it be discovered
2459 	 * by those referencers instead of just eliminating it, so that they
2460 	 * can clean up the mess they need to clean up.
2461 	 */
2462 	sp_set_dead_stream(stream, 1);
2463     }
2464 }
2465 
2466 
2467 /*
2468  * Returns the number of streams in the stream pool which are
2469  * SP_USEPOOL but not SP_PERMLOCKED.
2470  */
2471 int
2472 sp_nusepool_notperm(void)
2473 {
2474     int         i, cnt = 0;
2475     MAILSTREAM *m;
2476 
2477     for(i = 0; i < ps_global->s_pool.nstream; i++){
2478 	m = ps_global->s_pool.streams[i];
2479 	if(sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_PERMLOCKED))
2480 	  cnt++;
2481     }
2482 
2483     return(cnt);
2484 }
2485 
2486 
2487 /*
2488  * Returns the number of folders that the user has marked to be PERMLOCKED
2489  * folders (plus INBOX) that are remote IMAP folders.
2490  *
2491  * This routine depends on the fact that VAR_INBOX_PATH, VAR_PERMLOCKED,
2492  * and the ps_global->context_list are correctly set.
2493  */
2494 int
2495 sp_nremote_permlocked(void)
2496 {
2497     int         cnt = 0;
2498     char      **lock_these, *p = NULL, *dummy = NULL, *lt;
2499     DRIVER     *d;
2500 
2501     /* first check if INBOX is remote */
2502     lt = ps_global->VAR_INBOX_PATH;
2503     if(lt && (d=mail_valid(NIL, lt, (char *) NIL))
2504        && !strcmp(d->name, "imap"))
2505       cnt++;
2506 
2507     /* then count the user-specified permlocked folders */
2508     for(lock_these = ps_global->VAR_PERMLOCKED; lock_these && *lock_these;
2509 	lock_these++){
2510 
2511 	/*
2512 	 * Skip inbox, already done above. Should do this better so that we
2513 	 * catch the case where the user puts the technical spec of the inbox
2514 	 * in the list, or where the user lists one folder twice.
2515 	 */
2516 	if(*lock_these && !strucmp(*lock_these, ps_global->inbox_name))
2517 	  continue;
2518 
2519 	/* there isn't really a pair, it just dequotes for us */
2520 	get_pair(*lock_these, &dummy, &p, 0, 0);
2521 
2522 	/*
2523 	 * Check to see if this is an incoming nickname and replace it
2524 	 * with the full name.
2525 	 */
2526 	if(!(p && ps_global->context_list
2527 	     && ps_global->context_list->use & CNTXT_INCMNG
2528 	     && (lt=folder_is_nick(p, FOLDERS(ps_global->context_list),
2529 				   FN_WHOLE_NAME))))
2530 	  lt = p;
2531 
2532 	if(dummy)
2533 	  fs_give((void **) &dummy);
2534 
2535 	if(lt && (d=mail_valid(NIL, lt, (char *) NIL))
2536 	   && !strcmp(d->name, "imap"))
2537 	  cnt++;
2538 
2539 	if(p)
2540 	  fs_give((void **) &p);
2541     }
2542 
2543     return(cnt);
2544 }
2545 
2546 
2547 /*
2548  * Look for an already open stream that can be used for a new purpose.
2549  * (Note that we only look through streams flagged SP_USEPOOL.)
2550  *
2551  * Args:   mailbox
2552  *           flags
2553  *
2554  * Flags is a set of values or'd together which tells us what the request
2555  * is looking for.
2556  *
2557  *  Returns: a live stream from the stream pool or NULL.
2558  */
2559 MAILSTREAM *
2560 sp_stream_get(char *mailbox, long unsigned int flags)
2561 {
2562     int         i;
2563     MAILSTREAM *m;
2564 
2565     dprint((7, "sp_stream_get(%s):%s%s%s%s%s\n",
2566 	    mailbox ? mailbox : "?",
2567 	    (flags & SP_MATCH)    ? " SP_MATCH"    : "",
2568 	    (flags & SP_RO_OK)    ? " SP_RO_OK"    : "",
2569 	    (flags & SP_SAME)     ? " SP_SAME"     : "",
2570 	    (flags & SP_UNLOCKED) ? " SP_UNLOCKED" : "",
2571 	    (flags & SP_TEMPUSE)  ? " SP_TEMPUSE" : ""));
2572 
2573     /* look for stream already open to this mailbox */
2574     if(flags & SP_MATCH){
2575 	for(i = 0; i < ps_global->s_pool.nstream; i++){
2576 	    m = ps_global->s_pool.streams[i];
2577 	    if(m && sp_flagged(m, SP_USEPOOL)
2578 	       && (!m->rdonly || (flags & SP_RO_OK)) && !sp_dead_stream(m)
2579 	       && same_stream_and_mailbox(mailbox, m)){
2580 		if((sp_flagged(m, SP_LOCKED) && recent_activity(m))
2581 		   || pine_mail_ping(m)){
2582 		    dprint((7,
2583 		       "sp_stream_get: found exact match, slot %d\n", i));
2584 		    if(!sp_flagged(m, SP_LOCKED)){
2585 			dprint((7,
2586 			       "reset idle timer1: next TAG %08lx (%s)\n",
2587 			       m->gensym,
2588 			       debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
2589 			sp_set_last_use_time(m, time(0));
2590 		    }
2591 
2592 		    return(m);
2593 		}
2594 
2595 		sp_mark_stream_dead(m);
2596 	    }
2597 	}
2598     }
2599 
2600     /*
2601      * SP_SAME will not match if an SP_MATCH match would have worked.
2602      * If the caller is interested in SP_MATCH streams as well as SP_SAME
2603      * streams then the caller should make two separate calls to this
2604      * routine.
2605      */
2606     if(flags & SP_SAME){
2607 	/*
2608 	 * If the flags arg does not have either SP_TEMPUSE or SP_UNLOCKED
2609 	 * set, then we'll accept any stream, even if locked.
2610 	 * We want to prefer the LOCKED case so that we don't have to ping.
2611 	 */
2612 	if(!(flags & SP_UNLOCKED) && !(flags & SP_TEMPUSE)){
2613 	    for(i = 0; i < ps_global->s_pool.nstream; i++){
2614 		m = ps_global->s_pool.streams[i];
2615 		if(m && sp_flagged(m, SP_USEPOOL)
2616 		   && sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
2617 		   && same_stream(mailbox, m)
2618 		   && !same_stream_and_mailbox(mailbox, m)){
2619 		    if(recent_activity(m) || pine_mail_ping(m)){
2620 			dprint((7,
2621 			    "sp_stream_get: found SAME match, slot %d\n", i));
2622 			return(m);
2623 		    }
2624 
2625 		    sp_mark_stream_dead(m);
2626 		}
2627 	    }
2628 
2629 	    /* consider the unlocked streams */
2630 	    for(i = 0; i < ps_global->s_pool.nstream; i++){
2631 		m = ps_global->s_pool.streams[i];
2632 		if(m && sp_flagged(m, SP_USEPOOL)
2633 		   && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
2634 		   && same_stream(mailbox, m)
2635 		   && !same_stream_and_mailbox(mailbox, m)){
2636 		    /* always ping unlocked streams */
2637 		    if(pine_mail_ping(m)){
2638 			dprint((7,
2639 			    "sp_stream_get: found SAME match, slot %d\n", i));
2640 			dprint((7,
2641 			   "reset idle timer4: next TAG %08lx (%s)\n",
2642 			   m->gensym,
2643 			   debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
2644 			sp_set_last_use_time(m, time(0));
2645 
2646 			return(m);
2647 		    }
2648 
2649 		    sp_mark_stream_dead(m);
2650 		}
2651 	    }
2652 	}
2653 
2654 	/*
2655 	 * Prefer streams marked SP_TEMPUSE and not LOCKED.
2656 	 * If SP_TEMPUSE is set in the flags arg then this is the
2657 	 * only loop we try.
2658 	 */
2659 	for(i = 0; i < ps_global->s_pool.nstream; i++){
2660 	    m = ps_global->s_pool.streams[i];
2661 	    if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE)
2662 	       && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
2663 	       && same_stream(mailbox, m)
2664 	       && !same_stream_and_mailbox(mailbox, m)){
2665 		if(pine_mail_ping(m)){
2666 		    dprint((7,
2667 		      "sp_stream_get: found SAME/TEMPUSE match, slot %d\n", i));
2668 		    dprint((7,
2669 			   "reset idle timer2: next TAG %08lx (%s)\n",
2670 			   m->gensym,
2671 			   debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
2672 		    sp_set_last_use_time(m, time(0));
2673 		    return(m);
2674 		}
2675 
2676 		sp_mark_stream_dead(m);
2677 	    }
2678 	}
2679 
2680 	/*
2681 	 * If SP_TEMPUSE is not set in the flags arg but SP_UNLOCKED is,
2682 	 * then we will consider
2683 	 * streams which are not marked SP_TEMPUSE (but are still not
2684 	 * locked). We go through these in reverse order so that we'll get
2685 	 * the last one added instead of the first one. It's not clear if
2686 	 * that is a good idea or if a more complex search would somehow
2687 	 * be better. Maybe we should use a round-robin sort of search
2688 	 * here so that we don't leave behind unused streams. Or maybe
2689 	 * we should keep track of when we used it and look for the LRU stream.
2690 	 */
2691 	if(!(flags & SP_TEMPUSE)){
2692 	    for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){
2693 		m = ps_global->s_pool.streams[i];
2694 		if(m && sp_flagged(m, SP_USEPOOL)
2695 		   && !sp_flagged(m, SP_LOCKED) && !sp_dead_stream(m)
2696 		   && same_stream(mailbox, m)
2697 		   && !same_stream_and_mailbox(mailbox, m)){
2698 		    if(pine_mail_ping(m)){
2699 			dprint((7,
2700 		    "sp_stream_get: found SAME/UNLOCKED match, slot %d\n", i));
2701 			dprint((7,
2702 			   "reset idle timer3: next TAG %08lx (%s)\n",
2703 			   m->gensym,
2704 			   debug_time(1, ps_global->debug_timestamp, ps_global->signal_in_progress)));
2705 			sp_set_last_use_time(m, time(0));
2706 			return(m);
2707 		    }
2708 
2709 		    sp_mark_stream_dead(m);
2710 		}
2711 	    }
2712 	}
2713     }
2714 
2715     /*
2716      * If we can't find a useful stream to use in pine_mail_open, we may
2717      * want to re-use one that is not actively being used even though it
2718      * is not on the same server. We'll have to close it and then re-use
2719      * the slot.
2720      */
2721     if(!(flags & (SP_SAME | SP_MATCH))){
2722 	/*
2723 	 * Prefer streams marked SP_TEMPUSE and not LOCKED.
2724 	 * If SP_TEMPUSE is set in the flags arg then this is the
2725 	 * only loop we try.
2726 	 */
2727 	for(i = 0; i < ps_global->s_pool.nstream; i++){
2728 	    m = ps_global->s_pool.streams[i];
2729 	    if(m && sp_flagged(m, SP_USEPOOL) && sp_flagged(m, SP_TEMPUSE)
2730 	       && !sp_flagged(m, SP_LOCKED)){
2731 		dprint((7,
2732     "sp_stream_get: found Not-SAME/TEMPUSE match, slot %d\n", i));
2733 		/*
2734 		 * We ping it in case there is new mail that we should
2735 		 * pass through our filters. Pine_mail_actually_close will
2736 		 * do that.
2737 		 */
2738 		(void) pine_mail_ping(m);
2739 		return(m);
2740 	    }
2741 	}
2742 
2743 	/*
2744 	 * If SP_TEMPUSE is not set in the flags arg, then we will consider
2745 	 * streams which are not marked SP_TEMPUSE (but are still not
2746 	 * locked). Maybe we should use a round-robin sort of search
2747 	 * here so that we don't leave behind unused streams. Or maybe
2748 	 * we should keep track of when we used it and look for the LRU stream.
2749 	 */
2750 	if(!(flags & SP_TEMPUSE)){
2751 	    for(i = ps_global->s_pool.nstream - 1; i >= 0; i--){
2752 		m = ps_global->s_pool.streams[i];
2753 		if(m && sp_flagged(m, SP_USEPOOL) && !sp_flagged(m, SP_LOCKED)){
2754 		    dprint((7,
2755 	"sp_stream_get: found Not-SAME/UNLOCKED match, slot %d\n", i));
2756 		    /*
2757 		     * We ping it in case there is new mail that we should
2758 		     * pass through our filters. Pine_mail_actually_close will
2759 		     * do that.
2760 		     */
2761 		    (void) pine_mail_ping(m);
2762 		    return(m);
2763 		}
2764 	    }
2765 	}
2766     }
2767 
2768     dprint((7, "sp_stream_get: no match found\n"));
2769 
2770     return(NULL);
2771 }
2772 
2773 
2774 void
2775 sp_end(void)
2776 {
2777     int         i;
2778     MAILSTREAM *m;
2779 
2780     dprint((7, "sp_end\n"));
2781 
2782     for(i = 0; i < ps_global->s_pool.nstream; i++){
2783 	m = ps_global->s_pool.streams[i];
2784 	if(m)
2785 	  pine_mail_actually_close(m);
2786     }
2787 
2788     if(ps_global->s_pool.streams)
2789       fs_give((void **) &ps_global->s_pool.streams);
2790 
2791     ps_global->s_pool.nstream = 0;
2792 }
2793 
2794 
2795 /*
2796  * Find a vacant slot to put this new stream in.
2797  * We are willing to close and kick out another stream as long as it isn't
2798  * LOCKED. However, we may find that there is no place to put this one
2799  * because all the slots are used and locked. For now, we'll return -1
2800  * in that case and leave the new stream out of the pool.
2801  */
2802 int
2803 sp_add(MAILSTREAM *stream, int usepool)
2804 {
2805     int         i, slot = -1;
2806     MAILSTREAM *m;
2807 
2808     dprint((7, "sp_add(%s, %d)\n",
2809 	    (stream && stream->mailbox) ? stream->mailbox : "?", usepool));
2810 
2811     if(!stream){
2812 	dprint((7, "sp_add: NULL stream\n"));
2813 	return -1;
2814     }
2815 
2816     /* If this stream is already there, don't add it again */
2817     for(i = 0; i < ps_global->s_pool.nstream; i++){
2818 	m = ps_global->s_pool.streams[i];
2819 	if(m == stream){
2820 	    slot = i;
2821 	    dprint((7,
2822 		    "sp_add: stream was already in slot %d\n", slot));
2823 	    return 0;
2824 	}
2825     }
2826 
2827     if(usepool && !sp_flagged(stream, SP_PERMLOCKED)
2828        && sp_nusepool_notperm() >= ps_global->s_pool.max_remstream){
2829 	dprint((7,
2830 		"sp_add: reached max implicit SP_USEPOOL of %d\n",
2831 		ps_global->s_pool.max_remstream));
2832 	return -1;
2833     }
2834 
2835     /* Look for an unused slot */
2836     for(i = 0; i < ps_global->s_pool.nstream; i++){
2837 	m = ps_global->s_pool.streams[i];
2838 	if(!m){
2839 	    slot = i;
2840 	    dprint((7,
2841 		    "sp_add: using empty slot %d\n", slot));
2842 	    break;
2843 	}
2844     }
2845 
2846     /* else, allocate more space */
2847     if(slot < 0){
2848 	ps_global->s_pool.nstream++;
2849 	slot = ps_global->s_pool.nstream - 1;
2850 	if(ps_global->s_pool.streams){
2851 	    fs_resize((void **) &ps_global->s_pool.streams,
2852 		      ps_global->s_pool.nstream *
2853 		        sizeof(*ps_global->s_pool.streams));
2854 	    ps_global->s_pool.streams[slot] = NULL;
2855 	}
2856 	else{
2857 	    ps_global->s_pool.streams =
2858 		(MAILSTREAM **) fs_get(ps_global->s_pool.nstream *
2859 					sizeof(*ps_global->s_pool.streams));
2860 	    memset(ps_global->s_pool.streams, 0,
2861 		   ps_global->s_pool.nstream *
2862 		    sizeof(*ps_global->s_pool.streams));
2863 	}
2864 
2865 	dprint((7,
2866 	    "sp_add: allocate more space, using new slot %d\n", slot));
2867     }
2868 
2869     if(slot >= 0 && slot < ps_global->s_pool.nstream){
2870 	ps_global->s_pool.streams[slot] = stream;
2871 	return 0;
2872     }
2873     else{
2874 	dprint((7, "sp_add: failed to find a slot!\n"));
2875 	return -1;
2876     }
2877 }
2878 
2879 
2880 /*
2881  * Simply remove this stream from the stream pool.
2882  */
2883 void
2884 sp_delete(MAILSTREAM *stream)
2885 {
2886     int         i;
2887     MAILSTREAM *m;
2888 
2889     if(!stream)
2890       return;
2891 
2892     dprint((7, "sp_delete(%s)\n",
2893 	    (stream && stream->mailbox) ? stream->mailbox : "?"));
2894 
2895     /*
2896      * There are some global stream pointers that we have to worry
2897      * about before deleting the stream.
2898      */
2899 
2900     /* first, mail_stream is the global currently open folder */
2901     if(ps_global->mail_stream == stream)
2902       ps_global->mail_stream = NULL;
2903 
2904     /* remote address books may have open stream pointers */
2905     note_closed_adrbk_stream(stream);
2906 
2907     if(pith_opt_closing_stream)
2908       (*pith_opt_closing_stream)(stream);
2909 
2910     for(i = 0; i < ps_global->s_pool.nstream; i++){
2911 	m = ps_global->s_pool.streams[i];
2912 	if(m == stream){
2913 	    ps_global->s_pool.streams[i] = NULL;
2914 	    dprint((7,
2915 		    "sp_delete: stream removed from slot %d\n", i));
2916 	    return;
2917 	}
2918     }
2919 }
2920 
2921 
2922 /*
2923  * Returns 1 if any locked userfldr is dead, 0 if all alive.
2924  */
2925 int
2926 sp_a_locked_stream_is_dead(void)
2927 {
2928     int         i, ret = 0;
2929     MAILSTREAM *m;
2930 
2931     for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
2932 	m = ps_global->s_pool.streams[i];
2933 	if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
2934 	   && sp_dead_stream(m))
2935 	  ret++;
2936     }
2937 
2938     return(ret);
2939 }
2940 
2941 
2942 /*
2943  * Returns 1 if any locked stream is changed, 0 otherwise
2944  */
2945 int
2946 sp_a_locked_stream_changed(void)
2947 {
2948     int         i, ret = 0;
2949     MAILSTREAM *m;
2950 
2951     for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
2952 	m = ps_global->s_pool.streams[i];
2953 	if(m && sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)
2954 	   && sp_mail_box_changed(m))
2955 	  ret++;
2956     }
2957 
2958     return(ret);
2959 }
2960 
2961 
2962 /*
2963  * Returns the inbox stream or NULL.
2964  */
2965 MAILSTREAM *
2966 sp_inbox_stream(void)
2967 {
2968     int         i;
2969     MAILSTREAM *m, *ret = NULL;
2970 
2971     for(i = 0; !ret && i < ps_global->s_pool.nstream; i++){
2972 	m = ps_global->s_pool.streams[i];
2973 	if(m && sp_flagged(m, SP_INBOX))
2974 	  ret = m;
2975     }
2976 
2977     return(ret);
2978 }
2979 
2980 
2981 /*
2982  * Make sure that the sp_data per-stream data storage area exists.
2983  *
2984  * Returns a handle to the sp_data data unless stream is NULL,
2985  *         in which case NULL is returned
2986  */
2987 PER_STREAM_S **
2988 sp_data(MAILSTREAM *stream)
2989 {
2990     PER_STREAM_S **pss = NULL;
2991 
2992     if(stream){
2993 	if(*(pss = (PER_STREAM_S **) &stream->sparep) == NULL){
2994 	    *pss = (PER_STREAM_S *) fs_get(sizeof(PER_STREAM_S));
2995 	    memset(*pss, 0, sizeof(PER_STREAM_S));
2996 	    reset_check_point(stream);
2997 	}
2998     }
2999 
3000     return(pss);
3001 }
3002 
3003 
3004 /*
3005  * Returns a pointer to the msgmap associated with the argument stream.
3006  *
3007  * If the PER_STREAM_S data or the msgmap does not already exist, it will be
3008  * created.
3009  */
3010 MSGNO_S *
3011 sp_msgmap(MAILSTREAM *stream)
3012 {
3013     MSGNO_S      **msgmap = NULL;
3014     PER_STREAM_S **pss    = NULL;
3015 
3016     pss = sp_data(stream);
3017 
3018     if(pss && *pss
3019        && (*(msgmap = (MSGNO_S **) &(*pss)->msgmap) == NULL))
3020       mn_init(msgmap, stream->nmsgs);
3021 
3022     return(msgmap ? *msgmap : NULL);
3023 }
3024 
3025 
3026 void
3027 sp_free_callback(void **sparep)
3028 {
3029     PER_STREAM_S **pss;
3030     MAILSTREAM    *stream = NULL, *m;
3031     int            i;
3032 
3033     pss = (PER_STREAM_S **) sparep;
3034 
3035     if(pss && *pss){
3036 	/*
3037 	 * It is possible that this has been called from c-client when
3038 	 * we weren't expecting it. We need to clean up the stream pool
3039 	 * entries if the stream that goes with this pointer is in the
3040 	 * stream pool somewhere.
3041 	 */
3042 	for(i = 0; !stream && i < ps_global->s_pool.nstream; i++){
3043 	    m = ps_global->s_pool.streams[i];
3044 	    if(sparep && *sparep && m && m->sparep == *sparep)
3045 	      stream = m;
3046 	}
3047 
3048 	if(stream){
3049 	    if(ps_global->mail_stream == stream)
3050 	      ps_global->mail_stream = NULL;
3051 
3052 	    sp_delete(stream);
3053 	}
3054 
3055 	sp_free(pss);
3056     }
3057 }
3058 
3059 
3060 /*
3061  * Free the data but don't mess with the stream pool.
3062  */
3063 void
3064 sp_free(PER_STREAM_S **pss)
3065 {
3066     if(pss && *pss){
3067 	if((*pss)->msgmap){
3068 	    if(ps_global->msgmap == (*pss)->msgmap)
3069 	      ps_global->msgmap = NULL;
3070 
3071 	    mn_give(&(*pss)->msgmap);
3072 	}
3073 
3074 	if((*pss)->fldr)
3075 	  fs_give((void **) &(*pss)->fldr);
3076 
3077 	fs_give((void **) pss);
3078     }
3079 }
3080 
3081 
3082 
3083 /*----------------------------------------------------------------------
3084   See if stream can be used for a mailbox name
3085 
3086    Accepts: mailbox name
3087             candidate stream
3088    Returns: stream if it can be used, else NIL
3089 
3090   This is called to weed out unnecessary use of c-client streams. In other
3091   words, to help facilitate re-use of streams.
3092 
3093   This code is very similar to the same_remote_mailboxes code below, which
3094   is used in pine_mail_open. That code compares two mailbox names. One is
3095   usually from the config file and the other is either from the config file
3096   or is typed in. Here and in same_stream_and_mailbox below, we're comparing
3097   an open stream to a name instead of two names. We could conceivably use
3098   same_remote_mailboxes to compare stream->mailbox to name, but it isn't
3099   exactly the same and the differences may be important. Some stuff that
3100   happens here seems wrong, but it isn't easy to fix.
3101   Having !mb_n.port count as a match to any mb_s.port isn't right. It should
3102   only match if mb_s.port is equal to the default, but the default isn't
3103   something that is available to us. The same thing is done in c-client in
3104   the mail_usable_network_stream() routine, and it isn't right there, either.
3105   The semantics of a missing user are also suspect, because just like with
3106   port, a default is used.
3107   ----*/
3108 MAILSTREAM *
3109 same_stream(char *name, MAILSTREAM *stream)
3110 {
3111     NETMBX mb_s, mb_n, mb_o;
3112 
3113     if(stream && stream->mailbox && *stream->mailbox && name && *name
3114        && !(sp_dead_stream(stream))
3115        && mail_valid_net_parse(stream->mailbox, &mb_s)
3116        && mail_valid_net_parse(stream->original_mailbox, &mb_o)
3117        && mail_valid_net_parse(name, &mb_n)
3118        && !strucmp(mb_n.service, mb_s.service)
3119        && (!strucmp(mb_n.host, mb_o.host)	/* s is already canonical */
3120 	   || !strucmp(canonical_name(mb_n.host), mb_s.host))
3121        && (!mb_n.port || mb_n.port == mb_s.port)
3122        && mb_n.anoflag == stream->anonymous
3123        && ((mb_n.user && *mb_n.user &&
3124 	    mb_s.user && !strcmp(mb_n.user, mb_s.user))
3125 	   ||
3126 	   ((!mb_n.user || !*mb_n.user)
3127 	    && mb_s.user
3128 	    && ((ps_global->VAR_USER_ID
3129 		 && !strcmp(ps_global->VAR_USER_ID, mb_s.user))
3130 		||
3131 		(!ps_global->VAR_USER_ID
3132 		 && ps_global->ui.login[0]
3133 		 && !strcmp(ps_global->ui.login, mb_s.user))))
3134 	   ||
3135 	   (!((mb_n.user && *mb_n.user) || (mb_s.user && *mb_s.user))
3136 	    && stream->anonymous))
3137        && (struncmp(mb_n.service, "imap", 4) ? 1 : strcmp(imap_host(stream), ".NO-IMAP-CONNECTION."))){
3138 	dprint((7, "same_stream: name->%s == stream->%s: yes\n",
3139 	       name ? name : "?",
3140 	       (stream && stream->mailbox) ? stream->mailbox : "NULL"));
3141 	return(stream);
3142     }
3143 
3144     dprint((7, "same_stream: name->%s == stream->%s: no dice\n",
3145 	   name ? name : "?",
3146 	   (stream && stream->mailbox) ? stream->mailbox : "NULL"));
3147     return(NULL);
3148 }
3149 
3150 
3151 
3152 /*----------------------------------------------------------------------
3153   See if this stream has the named mailbox selected.
3154 
3155    Accepts: mailbox name
3156             candidate stream
3157    Returns: stream if it can be used, else NIL
3158   ----*/
3159 MAILSTREAM *
3160 same_stream_and_mailbox(char *name, MAILSTREAM *stream)
3161 {
3162     NETMBX mb_s, mb_n;
3163 
3164     if(same_stream(name, stream)
3165        && mail_valid_net_parse(stream->mailbox, &mb_s)
3166        && mail_valid_net_parse(name, &mb_n)
3167        && (mb_n.mailbox && mb_s.mailbox
3168        &&  (!strcmp(mb_n.mailbox,mb_s.mailbox)  /* case depend except INBOX */
3169             || (!strucmp(mb_n.mailbox,"INBOX")
3170 	        && !strucmp(mb_s.mailbox,"INBOX"))))){
3171 	dprint((7,
3172 	       "same_stream_and_mailbox: name->%s == stream->%s: yes\n",
3173 	       name ? name : "?",
3174 	       (stream && stream->mailbox) ? stream->mailbox : "NULL"));
3175 	return(stream);
3176     }
3177 
3178     dprint((7,
3179 	   "same_stream_and_mailbox: name->%s == stream->%s: no dice\n",
3180 	   name ? name : "?",
3181 	   (stream && stream->mailbox) ? stream->mailbox : "NULL"));
3182     return(NULL);
3183 }
3184 
3185 /*
3186  * Args -- name1 and name2 are remote mailbox names.
3187  *
3188  * Returns --  True  if names refer to same mailbox accessed in same way
3189  *             False if not
3190  *
3191  * This has some very similar code to same_stream_and_mailbox but we're not
3192  * quite ready to discard the differences.
3193  * The treatment of the port and the user is not quite the same.
3194  */
3195 int
3196 same_remote_mailboxes(char *name1, char *name2)
3197 {
3198     NETMBX mb1, mb2;
3199     char *cn1;
3200 
3201     /*
3202      * Probably we should allow !port equal to default port, but we don't
3203      * know how to get the default port. To match what c-client does we
3204      * allow !port to be equal to anything.
3205      */
3206     return(name1 && IS_REMOTE(name1)
3207 	   && name2 && IS_REMOTE(name2)
3208 	   && mail_valid_net_parse(name1, &mb1)
3209 	   && mail_valid_net_parse(name2, &mb2)
3210 	   && !strucmp(mb1.service, mb2.service)
3211 	   && (!strucmp(mb1.host, mb2.host)	/* just to save DNS lookups */
3212 	       || !strucmp(cn1=canonical_name(mb1.host), mb2.host)
3213 	       || !strucmp(cn1, canonical_name(mb2.host)))
3214 	   && (!mb1.port || !mb2.port || mb1.port == mb2.port)
3215 	   && mb1.anoflag == mb2.anoflag
3216 	   && mb1.mailbox && mb2.mailbox
3217 	   && (!strcmp(mb1.mailbox, mb2.mailbox)
3218 	       || (!strucmp(mb1.mailbox,"INBOX")
3219 		   && !strucmp(mb2.mailbox,"INBOX")))
3220 	   && ((mb1.user && *mb1.user && mb2.user && *mb2.user
3221 		&& !strcmp(mb1.user, mb2.user))
3222 	       ||
3223 	       (!(mb1.user && *mb1.user) && !(mb2.user && *mb2.user))
3224 	       ||
3225 	       (!(mb1.user && *mb1.user)
3226 		&& ((ps_global->VAR_USER_ID
3227 		     && !strcmp(ps_global->VAR_USER_ID, mb2.user))
3228 		    ||
3229 		    (!ps_global->VAR_USER_ID
3230 		     && ps_global->ui.login[0]
3231 		     && !strcmp(ps_global->ui.login, mb2.user))))
3232 	       ||
3233 	       (!(mb2.user && *mb2.user)
3234 		&& ((ps_global->VAR_USER_ID
3235 		     && !strcmp(ps_global->VAR_USER_ID, mb1.user))
3236 		    ||
3237 		    (!ps_global->VAR_USER_ID
3238 		     && ps_global->ui.login[0]
3239 		     && !strcmp(ps_global->ui.login, mb1.user))))));
3240 }
3241 
3242 
3243 int
3244 is_imap_stream(MAILSTREAM *stream)
3245 {
3246     return(stream && stream->dtb && stream->dtb->name
3247            && !strcmp(stream->dtb->name, "imap"));
3248 }
3249 
3250 
3251 int
3252 modern_imap_stream(MAILSTREAM *stream)
3253 {
3254     return(is_imap_stream(stream) && LEVELIMAP4rev1(stream));
3255 }
3256 
3257 
3258 /*----------------------------------------------------------------------
3259      Check and see if all the stream are alive
3260 
3261 Returns:  0 if there was no change
3262          >0 if streams have died since last call
3263 
3264 Also outputs a message that the streams have died
3265  ----*/
3266 int
3267 streams_died(void)
3268 {
3269     int rv = 0;
3270     int         i;
3271     MAILSTREAM *m;
3272     unsigned char *folder;
3273 
3274     for(i = 0; i < ps_global->s_pool.nstream; i++){
3275 	m = ps_global->s_pool.streams[i];
3276 	if(m && sp_dead_stream(m)){
3277 	    if(sp_flagged(m, SP_LOCKED) && sp_flagged(m, SP_USERFLDR)){
3278 		if(!sp_noticed_dead_stream(m)){
3279 		    rv++;
3280 		    sp_set_noticed_dead_stream(m, 1);
3281 		    folder = folder_name_decoded((unsigned char *)STREAMNAME(m));
3282 		    q_status_message1(SM_ORDER | SM_DING, 3, 3,
3283 			  _("MAIL FOLDER \"%s\" CLOSED DUE TO ACCESS ERROR"),
3284 			  short_str(pretty_fn((char *) folder) ? pretty_fn((char *) folder) : "?",
3285 				    tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots));
3286 		    dprint((6, "streams_died: locked: \"%s\"\n",
3287 			    folder));
3288 		    if(rv == 1){
3289 			snprintf(tmp_20k_buf, SIZEOF_20KBUF, _("Folder \"%s\" is Closed"),
3290 			  short_str(pretty_fn((char *)folder) ? pretty_fn((char *)folder) : "?",
3291 			  tmp_20k_buf+1000, SIZEOF_20KBUF-1000, 35, FrontDots));
3292 			if(pith_opt_icon_text)
3293 			  (*pith_opt_icon_text)(tmp_20k_buf, IT_MCLOSED);
3294 		    }
3295 		    if(folder) fs_give((void **)&folder);
3296 		}
3297 	    }
3298 	    else{
3299 		if(!sp_noticed_dead_stream(m)){
3300 		    sp_set_noticed_dead_stream(m, 1);
3301 		    folder = (unsigned char *) STREAMNAME(m);
3302 		    /*
3303 		     * If a cached stream died and then we tried to use it
3304 		     * it could cause problems. We could warn about it here
3305 		     * but it may be confusing because it might be
3306 		     * unrelated to what the user is doing and not cause
3307 		     * any problem at all.
3308 		     */
3309 #if 0
3310 		    if(sp_flagged(m, SP_USEPOOL))
3311 		      q_status_message(SM_ORDER, 3, 3,
3312 	"Warning: Possible problem accessing remote data, connection died.");
3313 #endif
3314 
3315 		    dprint((6, "streams_died: not locked: \"%s\"\n",
3316 			    folder));
3317 		}
3318 
3319 		pine_mail_actually_close(m);
3320 	    }
3321 	}
3322     }
3323 
3324     return(rv);
3325 }
3326 
3327 
3328 /* Some stream is locked checks to see if there is any stream for which we
3329  * are in a callback from c-client
3330  */
3331 
3332 int
3333 some_stream_is_locked(void)
3334 {
3335     int rv = 0, i;
3336     MAILSTREAM *m;
3337 
3338     for(i = 0; rv == 0 && i < ps_global->s_pool.nstream; i++){
3339 	m = ps_global->s_pool.streams[i];
3340 	if(m && m->lock)
3341 	  rv++;
3342      }
3343 
3344     return(rv);
3345 }
3346 
3347 /*
3348  * Very simple version of appenduid_cb until we need something
3349  * more complex.
3350  */
3351 
3352 static imapuid_t last_append_uid;
3353 
3354 void
3355 appenduid_cb(char *mailbox,unsigned long uidvalidity, SEARCHSET *set)
3356 {
3357     last_append_uid = set ? set->first : 0L;
3358 }
3359 
3360 
3361 imapuid_t
3362 get_last_append_uid(void)
3363 {
3364     return last_append_uid;
3365 }
3366 
3367 
3368 /*
3369  * mail_cmd_stream - return a stream suitable for mail_lsub,
3370  *		      mail_subscribe, and mail_unsubscribe
3371  *
3372  */
3373 MAILSTREAM *
3374 mail_cmd_stream(CONTEXT_S *context, int *closeit)
3375 {
3376     char	tmp[MAILTMPLEN];
3377 
3378     *closeit = 1;
3379     (void) context_apply(tmp, context, "x", sizeof(tmp));
3380 
3381     return(pine_mail_open(NULL, tmp,
3382 			  OP_HALFOPEN | OP_SILENT | SP_USEPOOL | SP_TEMPUSE,
3383 			  NULL));
3384 }
3385 
3386 
3387 /*
3388  * This is so we can replace the old rfc822_ routines like rfc822_header_line
3389  * with the new version that checks bounds, like rfc822_output_header_line.
3390  * This routine is called when would be a bounds overflow, which we simply log
3391  * and go on with the truncated data.
3392  */
3393 long
3394 dummy_soutr(void *stream, char *string)
3395 {
3396     dprint((2, "dummy_soutr unexpected call, caught overflow\n"));
3397     return LONGT;
3398 }
3399