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