1 /* ========================================================================
2  * Copyright 1988-2007 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *
11  * ========================================================================
12  */
13 
14 /*
15  * Program:	News routines
16  *
17  * Author:	Mark Crispin
18  *		Networks and Distributed Computing
19  *		Computing & Communications
20  *		University of Washington
21  *		Administration Building, AG-44
22  *		Seattle, WA  98195
23  *		Internet: MRC@CAC.Washington.EDU
24  *
25  * Date:	4 September 1991
26  * Last Edited:	30 January 2007
27  */
28 
29 
30 #include <stdio.h>
31 #include <ctype.h>
32 #include <errno.h>
33 extern int errno;		/* just in case */
34 #include "mail.h"
35 #include "osdep.h"
36 #include <sys/stat.h>
37 #include <sys/time.h>
38 #include "misc.h"
39 #include "newsrc.h"
40 #include "fdstring.h"
41 
42 
43 /* news_load_message() flags */
44 
45 #define NLM_HEADER 0x1		/* load message text */
46 #define NLM_TEXT 0x2		/* load message text */
47 
48 /* NEWS I/O stream local data */
49 
50 typedef struct news_local {
51   unsigned int dirty : 1;	/* disk copy of .newsrc needs updating */
52   char *dir;			/* spool directory name */
53   char *name;			/* local mailbox name */
54   unsigned char buf[CHUNKSIZE];	/* scratch buffer */
55   unsigned long cachedtexts;	/* total size of all cached texts */
56 } NEWSLOCAL;
57 
58 
59 /* Convenient access to local data */
60 
61 #define LOCAL ((NEWSLOCAL *) stream->local)
62 
63 
64 /* Function prototypes */
65 
66 DRIVER *news_valid (char *name);
67 DRIVER *news_isvalid (char *name,char *mbx);
68 void *news_parameters (long function,void *value);
69 void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents);
70 void news_list (MAILSTREAM *stream,char *ref,char *pat);
71 void news_lsub (MAILSTREAM *stream,char *ref,char *pat);
72 long news_canonicalize (char *ref,char *pat,char *pattern);
73 long news_subscribe (MAILSTREAM *stream,char *mailbox);
74 long news_unsubscribe (MAILSTREAM *stream,char *mailbox);
75 long news_create (MAILSTREAM *stream,char *mailbox);
76 long news_delete (MAILSTREAM *stream,char *mailbox);
77 long news_rename (MAILSTREAM *stream,char *old,char *newname);
78 MAILSTREAM *news_open (MAILSTREAM *stream);
79 int news_select (struct direct *name);
80 int news_numsort (const void *d1,const void *d2);
81 void news_close (MAILSTREAM *stream,long options);
82 void news_fast (MAILSTREAM *stream,char *sequence,long flags);
83 void news_flags (MAILSTREAM *stream,char *sequence,long flags);
84 void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags);
85 char *news_header (MAILSTREAM *stream,unsigned long msgno,
86 		   unsigned long *length,long flags);
87 long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags);
88 void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt);
89 long news_ping (MAILSTREAM *stream);
90 void news_check (MAILSTREAM *stream);
91 long news_expunge (MAILSTREAM *stream,char *sequence,long options);
92 long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options);
93 long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data);
94 
95 /* News routines */
96 
97 
98 /* Driver dispatch used by MAIL */
99 
100 DRIVER newsdriver = {
101   "news",			/* driver name */
102 				/* driver flags */
103   DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE|DR_NONEWMAIL|DR_DIRFMT,
104   (DRIVER *) NIL,		/* next driver */
105   news_valid,			/* mailbox is valid for us */
106   news_parameters,		/* manipulate parameters */
107   news_scan,			/* scan mailboxes */
108   news_list,			/* find mailboxes */
109   news_lsub,			/* find subscribed mailboxes */
110   news_subscribe,		/* subscribe to mailbox */
111   news_unsubscribe,		/* unsubscribe from mailbox */
112   news_create,			/* create mailbox */
113   news_delete,			/* delete mailbox */
114   news_rename,			/* rename mailbox */
115   mail_status_default,		/* status of mailbox */
116   news_open,			/* open mailbox */
117   news_close,			/* close mailbox */
118   news_fast,			/* fetch message "fast" attributes */
119   news_flags,			/* fetch message flags */
120   NIL,				/* fetch overview */
121   NIL,				/* fetch message envelopes */
122   news_header,			/* fetch message header */
123   news_text,			/* fetch message body */
124   NIL,				/* fetch partial message text */
125   NIL,				/* unique identifier */
126   NIL,				/* message number */
127   NIL,				/* modify flags */
128   news_flagmsg,			/* per-message modify flags */
129   NIL,				/* search for message based on criteria */
130   NIL,				/* sort messages */
131   NIL,				/* thread messages */
132   news_ping,			/* ping mailbox to see if still alive */
133   news_check,			/* check for new messages */
134   news_expunge,			/* expunge deleted messages */
135   news_copy,			/* copy messages to another mailbox */
136   news_append,			/* append string message to mailbox */
137   NIL,				/* garbage collect stream */
138   NIL				/* renew stream */
139 };
140 
141 				/* prototype stream */
142 MAILSTREAM newsproto = {&newsdriver};
143 
144 /* News validate mailbox
145  * Accepts: mailbox name
146  * Returns: our driver if name is valid, NIL otherwise
147  */
148 
news_valid(char * name)149 DRIVER *news_valid (char *name)
150 {
151   int fd;
152   char *s,*t,*u;
153   struct stat sbuf;
154   if ((name[0] == '#') && (name[1] == 'n') && (name[2] == 'e') &&
155       (name[3] == 'w') && (name[4] == 's') && (name[5] == '.') &&
156       !strchr (name,'/') &&
157       !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
158       ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),O_RDONLY,
159 		   NIL)) >= 0)) {
160     fstat (fd,&sbuf);		/* get size of active file */
161 				/* slurp in active file */
162     read (fd,t = s = (char *) fs_get (sbuf.st_size+1),sbuf.st_size);
163     s[sbuf.st_size] = '\0';	/* tie off file */
164     close (fd);			/* flush file */
165     while (*t && (u = strchr (t,' '))) {
166       *u++ = '\0';		/* tie off at end of name */
167       if (!strcmp (name+6,t)) {
168 	fs_give ((void **) &s);	/* flush data */
169 	return &newsdriver;
170       }
171       t = 1 + strchr (u,'\n');	/* next line */
172     }
173     fs_give ((void **) &s);	/* flush data */
174   }
175   return NIL;			/* return status */
176 }
177 
178 /* News manipulate driver parameters
179  * Accepts: function code
180  *	    function-dependent value
181  * Returns: function-dependent return value
182  */
183 
news_parameters(long function,void * value)184 void *news_parameters (long function,void *value)
185 {
186   return (function == GET_NEWSRC) ? env_parameters (function,value) : NIL;
187 }
188 
189 
190 /* News scan mailboxes
191  * Accepts: mail stream
192  *	    reference
193  *	    pattern to search
194  *	    string to scan
195  */
196 
news_scan(MAILSTREAM * stream,char * ref,char * pat,char * contents)197 void news_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
198 {
199   char tmp[MAILTMPLEN];
200   if (news_canonicalize (ref,pat,tmp))
201     mm_log ("Scan not valid for news mailboxes",ERROR);
202 }
203 
204 /* News find list of newsgroups
205  * Accepts: mail stream
206  *	    reference
207  *	    pattern to search
208  */
209 
news_list(MAILSTREAM * stream,char * ref,char * pat)210 void news_list (MAILSTREAM *stream,char *ref,char *pat)
211 {
212   int fd;
213   int i;
214   char *s,*t,*u,*r,pattern[MAILTMPLEN],name[MAILTMPLEN];
215   struct stat sbuf;
216   if (!pat || !*pat) {		/* empty pattern? */
217     if (news_canonicalize (ref,"*",pattern)) {
218 				/* tie off name at root */
219       if ((s = strchr (pattern,'.')) != NULL) *++s = '\0';
220       else pattern[0] = '\0';
221       mm_list (stream,'.',pattern,LATT_NOSELECT);
222     }
223   }
224   else if (news_canonicalize (ref,pat,pattern) &&
225 	   !stat ((char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),&sbuf) &&
226 	   ((fd = open ((char *) mail_parameters (NIL,GET_NEWSACTIVE,NIL),
227 			O_RDONLY,NIL)) >= 0)) {
228     fstat (fd,&sbuf);		/* get file size and read data */
229     read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
230     close (fd);			/* close file */
231     s[sbuf.st_size] = '\0';	/* tie off string */
232     strcpy (name,"#news.");	/* write initial prefix */
233     i = strlen (pattern);	/* length of pattern */
234     if (pattern[--i] != '%') i = 0;
235     if ((t = strtok_r (s,"\n",&r)) != NULL) do if ((u = strchr (t,' ')) != NULL) {
236       *u = '\0';		/* tie off at end of name */
237       strcpy (name + 6,t);	/* make full form of name */
238       if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
239       else if (i && (u = strchr (name + i,'.'))) {
240 	*u = '\0';		/* tie off at delimiter, see if matches */
241 	if (pmatch_full (name,pattern,'.'))
242 	  mm_list (stream,'.',name,LATT_NOSELECT);
243       }
244     } while ((t = strtok_r (NIL,"\n",&r)) != NULL);
245     fs_give ((void **) &s);
246   }
247 }
248 
249 /* News find list of subscribed newsgroups
250  * Accepts: mail stream
251  *	    reference
252  *	    pattern to search
253  */
254 
news_lsub(MAILSTREAM * stream,char * ref,char * pat)255 void news_lsub (MAILSTREAM *stream,char *ref,char *pat)
256 {
257   char pattern[MAILTMPLEN];
258 				/* return data from newsrc */
259   if (news_canonicalize (ref,pat,pattern)) newsrc_lsub (stream,pattern);
260 }
261 
262 
263 /* News canonicalize newsgroup name
264  * Accepts: reference
265  *	    pattern
266  *	    returned single pattern
267  * Returns: T on success, NIL on failure
268  */
269 
news_canonicalize(char * ref,char * pat,char * pattern)270 long news_canonicalize (char *ref,char *pat,char *pattern)
271 {
272   unsigned long i;
273   char *s;
274   if (ref && *ref) {		/* have a reference */
275     strcpy (pattern,ref);	/* copy reference to pattern */
276 				/* # overrides mailbox field in reference */
277     if (*pat == '#') strcpy (pattern,pat);
278 				/* pattern starts, reference ends, with . */
279     else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
280       strcat (pattern,pat + 1);	/* append, omitting one of the period */
281     else strcat (pattern,pat);	/* anything else is just appended */
282   }
283   else strcpy (pattern,pat);	/* just have basic name */
284   if ((pattern[0] == '#') && (pattern[1] == 'n') && (pattern[2] == 'e') &&
285       (pattern[3] == 'w') && (pattern[4] == 's') && (pattern[5] == '.') &&
286       !strchr (pattern,'/')) {	/* count wildcards */
287     for (i = 0, s = pattern; *s; s++) if ((*s == '*') || (*s == '%')) ++i;
288 				/* success if not too many */
289     if (i <= MAXWILDCARDS) return LONGT;
290     MM_LOG ("Excessive wildcards in LIST/LSUB",ERROR);
291   }
292   return NIL;
293 }
294 
295 /* News subscribe to mailbox
296  * Accepts: mail stream
297  *	    mailbox to add to subscription list
298  * Returns: T on success, NIL on failure
299  */
300 
news_subscribe(MAILSTREAM * stream,char * mailbox)301 long news_subscribe (MAILSTREAM *stream,char *mailbox)
302 {
303   return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,':') : NIL;
304 }
305 
306 
307 /* NEWS unsubscribe to mailbox
308  * Accepts: mail stream
309  *	    mailbox to delete from subscription list
310  * Returns: T on success, NIL on failure
311  */
312 
news_unsubscribe(MAILSTREAM * stream,char * mailbox)313 long news_unsubscribe (MAILSTREAM *stream,char *mailbox)
314 {
315   return news_valid (mailbox) ? newsrc_update (stream,mailbox+6,'!') : NIL;
316 }
317 
318 /* News create mailbox
319  * Accepts: mail stream
320  *	    mailbox name to create
321  * Returns: T on success, NIL on failure
322  */
323 
news_create(MAILSTREAM * stream,char * mailbox)324 long news_create (MAILSTREAM *stream,char *mailbox)
325 {
326   return NIL;			/* never valid for News */
327 }
328 
329 
330 /* News delete mailbox
331  *	    mailbox name to delete
332  * Returns: T on success, NIL on failure
333  */
334 
news_delete(MAILSTREAM * stream,char * mailbox)335 long news_delete (MAILSTREAM *stream,char *mailbox)
336 {
337   return NIL;			/* never valid for News */
338 }
339 
340 
341 /* News rename mailbox
342  * Accepts: mail stream
343  *	    old mailbox name
344  *	    new mailbox name
345  * Returns: T on success, NIL on failure
346  */
347 
news_rename(MAILSTREAM * stream,char * old,char * newname)348 long news_rename (MAILSTREAM *stream,char *old,char *newname)
349 {
350   return NIL;			/* never valid for News */
351 }
352 
353 /* News open
354  * Accepts: stream to open
355  * Returns: stream on success, NIL on failure
356  */
357 
news_open(MAILSTREAM * stream)358 MAILSTREAM *news_open (MAILSTREAM *stream)
359 {
360   long i,nmsgs;
361   char *s,tmp[MAILTMPLEN];
362   struct direct **names = NIL;
363   				/* return prototype for OP_PROTOTYPE call */
364   if (!stream) return &newsproto;
365   if (stream->local) fatal ("news recycle stream");
366 				/* build directory name */
367   sprintf (s = tmp,"%s/%s",(char *) mail_parameters (NIL,GET_NEWSSPOOL,NIL),
368 	   stream->mailbox + 6);
369   while ((s = strchr (s,'.')) != NULL) *s = '/';
370 				/* scan directory */
371   if ((nmsgs = scandir (tmp,&names,news_select,news_numsort)) >= 0) {
372     mail_exists (stream,nmsgs);	/* notify upper level that messages exist */
373     stream->local = fs_get (sizeof (NEWSLOCAL));
374     LOCAL->dirty = NIL;		/* no update to .newsrc needed yet */
375     LOCAL->dir = cpystr (tmp);	/* copy directory name for later */
376     LOCAL->name = cpystr (stream->mailbox + 6);
377     for (i = 0; i < nmsgs; ++i) {
378       stream->uid_last = mail_elt (stream,i+1)->private.uid =
379 	atoi (names[i]->d_name);
380       fs_give ((void **) &names[i]);
381     }
382     s = (void *) names;		/* stupid language */
383     fs_give ((void **) &s);	/* free directory */
384     LOCAL->cachedtexts = 0;	/* no cached texts */
385     stream->sequence++;		/* bump sequence number */
386     stream->rdonly = stream->perm_deleted = T;
387 				/* UIDs are always valid */
388     stream->uid_validity = 0xbeefface;
389 				/* read .newsrc entries */
390     mail_recent (stream,newsrc_read (LOCAL->name,stream));
391 				/* notify if empty newsgroup */
392     if (!(stream->nmsgs || stream->silent)) {
393       sprintf (tmp,"Newsgroup %s is empty",LOCAL->name);
394       mm_log (tmp,WARN);
395     }
396   }
397   else mm_log ("Unable to scan newsgroup spool directory",ERROR);
398   return LOCAL ? stream : NIL;	/* if stream is alive, return to caller */
399 }
400 
401 /* News file name selection test
402  * Accepts: candidate directory entry
403  * Returns: T to use file name, NIL to skip it
404  */
405 
news_select(struct direct * name)406 int news_select (struct direct *name)
407 {
408   char c;
409   char *s = name->d_name;
410   while ((c = *s++) != '\0') if (!isdigit (c)) return NIL;
411   return T;
412 }
413 
414 
415 /* News file name comparison
416  * Accepts: first candidate directory entry
417  *	    second candidate directory entry
418  * Returns: negative if d1 < d2, 0 if d1 == d2, positive if d1 > d2
419  */
420 
news_numsort(const void * d1,const void * d2)421 int news_numsort (const void *d1,const void *d2)
422 {
423   return atoi ((*(struct direct **) d1)->d_name) -
424     atoi ((*(struct direct **) d2)->d_name);
425 }
426 
427 
428 /* News close
429  * Accepts: MAIL stream
430  *	    option flags
431  */
432 
news_close(MAILSTREAM * stream,long options)433 void news_close (MAILSTREAM *stream,long options)
434 {
435   if (LOCAL) {			/* only if a file is open */
436     news_check (stream);	/* dump final checkpoint */
437     if (LOCAL->dir) fs_give ((void **) &LOCAL->dir);
438     if (LOCAL->name) fs_give ((void **) &LOCAL->name);
439 				/* nuke the local data */
440     fs_give ((void **) &stream->local);
441     stream->dtb = NIL;		/* log out the DTB */
442   }
443 }
444 
445 /* News fetch fast information
446  * Accepts: MAIL stream
447  *	    sequence
448  *	    option flags
449  */
450 
news_fast(MAILSTREAM * stream,char * sequence,long flags)451 void news_fast (MAILSTREAM *stream,char *sequence,long flags)
452 {
453   MESSAGECACHE *elt;
454   unsigned long i;
455 				/* set up metadata for all messages */
456   if (stream && LOCAL && ((flags & FT_UID) ?
457 			  mail_uid_sequence (stream,sequence) :
458 			  mail_sequence (stream,sequence)))
459     for (i = 1; i <= stream->nmsgs; i++)
460       if ((elt = mail_elt (stream,i))->sequence &&
461 	  !(elt->day && elt->rfc822_size)) news_load_message (stream,i,NIL);
462 }
463 
464 
465 /* News fetch flags
466  * Accepts: MAIL stream
467  *	    sequence
468  *	    option flags
469  */
470 
news_flags(MAILSTREAM * stream,char * sequence,long flags)471 void news_flags (MAILSTREAM *stream,char *sequence,long flags)
472 {
473   unsigned long i;
474   if ((flags & FT_UID) ?	/* validate all elts */
475       mail_uid_sequence (stream,sequence) : mail_sequence (stream,sequence))
476     for (i = 1; i <= stream->nmsgs; i++) mail_elt (stream,i)->valid = T;
477 }
478 
479 /* News load message into cache
480  * Accepts: MAIL stream
481  *	    message #
482  *	    option flags
483  */
484 
news_load_message(MAILSTREAM * stream,unsigned long msgno,long flags)485 void news_load_message (MAILSTREAM *stream,unsigned long msgno,long flags)
486 {
487   unsigned long i,j,nlseen;
488   int fd;
489   unsigned char c,*t;
490   struct stat sbuf;
491   MESSAGECACHE *elt;
492   FDDATA d;
493   STRING bs;
494   elt = mail_elt (stream,msgno);/* get elt */
495 				/* build message file name */
496   sprintf (LOCAL->buf,"%s/%lu",LOCAL->dir,elt->private.uid);
497 				/* anything we need not currently cached? */
498   if ((!elt->day || !elt->rfc822_size ||
499        ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
500        ((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) &&
501       ((fd = open (LOCAL->buf,O_RDONLY,NIL)) >= 0)) {
502     fstat (fd,&sbuf);		/* get file metadata */
503     d.fd = fd;			/* set up file descriptor */
504     d.pos = 0;			/* start of file */
505     d.chunk = LOCAL->buf;
506     d.chunksize = CHUNKSIZE;
507     INIT (&bs,fd_string,&d,sbuf.st_size);
508     if (!elt->day) {		/* set internaldate to file date */
509       struct tm *tm = gmtime (&sbuf.st_mtime);
510       elt->day = tm->tm_mday; elt->month = tm->tm_mon + 1;
511       elt->year = tm->tm_year + 1900 - BASEYEAR;
512       elt->hours = tm->tm_hour; elt->minutes = tm->tm_min;
513       elt->seconds = tm->tm_sec;
514       elt->zhours = 0; elt->zminutes = 0;
515     }
516 
517     if (!elt->rfc822_size) {	/* know message size yet? */
518       for (i = 0, j = SIZE (&bs), nlseen = 0; j--; ) switch (SNX (&bs)) {
519       case '\015':		/* unlikely carriage return */
520 	if (!j || (CHR (&bs) != '\012')) {
521 	  i++;			/* ugh, raw CR */
522 	  nlseen = NIL;
523 	  break;
524 	}
525 	SNX (&bs);		/* eat the line feed, drop in */
526       case '\012':		/* line feed? */
527 	i += 2;			/* count a CRLF */
528 				/* header size known yet? */
529 	if (!elt->private.msg.header.text.size && nlseen) {
530 				/* note position in file */
531 	  elt->private.special.text.size = GETPOS (&bs);
532 				/* and CRLF-adjusted size */
533 	  elt->private.msg.header.text.size = i;
534 	}
535 	nlseen = T;		/* note newline seen */
536 	break;
537       default:			/* ordinary character */
538 	i++;
539 	nlseen = NIL;
540 	break;
541       }
542       SETPOS (&bs,0);		/* restore old position */
543       elt->rfc822_size = i;	/* note that we have size now */
544 				/* header is entire message if no delimiter */
545       if (!elt->private.msg.header.text.size)
546 	elt->private.msg.header.text.size = elt->rfc822_size;
547 				/* text is remainder of message */
548       elt->private.msg.text.text.size =
549 	elt->rfc822_size - elt->private.msg.header.text.size;
550     }
551 
552 				/* need to load cache with message data? */
553     if (((flags & NLM_HEADER) && !elt->private.msg.header.text.data) ||
554 	((flags & NLM_TEXT) && !elt->private.msg.text.text.data)) {
555 				/* purge cache if too big */
556       if (LOCAL->cachedtexts > max (stream->nmsgs * 4096,2097152)) {
557 				/* just can't keep that much */
558 	mail_gc (stream,GC_TEXTS);
559 	LOCAL->cachedtexts = 0;
560       }
561       if ((flags & NLM_HEADER) && !elt->private.msg.header.text.data) {
562 	t = elt->private.msg.header.text.data =
563 	  (unsigned char *) fs_get (elt->private.msg.header.text.size + 1);
564 	LOCAL->cachedtexts += elt->private.msg.header.text.size;
565 				/* read in message header */
566 	for (i = 0; i <= elt->private.msg.header.text.size; i++)
567 	  switch (c = SNX (&bs)) {
568 	  case '\015':		/* unlikely carriage return */
569 	    *t++ = c;
570 	    if (CHR (&bs) == '\012') *t++ = SNX (&bs);
571 	    break;
572 	  case '\012':		/* line feed? */
573 	    *t++ = '\015';
574 	  default:
575 	    *t++ = c;
576 	    break;
577 	  }
578 	*t = '\0';		/* tie off string */
579       }
580       if ((flags & NLM_TEXT) && !elt->private.msg.text.text.data) {
581 	t = elt->private.msg.text.text.data =
582 	  (unsigned char *) fs_get (elt->private.msg.text.text.size + 1);
583 	SETPOS (&bs,elt->private.msg.header.text.size);
584 	LOCAL->cachedtexts += elt->private.msg.text.text.size;
585 				/* read in message text */
586 	for (i = 0; i <= elt->private.msg.text.text.size; i++)
587 	  switch (c = SNX (&bs)) {
588 	  case '\015':		/* unlikely carriage return */
589 	    *t++ = c;
590 	    if (CHR (&bs) == '\012') *t++ = SNX (&bs);
591 	    break;
592 	  case '\012':		/* line feed? */
593 	    *t++ = '\015';
594 	  default:
595 	    *t++ = c;
596 	    break;
597 	  }
598 	*t = '\0';		/* tie off string */
599       }
600     }
601     close (fd);			/* flush message file */
602   }
603 }
604 
605 /* News fetch message header
606  * Accepts: MAIL stream
607  *	    message # to fetch
608  *	    pointer to returned header text length
609  *	    option flags
610  * Returns: message header in RFC822 format
611  */
612 
news_header(MAILSTREAM * stream,unsigned long msgno,unsigned long * length,long flags)613 char *news_header (MAILSTREAM *stream,unsigned long msgno,
614 		   unsigned long *length,long flags)
615 {
616   MESSAGECACHE *elt;
617   *length = 0;			/* default to empty */
618   if (flags & FT_UID) return "";/* UID call "impossible" */
619   elt = mail_elt (stream,msgno);/* get elt */
620   if (!elt->private.msg.header.text.data)
621     news_load_message (stream,msgno,NLM_HEADER);
622   *length = elt->private.msg.header.text.size;
623   return (char *) elt->private.msg.header.text.data;
624 }
625 
626 
627 /* News fetch message text (body only)
628  * Accepts: MAIL stream
629  *	    message # to fetch
630  *	    pointer to returned stringstruct
631  *	    option flags
632  * Returns: T on success, NIL on failure
633  */
634 
news_text(MAILSTREAM * stream,unsigned long msgno,STRING * bs,long flags)635 long news_text (MAILSTREAM *stream,unsigned long msgno,STRING *bs,long flags)
636 {
637   MESSAGECACHE *elt;
638 				/* UID call "impossible" */
639   if (flags & FT_UID) return NIL;
640   elt = mail_elt (stream,msgno);/* get elt */
641 				/* snarf message if don't have it yet */
642   if (!elt->private.msg.text.text.data) {
643     news_load_message (stream,msgno,NLM_TEXT);
644     if (!elt->private.msg.text.text.data) return NIL;
645   }
646   if (!(flags & FT_PEEK)) {	/* mark as seen */
647     mail_elt (stream,msgno)->seen = T;
648     mm_flags (stream,msgno);
649   }
650   INIT (bs,mail_string,elt->private.msg.text.text.data,
651 	elt->private.msg.text.text.size);
652   return T;
653 }
654 
655 /* News per-message modify flag
656  * Accepts: MAIL stream
657  *	    message cache element
658  */
659 
news_flagmsg(MAILSTREAM * stream,MESSAGECACHE * elt)660 void news_flagmsg (MAILSTREAM *stream,MESSAGECACHE *elt)
661 {
662   if (!LOCAL->dirty) {		/* only bother checking if not dirty yet */
663     if (elt->valid) {		/* if done, see if deleted changed */
664       if (elt->sequence != elt->deleted) LOCAL->dirty = T;
665       elt->sequence = T;	/* leave the sequence set */
666     }
667 				/* note current setting of deleted flag */
668     else elt->sequence = elt->deleted;
669   }
670 }
671 
672 
673 /* News ping mailbox
674  * Accepts: MAIL stream
675  * Returns: T if stream alive, else NIL
676  */
677 
news_ping(MAILSTREAM * stream)678 long news_ping (MAILSTREAM *stream)
679 {
680   return T;			/* always alive */
681 }
682 
683 
684 /* News check mailbox
685  * Accepts: MAIL stream
686  */
687 
news_check(MAILSTREAM * stream)688 void news_check (MAILSTREAM *stream)
689 {
690 				/* never do if no updates */
691   if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
692   LOCAL->dirty = NIL;
693 }
694 
695 
696 /* News expunge mailbox
697  * Accepts: MAIL stream
698  *	    sequence to expunge if non-NIL
699  *	    expunge options
700  * Returns: T if success, NIL if failure
701  */
702 
news_expunge(MAILSTREAM * stream,char * sequence,long options)703 long news_expunge (MAILSTREAM *stream,char *sequence,long options)
704 {
705   if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
706   return LONGT;
707 }
708 
709 /* News copy message(s)
710  * Accepts: MAIL stream
711  *	    sequence
712  *	    destination mailbox
713  *	    option flags
714  * Returns: T if copy successful, else NIL
715  */
716 
news_copy(MAILSTREAM * stream,char * sequence,char * mailbox,long options)717 long news_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
718 {
719   mailproxycopy_t pc =
720     (mailproxycopy_t) mail_parameters (stream,GET_MAILPROXYCOPY,NIL);
721   if (pc) return (*pc) (stream,sequence,mailbox,options);
722   mm_log ("Copy not valid for News",ERROR);
723   return NIL;
724 }
725 
726 
727 /* News append message from stringstruct
728  * Accepts: MAIL stream
729  *	    destination mailbox
730  *	    append callback function
731  *	    data for callback
732  * Returns: T if append successful, else NIL
733  */
734 
news_append(MAILSTREAM * stream,char * mailbox,append_t af,void * data)735 long news_append (MAILSTREAM *stream,char *mailbox,append_t af,void *data)
736 {
737   mm_log ("Append not valid for news",ERROR);
738   return NIL;
739 }
740