/*
* Program: Network News Transfer Protocol (NNTP) routines
*
* Author: Mark Crispin
* Networks and Distributed Computing
* Computing & Communications
* University of Washington
* Administration Building, AG-44
* Seattle, WA 98195
* Internet: MRC@CAC.Washington.EDU
*
* Date: 10 February 1992
* Last Edited: 4 January 1996
*
* Copyright 1996 by the University of Washington
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose and without fee is hereby granted, provided
* that the above copyright notices appear in all copies and that both the
* above copyright notices and this permission notice appear in supporting
* documentation, and that the name of the University of Washington not be
* used in advertising or publicity pertaining to distribution of the software
* without specific, written prior permission. This software is made
* available "as is", and
* THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
* WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
* NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
* (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <ctype.h>
#include <stdio.h>
#include "mail.h"
#include "osdep.h"
#include "nntp.h"
#include "rfc822.h"
#include "misc.h"
#include "newsrc.h"
#include "netmsg.h"
/* Mailer parameters */
long nntp_port = 0; /* default port override */
/* Driver dispatch used by MAIL */
DRIVER nntpdriver = {
"nntp", /* driver name */
/* driver flags */
DR_NEWS|DR_READONLY|DR_NOFAST|DR_NAMESPACE,
(DRIVER *) NIL, /* next driver */
nntp_valid, /* mailbox is valid for us */
nntp_parameters, /* manipulate parameters */
nntp_scan, /* scan mailboxes */
nntp_list, /* find mailboxes */
nntp_lsub, /* find subscribed mailboxes */
nntp_subscribe, /* subscribe to mailbox */
nntp_unsubscribe, /* unsubscribe from mailbox */
nntp_create, /* create mailbox */
nntp_delete, /* delete mailbox */
nntp_rename, /* rename mailbox */
nntp_status, /* status of mailbox */
nntp_mopen, /* open mailbox */
nntp_mclose, /* close mailbox */
nntp_fetchfast, /* fetch message "fast" attributes */
nntp_fetchflags, /* fetch message flags */
nntp_fetchstructure, /* fetch message envelopes */
nntp_fetchheader, /* fetch message header only */
nntp_fetchtext, /* fetch message body only */
nntp_fetchbody, /* fetch message body section */
NIL, /* unique identifier */
nntp_setflag, /* set message flag */
nntp_clearflag, /* clear message flag */
NIL, /* search for message based on criteria */
nntp_ping, /* ping mailbox to see if still alive */
nntp_check, /* check for new messages */
nntp_expunge, /* expunge deleted messages */
nntp_copy, /* copy messages to another mailbox */
nntp_append, /* append string message to mailbox */
nntp_gc /* garbage collect stream */
};
/* prototype stream */
MAILSTREAM nntpproto = {&nntpdriver};
/* NNTP validate mailbox
* Accepts: mailbox name
* Returns: our driver if name is valid, NIL otherwise
*/
DRIVER *nntp_valid (char *name)
{
char tmp[MAILTMPLEN];
return nntp_isvalid (name,tmp);
}
/* NNTP validate mailbox work routine
* Accepts: mailbox name
* buffer for returned mailbox name
* Returns: our driver if name is valid, NIL otherwise
*/
DRIVER *nntp_isvalid (char *name,char *mbx)
{
DRIVER *ret = mail_valid_net (name,&nntpdriver,NIL,mbx);
if (ret && (mbx[0] == '#')) { /* namespace format name? */
if ((mbx[1] == 'n') && (mbx[2] == 'e') && (mbx[3] == 'w') &&
(mbx[4] == 's') && (mbx[5] == '.')) memmove (mbx,mbx+6,strlen(mbx+6) + 1);
else ret = NIL; /* bogus name */
}
return ret;
}
/* News manipulate driver parameters
* Accepts: function code
* function-dependent value
* Returns: function-dependent return value
*/
void *nntp_parameters (long function,void *value)
{
return NIL;
}
/* NNTP mail scan mailboxes for string
* Accepts: mail stream
* reference
* pattern to search
* string to scan
*/
void nntp_scan (MAILSTREAM *stream,char *ref,char *pat,char *contents)
{
char tmp[MAILTMPLEN];
if (nntp_canonicalize (ref,pat,tmp))
mm_log ("Scan not valid for NNTP mailboxes",WARN);
}
/* NNTP list newsgroups
* Accepts: mail stream
* reference
* pattern to search
*/
void nntp_list (MAILSTREAM *stream,char *ref,char *pat)
{
MAILSTREAM *st = stream;
char *s,*t,*lcl,pattern[MAILTMPLEN],name[MAILTMPLEN];
int showuppers = pattern[strlen (pattern) - 1] == '%';
/* ask server for open newsgroups */
if (nntp_canonicalize (ref,pat,pattern) &&
((stream && LOCAL && LOCAL->nntpstream) ||
(stream = mail_open (NIL,pattern,OP_HALFOPEN))) &&
(nntp_send (LOCAL->nntpstream,"LIST","ACTIVE") == NNTPGLIST)) {
/* namespace format name? */
if (*(lcl = strchr (strcpy (name,pattern),'}') + 1) == '#') lcl += 6;
/* process data until we see final dot */
while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) && *s != '.') {
/* tie off after newsgroup name */
if (t = strchr (s,' ')) *t = '\0';
strcpy (lcl,s); /* make full form of name */
/* report if match */
if (pmatch_full (name,pattern,'.')) mm_list (stream,'.',name,NIL);
else while (showuppers && (t = strrchr (lcl,'.'))) {
*t = '\0'; /* tie off the name */
if (pmatch_full (name,pattern,'.'))
mm_list (stream,'.',name,LATT_NOSELECT);
}
fs_give ((void **) &s); /* clean up */
}
if (stream != st) mail_close (stream);
}
}
/* NNTP list subscribed newsgroups
* Accepts: mail stream
* reference
* pattern to search
*/
void nntp_lsub (MAILSTREAM *stream,char *ref,char *pat)
{
void *sdb = NIL;
char *s,pattern[MAILTMPLEN];
/* return data from newsrc */
if (nntp_canonicalize (ref,pat,pattern)) newsrc_lsub (stream,pattern);
/* only if null stream and * requested */
if (!stream && !ref && !strcmp (pat,"*") && (s = sm_read (&sdb)))
do if (nntp_valid (s)) mm_lsub (stream,NIL,s,NIL);
while (s = sm_read (&sdb)); /* until no more subscriptions */
}
/* NNTP canonicalize newsgroup name
* Accepts: reference
* pattern
* returned single pattern
* Returns: T on success, NIL on failure
*/
long nntp_canonicalize (char *ref,char *pat,char *pattern)
{
if (ref && *ref) { /* have a reference */
if (!nntp_valid (ref)) return NIL;
strcpy (pattern,ref); /* copy reference to pattern */
/* # overrides mailbox field in reference */
if (*pat == '#') strcpy (strchr (pattern,'}') + 1,pat);
/* pattern starts, reference ends, with . */
else if ((*pat == '.') && (pattern[strlen (pattern) - 1] == '.'))
strcat (pattern,pat + 1); /* append, omitting one of the period */
else strcat (pattern,pat); /* anything else is just appended */
}
else strcpy (pattern,pat); /* just have basic name */
return nntp_valid (pattern) ? T : NIL;
}
/* NNTP subscribe to mailbox
* Accepts: mail stream
* mailbox to add to subscription list
* Returns: T on success, NIL on failure
*/
long nntp_subscribe (MAILSTREAM *stream,char *mailbox)
{
char mbx[MAILTMPLEN];
return nntp_isvalid (mailbox,mbx) ? newsrc_update (mbx,':') : NIL;
}
/* NNTP unsubscribe to mailbox
* Accepts: mail stream
* mailbox to delete from subscription list
* Returns: T on success, NIL on failure
*/
long nntp_unsubscribe (MAILSTREAM *stream,char *mailbox)
{
char mbx[MAILTMPLEN];
return nntp_isvalid (mailbox,mbx) ? newsrc_update (mbx,'!') : NIL;
}
/* NNTP create mailbox
* Accepts: mail stream
* mailbox name to create
* Returns: T on success, NIL on failure
*/
long nntp_create (MAILSTREAM *stream,char *mailbox)
{
return NIL; /* never valid for NNTP */
}
/* NNTP delete mailbox
* mailbox name to delete
* Returns: T on success, NIL on failure
*/
long nntp_delete (MAILSTREAM *stream,char *mailbox)
{
return NIL; /* never valid for NNTP */
}
/* NNTP rename mailbox
* Accepts: mail stream
* old mailbox name
* new mailbox name
* Returns: T on success, NIL on failure
*/
long nntp_rename (MAILSTREAM *stream,char *old,char *newname)
{
return NIL; /* never valid for NNTP */
}
/* NNTP status
* Accepts: mail stream
* mailbox name
* status flags
* Returns: T on success, NIL on failure
*/
long nntp_status (MAILSTREAM *stream,char *mbx,long flags)
{
MAILSTATUS status;
NETMBX mb;
unsigned long i;
long ret = NIL;
char *s,*state,tmp[MAILTMPLEN];
char *old = stream ? LOCAL->name : NIL;
mail_valid_net_parse(mbx,&mb);/* get host and mailbox names */
if (stream) { /* stream argument given? */
/* can't use it if not the same host */
if (!strcmp (ucase (strcpy (tmp,LOCAL->host)),ucase (mb.host)))
return nntp_status (NIL,mbx,flags);
}
/* no stream argument, make new stream */
else if (!(stream = mail_open (NIL,mbx,OP_HALFOPEN|OP_SILENT))) return NIL;
if (nntp_send (LOCAL->nntpstream,"GROUP",mb.mailbox) == NNTPGOK) {
status.flags = flags; /* status validity flags */
status.messages = strtoul (LOCAL->nntpstream->reply + 4,&s,10);
i = strtoul (s,&s,10);
status.uidnext = strtoul (s,NIL,10) + 1;
/* initially empty */
status.recent = status.unseen = 0;
/* don't bother if empty or don't want this */
if (status.messages && (flags & (SA_RECENT | SA_UNSEEN))) {
if (state = newsrc_state (mb.mailbox)) {
/* in case have to do XHDR Date */
sprintf (tmp,"%ld-%ld",i,status.uidnext - 1);
/* only bother if there appear to be holes */
if ((status.messages < (status.uidnext - i)) &&
((nntp_send (LOCAL->nntpstream,"LISTGROUP",mb.mailbox) == NNTPGOK)
|| (nntp_send (LOCAL->nntpstream,"XHDR Date",tmp) == NNTPHEAD)))
while ((s = tcp_getline (LOCAL->nntpstream->tcpstream)) &&
strcmp (s,".")) {
newsrc_check_uid (state,strtoul (s,NIL,10),&status.recent,
&status.unseen);
fs_give ((void **) &s);
}
/* assume c-client/NNTP map is entire range */
else while (i < status.uidnext)
newsrc_check_uid (state,i++,&status.recent,&status.unseen);
fs_give ((void **) &state);
}
/* no .newsrc state, all messages new */
else status.recent = status.unseen = status.messages;
}
/* UID validity is a constant */
status.uidvalidity = stream->uid_validity;
/* pass status to main program */
mm_status (stream,mbx,&status);
ret = T; /* succes */
}
if (!old) mail_close (stream);/* flush stream if no old mailbox */
/* else reopen old newsgroup */
else if (nntp_send (LOCAL->nntpstream,"GROUP",old) != NNTPGOK) {
mm_log (LOCAL->nntpstream->reply,ERROR);
nntp_close (LOCAL->nntpstream);
LOCAL->nntpstream = NIL;
}
return ret; /* success */
}
/* NNTP open
* Accepts: stream to open
* Returns: stream on success, NIL on failure
*/
MAILSTREAM *nntp_mopen (MAILSTREAM *stream)
{
unsigned long i,j,k;
unsigned long nmsgs = 0;
char *s,*mbx,tmp[MAILTMPLEN];
NETMBX mb;
SENDSTREAM *nstream = NIL;
/* return prototype for OP_PROTOTYPE call */
if (!stream) return &nntpproto;
if (!(mail_valid_net_parse (stream->mailbox,&mb) &&
(stream->halfopen || /* insist upon valid name */
(mail_valid_net_parse (stream->mailbox,&mb) && *mb.mailbox &&
((mb.mailbox[0] != '#') ||
((mb.mailbox[1] == 'n') && (mb.mailbox[2] == 'e') &&
(mb.mailbox[3] == 'w') && (mb.mailbox[4] == 's') &&
(mb.mailbox[5] == '.'))))))) {
sprintf (tmp,"Invalid NNTP name %s",stream->mailbox);
mm_log (tmp,ERROR);
return NIL;
}
/* note mailbox anme */
mbx = (*mb.mailbox == '#') ? mb.mailbox+6 : mb.mailbox;
if (LOCAL) { /* if recycle stream, see if changing hosts */
if (strcmp (lcase (mb.host),lcase (strcpy (tmp,LOCAL->host)))) {
sprintf (tmp,"Closing connection to %s",LOCAL->host);
if (!stream->silent) mm_log (tmp,(long) NIL);
}
else { /* same host, preserve NNTP connection */
sprintf (tmp,"Reusing connection to %s",LOCAL->host);
if (!stream->silent) mm_log (tmp,(long) NIL);
nstream = LOCAL->nntpstream;
LOCAL->nntpstream = NIL; /* keep nntp_mclose() from punting it */
}
nntp_mclose (stream,NIL); /* do close action */
stream->dtb = &nntpdriver; /* reattach this driver */
mail_free_cache (stream); /* clean up cache */
}
/* in case /debug switch given */
if (mb.dbgflag) stream->debug = T;
if (!nstream) { /* open NNTP now if not already open */
char *hostlist[2];
hostlist[0] = strcpy (tmp,mb.host);
if (mb.port) sprintf (tmp + strlen (tmp),":%ld",mb.port);
hostlist[1] = NIL;
nstream = nntp_open (hostlist,OP_READONLY+(stream->debug ? OP_DEBUG:NIL));
}
if (nstream) { /* now try to open newsgroup */
if ((!stream->halfopen) && /* open the newsgroup if not halfopen */
(nntp_send (nstream,"GROUP",mbx) != NNTPGOK)) {
mm_log (nstream->reply,ERROR);
nntp_close (nstream); /* punt stream */
nstream = NIL;
return NIL;
}
k = strtoul (nstream->reply + 4,&s,10);
i = strtoul (s,&s,10);
stream->uid_last = j = strtoul (s,&s,10);
nmsgs = (i | j) ? 1 + j - i : 0;
/* newsgroup open, instantiate local data */
stream->local = fs_get (sizeof (NNTPLOCAL));
LOCAL->nntpstream = nstream;
LOCAL->dirty = NIL; /* no update to .newsrc needed yet */
/* copy host and newsgroup name */
LOCAL->host = cpystr (mb.host);
LOCAL->name = cpystr (mbx);
LOCAL->hdn = LOCAL->txn = 0;/* no message numbers or texts */
LOCAL->hdr = NIL;
LOCAL->txt = NIL;
stream->sequence++; /* bump sequence number */
stream->rdonly = stream->perm_deleted = T;
/* UIDs are always valid */
stream->uid_validity = 0xbeefface;
sprintf (tmp,"{%s:%lu/nntp}%s%s",tcp_host (nstream->tcpstream),
tcp_port (nstream->tcpstream),*mbx == '#' ? "" : "#news.",mbx);
fs_give ((void **) &stream->mailbox);
stream->mailbox = cpystr (tmp);
if (!stream->halfopen) { /* if not half-open */
if (nmsgs) { /* if any messages exist */
/* in case have to do XHDR Date */
sprintf (tmp,"%ld-%ld",i,j);
if ((k < nmsgs) && /* only bother if there appear to be holes */
((nntp_send (nstream,"LISTGROUP",mbx) == NNTPGOK) ||
(nntp_send (nstream,"XHDR Date",tmp) == NNTPHEAD))) {
nmsgs = 0; /* have holes, calculate true count */
while ((s = tcp_getline (nstream->tcpstream)) && strcmp (s,".")) {
mail_elt (stream,++nmsgs)->uid = atol (s);
fs_give ((void **) &s);
}
}
/* assume c-client/NNTP map is entire range */
else for (k = 1; k <= nmsgs; k++) mail_elt (stream,k)->uid = i++;
}
mail_exists(stream,nmsgs);/* notify upper level that messages exist */
/* read .newsrc entries */
mail_recent (stream,newsrc_read (LOCAL->name,stream));
/* notify if empty newsgroup */
if (!(stream->nmsgs || stream->silent))mm_log("Newsgroup is empty",WARN);
}
}
return LOCAL ? stream : NIL; /* if stream is alive, return to caller */
}
/* NNTP close
* Accepts: MAIL stream
* option flags
*/
void nntp_mclose (MAILSTREAM *stream,long options)
{
if (LOCAL) { /* only if a file is open */
nntp_check (stream); /* dump final checkpoint */
if (LOCAL->name) fs_give ((void **) &LOCAL->name);
if (LOCAL->host) fs_give ((void **) &LOCAL->host);
nntp_gc (stream,GC_TEXTS); /* free local cache */
if (LOCAL->txt) fclose (LOCAL->txt);
/* close NNTP connection */
if (LOCAL->nntpstream) nntp_close (LOCAL->nntpstream);
/* nuke the local data */
fs_give ((void **) &stream->local);
stream->dtb = NIL; /* log out the DTB */
}
}
/* NNTP fetch fast information
* Accepts: MAIL stream
* sequence
* option flags
*/
void nntp_fetchfast (MAILSTREAM *stream,char *sequence,long flags)
{
unsigned long i;
BODY *b;
/* ugly and slow */
if (stream && LOCAL && ((flags & FT_UID) ?
mail_uid_sequence (stream,sequence) :
mail_sequence (stream,sequence)))
for (i = 1; i <= stream->nmsgs; i++)
if (mail_elt (stream,i)->sequence)
nntp_fetchstructure (stream,i,&b,flags & ~FT_UID);
}
/* NNTP fetch flags
* Accepts: MAIL stream
* sequence
* option flags
*/
void nntp_fetchflags (MAILSTREAM *stream,char *sequence,long flags)
{
return; /* no-op for NNTP */
}
/* NNTP fetch envelope
* Accepts: MAIL stream
* message # to fetch
* pointer to return body
* option flags
* Returns: envelope of this message, body returned in body value
*
* Fetches the "fast" information as well
*/
#define MAXHDR (unsigned long) 4*MAILTMPLEN
ENVELOPE *nntp_fetchstructure (MAILSTREAM *stream,unsigned long msgno,
BODY **body,long flags)
{
char *h,*t,tmp[MAXHDR];
LONGCACHE *lelt;
ENVELOPE **env;
STRING bs;
BODY **b;
unsigned long hdrsize;
unsigned long textsize = 0;
MESSAGECACHE *elt;
unsigned long i;
if (flags & FT_UID) { /* UID form of call */
for (i = 1; i <= stream->nmsgs; i++)
if (mail_uid (stream,i) == msgno)
return nntp_fetchstructure (stream,i,body,flags & ~FT_UID);
return NIL; /* didn't find the UID */
}
elt = mail_elt (stream,msgno);
if (stream->scache) { /* short cache */
if (msgno != stream->msgno){/* flush old poop if a different message */
mail_free_envelope (&stream->env);
mail_free_body (&stream->body);
}
stream->msgno = msgno;
env = &stream->env; /* get pointers to envelope and body */
b = &stream->body;
}
else { /* long cache */
lelt = mail_lelt (stream,msgno);
env = &lelt->env; /* get pointers to envelope and body */
b = &lelt->body;
}
if ((body && !*b) || !*env) { /* have the poop we need? */
mail_free_envelope (env); /* flush old envelope and body */
mail_free_body (b);
h = nntp_fetchheader (stream,msgno,NIL,&hdrsize,NIL);
if (body) { /* only if want to parse body */
mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
t = nntp_fetchtext_work (stream,msgno,&textsize,NIL);
/* calculate message size */
elt->rfc822_size = hdrsize + textsize;
if (mg) INIT (&bs,netmsg_string,(void *)LOCAL->txt,textsize);
else INIT (&bs,mail_string,(void *) t,textsize);
}
/* parse envelope and body */
rfc822_parse_msg (env,body ? b : NIL,h ? h : "",hdrsize,body ? &bs : NIL,
BADHOST,tmp);
/* parse date */
if (*env && (*env)->date) mail_parse_date (elt,(*env)->date);
if (!elt->month) mail_parse_date (elt,"01-JAN-1969 00:00:00 GMT");
}
if (body) *body = *b; /* return the body */
return *env; /* return the envelope */
}
/* NNTP fetch message header
* Accepts: MAIL stream
* message # to fetch
* list of header to fetch
* pointer to returned header text length
* option flags
* Returns: message header in RFC822 format
*/
char *nntp_fetchheader (MAILSTREAM *stream,unsigned long msgno,
STRINGLIST *lines,unsigned long *len,long flags)
{
char *hdr,tmp[MAILTMPLEN];
MESSAGECACHE *elt;
unsigned long i;
if (flags & FT_UID) { /* UID form of call */
for (i = 1; i <= stream->nmsgs; i++)
if (mail_uid (stream,i) == msgno)
return nntp_fetchheader (stream,i,lines,len,flags & ~FT_UID);
return ""; /* didn't find the UID */
}
elt = mail_elt (stream,msgno);/* get elt */
if (len) *len = 0; /* no data returned yet */
hdr = stream->scache ? ((LOCAL->hdn == msgno) ? LOCAL->hdr : NIL) :
(char *) elt->data1; /* get cached data if any */
if (!hdr) { /* if don't have header already, must snarf */
sprintf(tmp,"%ld",elt->uid);/* make UID string*/
if (nntp_send (LOCAL->nntpstream,"HEAD",tmp) != NNTPHEAD) {
/* failed, mark as deleted */
mail_elt (stream,msgno)->deleted = T;
return ""; /* return empty string */
}
/* get message header as text */
hdr = netmsg_slurp_text (LOCAL->nntpstream->tcpstream,&elt->data2);
if (stream->scache) { /* set new current header if short caching */
if (LOCAL->hdr) fs_give ((void **) &LOCAL->hdr);
LOCAL->hdr = hdr;
LOCAL->hdn = msgno;
}
/* cache in elt if ordinary caching */
else elt->data1 = (unsigned long) hdr;
}
if (lines) { /* if want filtering, filter copy of text */
if (stream->text) fs_give ((void **) &stream->text);
i = mail_filter (hdr = stream->text = cpystr (hdr),elt->data2,lines,flags);
}
else i = elt->data2; /* full header size */
if (len) *len = i; /* return header length */
return hdr; /* return header */
}
/* NNTP fetch message text (body only)
* Accepts: MAIL stream
* message # to fetch
* pointer to returned message length
* option flags
* Returns: message text in RFC822 format
*/
char *nntp_fetchtext (MAILSTREAM *stream,unsigned long msgno,
unsigned long *len,long flags)
{
unsigned long i;
if (flags & FT_UID) { /* UID form of call */
for (i = 1; i <= stream->nmsgs; i++)
if (mail_uid (stream,i) == msgno)
return nntp_fetchtext (stream,i,len,flags & ~FT_UID);
return ""; /* didn't find the UID */
}
/* mark as seen */
if (!(flags & FT_PEEK)) mail_elt (stream,msgno)->seen = T;
return nntp_fetchtext_work (stream,msgno,len,flags);
}
/* NNTP fetch message text work
* Accepts: MAIL stream
* message # to fetch
* pointer to returned message length
* option flags (never FT_UID)
* Returns: message text in RFC822 format
*/
char *nntp_fetchtext_work (MAILSTREAM *stream,unsigned long msgno,
unsigned long *len,long flags)
{
char *txt;
char tmp[MAILTMPLEN];
mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
MESSAGECACHE *elt = mail_elt (stream,msgno);
if (len) *len = 0; /* no data returned yet */
txt = (stream->scache || mg) ?
((LOCAL->txn == msgno) ? (char *) LOCAL->txt : NIL) :
(char *) elt->data3; /* get cached data if any */
if (!txt) { /* if don't have header already, must snarf */
sprintf(tmp,"%ld",elt->uid);/* make UID string*/
if (nntp_send (LOCAL->nntpstream,"BODY",tmp) != NNTPBODY) {
/* failed, mark as deleted */
mail_elt (stream,msgno)->deleted = T;
return ""; /* return empty string */
}
if (stream->scache || mg) { /* get new current text if short caching */
if (LOCAL->txt) fclose (LOCAL->txt);
LOCAL->txt = netmsg_slurp (LOCAL->nntpstream->tcpstream,&elt->data4,NIL);
LOCAL->hdn = msgno;
fseek (LOCAL->txt,0,L_SET);
if (!mg) mg = mm_gets; /* nothing sucks like VAX/VMS!!!!!!!! */
return (*mg) (netmsg_read,LOCAL->txt,*len);
}
else elt->data3 = (unsigned long)
(txt = netmsg_slurp_text (LOCAL->nntpstream->tcpstream,&elt->data4));
}
if (len) *len = elt->data4; /* return text length */
return txt; /* return text */
}
/* NNTP fetch message body as a structure
* Accepts: Mail stream
* message # to fetch
* section specifier
* pointer to length
* option flags
* Returns: pointer to section of message body
*/
char *nntp_fetchbody (MAILSTREAM *stream,unsigned long msgno,char *s,
unsigned long *len,long flags)
{
char *f;
BODY *b;
PART *pt;
unsigned long i;
unsigned long offset = 0;
mailgets_t mg = (mailgets_t) mail_parameters (NIL,GET_GETS,NIL);
MESSAGECACHE *elt;
if (flags & FT_UID) { /* UID form of call */
for (i = 1; i <= stream->nmsgs; i++)
if (mail_uid (stream,i) == msgno)
return nntp_fetchbody (stream,i,s,len,flags & ~FT_UID);
return NIL; /* didn't find the UID */
}
elt = mail_elt (stream,msgno);/* get elt */
*len = 0; /* no length */
/* make sure have a body */
if (!(nntp_fetchstructure (stream,msgno,&b,flags & ~FT_UID) && b && s &&
*s && isdigit (*s))) return NIL;
if (!(i = strtoul (s,&s,10))) /* section 0 */
return *s ? NIL : nntp_fetchheader (stream,msgno,NIL,len,flags);
do { /* until find desired body part */
/* multipart content? */
if (b->type == TYPEMULTIPART) {
pt = b->contents.part; /* yes, find desired part */
while (--i && (pt = pt->next));
if (!pt) return NIL; /* bad specifier */
/* note new body, check valid nesting */
if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
offset = pt->offset; /* get new offset */
}
else if (i != 1) return NIL;/* otherwise must be section 1 */
/* need to go down further? */
if (i = *s) switch (b->type) {
case TYPEMESSAGE: /* embedded message, calculate new base */
if (!((*s++ == '.') && isdigit (*s))) return NIL;
/* get message's body if non-zero */
if (i = strtoul (s,&s,10)) {
offset = b->contents.msg.offset;
b = b->contents.msg.body;
}
else { /* want header */
*len = b->size.ibytes - b->contents.msg.offset;
b = NIL; /* make sure the code below knows */
}
break;
case TYPEMULTIPART: /* multipart, get next section */
if ((*s++ == '.') && (i = strtoul (s,&s,10)) > 0) break;
default: /* bogus subpart specification */
return NIL;
}
} while (i);
if (b) { /* looking at a non-multipart body? */
if (b->type == TYPEMULTIPART) return NIL;
*len = b->size.ibytes; /* yes, get its size */
}
else if (!*len) return NIL; /* lose if not getting a header */
elt->seen = T; /* mark as seen */
/* get text */
if (!(f = (char *) nntp_fetchtext_work (stream,msgno,&i,flags)) ||
(i < (*len + offset))) return NIL;
if (stream->scache || mg) { /* short caching? */
fseek ((FILE *) f,offset,L_SET);
if (!mg) mg = mm_gets; /* nothing sucks like VAX/VMS!!!!!!!! */
return (*mg) (netmsg_read,f,*len);
}
return f + offset; /* normal caching */
}
/* NNTP set flag
* Accepts: MAIL stream
* sequence
* flag(s)
* option flags
*/
void nntp_setflag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
MESSAGECACHE *elt;
unsigned long i;
long f = mail_parse_flags (stream,flag,&i);
if (!f) return; /* no-op if no flags to modify */
/* get sequence and loop on it */
if ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
mail_sequence (stream,sequence))
for (i = 0; i < stream->nmsgs; i++)
if ((elt = mail_elt (stream,i + 1))->sequence) {
if (f&fSEEN)elt->seen=T;/* set all requested flags */
if (f&fDELETED) { /* deletion also purges the cache */
elt->deleted = T; /* mark deleted */
LOCAL->dirty = T; /* mark dirty */
}
if (f&fFLAGGED) elt->flagged = T;
if (f&fANSWERED) elt->answered = T;
if (f&fDRAFT) elt->draft = T;
}
}
/* NNTP clear flag
* Accepts: MAIL stream
* sequence
* flag(s)
* option flags
*/
void nntp_clearflag (MAILSTREAM *stream,char *sequence,char *flag,long flags)
{
MESSAGECACHE *elt;
unsigned long i;
long f = mail_parse_flags (stream,flag,&i);
if (!f) return; /* no-op if no flags to modify */
/* get sequence and loop on it */
if ((flags & ST_UID) ? mail_uid_sequence (stream,sequence) :
mail_sequence (stream,sequence))
for (i = 0; i < stream->nmsgs; i++)
if ((elt = mail_elt (stream,i + 1))->sequence) {
/* clear all requested flags */
if (f&fSEEN) elt->seen = NIL;
if (f&fDELETED) {
elt->deleted = NIL; /* undelete */
LOCAL->dirty = T; /* mark stream as dirty */
}
if (f&fFLAGGED) elt->flagged = NIL;
if (f&fDRAFT) elt->draft = NIL;
}
}
/* NNTP ping mailbox
* Accepts: MAIL stream
* Returns: T if stream alive, else NIL
*/
long nntp_ping (MAILSTREAM *stream)
{
return (nntp_send (LOCAL->nntpstream,"STAT",NIL) != NNTPSOFTFATAL);
}
/* NNTP check mailbox
* Accepts: MAIL stream
*/
void nntp_check (MAILSTREAM *stream)
{
/* never do if no updates */
if (LOCAL->dirty) newsrc_write (LOCAL->name,stream);
LOCAL->dirty = NIL;
}
/* NNTP expunge mailbox
* Accepts: MAIL stream
*/
void nntp_expunge (MAILSTREAM *stream)
{
if (!stream->silent) mm_log ("Expunge ignored on readonly mailbox",NIL);
}
/* NNTP copy message(s)
* Accepts: MAIL stream
* sequence
* destination mailbox
* option flags
* Returns: T if copy successful, else NIL
*/
long nntp_copy (MAILSTREAM *stream,char *sequence,char *mailbox,long options)
{
mm_log ("Copy not valid for NNTP",ERROR);
return NIL;
}
/* NNTP append message from stringstruct
* Accepts: MAIL stream
* destination mailbox
* stringstruct of messages to append
* Returns: T if append successful, else NIL
*/
long nntp_append (MAILSTREAM *stream,char *mailbox,char *flags,char *date,
STRING *message)
{
mm_log ("Append not valid for NNTP",ERROR);
return NIL;
}
/* NNTP garbage collect stream
* Accepts: Mail stream
* garbage collection flags
*/
void nntp_gc (MAILSTREAM *stream,long gcflags)
{
unsigned long i;
MESSAGECACHE *elt;
if (gcflags & GC_TEXTS) { /* garbage collect texts? */
for (i = 1; i <= stream->nmsgs; ++i) {
if ((elt = mail_elt (stream,i))->data1) fs_give ((void **) &elt->data1);
if (elt->data3) fs_give ((void **) &elt->data3);
}
if (LOCAL->hdr) fs_give ((void **) &LOCAL->hdr);
LOCAL->hdn = 0;
/* flush this too */
if (stream->text) fs_give ((void **) &stream->text);
}
}
/* NNTP open connection
* Accepts: service host list
* initial debugging flag
* Returns: SEND stream on success, NIL on failure
*/
SENDSTREAM *nntp_open (char **hostlist,long options)
{
SENDSTREAM *stream = NIL;
long reply;
void *tcpstream;
if (!(hostlist && *hostlist)) mm_log ("Missing NNTP service host",ERROR);
else do { /* try to open connection */
if (tcpstream = nntp_port ? tcp_open (*hostlist,NIL,nntp_port) :
tcp_open (*hostlist,"nntp",NNTPTCPPORT)) {
stream = (SENDSTREAM *) fs_get (sizeof (SENDSTREAM));
/* initialize stream */
memset ((void *) stream,0,sizeof (SENDSTREAM));
stream->tcpstream = tcpstream;
stream->debug = (options & OP_DEBUG) ? T : NIL;
stream->ok_8bitmime = 1; /* NNTP always supports 8-bit */
/* get server greeting */
reply = nntp_reply (stream);
/* if just want to read, either code is OK */
if (options & OP_READONLY) {
if ((reply == NNTPGREET) ||
(stream->local.nntp.ok_post = (reply == NNTPGREETNOPOST)))
mm_log (stream->reply + 4,(long) NIL);
else { /* oops */
mm_log (stream->reply,ERROR);
stream = nntp_close (stream);
}
}
/* want to post, don't babble */
else if (reply != NNTPGREET) stream = nntp_close (stream);
}
} while (!stream && *++hostlist);
/* some silly servers require this */
if (stream) nntp_send (stream,"MODE","READER");
return stream;
}
/* NNTP close connection
* Accepts: SEND stream
* Returns: NIL always
*/
SENDSTREAM *nntp_close (SENDSTREAM *stream)
{
if (stream) { /* send "QUIT" */
nntp_send (stream,"QUIT",NIL);
/* close TCP connection */
tcp_close (stream->tcpstream);
if (stream->reply) fs_give ((void **) &stream->reply);
fs_give ((void **) &stream);/* flush the stream */
}
return NIL;
}
/* NNTP deliver news
* Accepts: SEND stream
* message envelope
* message body
* Returns: T on success, NIL on failure
*/
long nntp_mail (SENDSTREAM *stream,ENVELOPE *env,BODY *body)
{
long ret = NIL;
char tmp[8*MAILTMPLEN],*s;
/* negotiate post command */
if (nntp_send (stream,"POST",NIL) == NNTPREADY) {
/* set up error in case failure */
nntp_fake (stream,NNTPSOFTFATAL,"NNTP connection broken (message text)");
/* Gabba gabba hey, we need some brain damage to send netnews!!!
*
* First, we give ourselves a frontal lobotomy, and put in some UUCP
* syntax. It doesn't matter that it's completely bogus UUCP, and
* that UUCP has nothing to do with anything we're doing.
*
* Then, we bop ourselves on the head with a ball-peen hammer. How
* dare we be so presumptious as to insert a *comment* in a Date:
* header line. Why, we were actually trying to be nice to a human
* by giving a symbolic timezone (such as PST) in addition to a
* numeric timezone (such as -0800). But the gods of news transport
* will have none of this. Unix weenies, tried and true, rule!!!
*/
/* RFC-1036 requires this cretinism */
sprintf (tmp,"Path: %s!%s\015\012",tcp_localhost (stream->tcpstream),
env->from ? env->from->mailbox : "foo");
/* here's another cretinism */
if (s = strstr (env->date," (")) *s = NIL;
/* output data, return success status */
ret = tcp_soutr (stream->tcpstream,tmp) &&
rfc822_output (tmp,env,body,nntp_soutr,stream->tcpstream) &&
(nntp_send (stream,".",NIL) == NNTPOK);
if (s) *s = ' '; /* put the comment in the date back */
}
return ret;
}
/* NNTP send command
* Accepts: SEND stream
* text
* Returns: reply code
*/
long nntp_send (SENDSTREAM *stream,char *command,char *args)
{
long ret;
char tmp[MAILTMPLEN];
/* build the complete command */
if (args) sprintf (tmp,"%s %s",command,args);
else strcpy (tmp,command);
if (stream->debug) mm_dlog (tmp);
strcat (tmp,"\015\012");
/* send the command */
ret = tcp_soutr (stream->tcpstream,tmp) ? nntp_reply (stream) :
nntp_fake (stream,NNTPSOFTFATAL,"NNTP connection broken (command)");
if ((ret == NNTPWANTAUTH) || (ret == NNTPWANTAUTH2)) {
NETMBX mb;
char usr[MAILTMPLEN],pwd[MAILTMPLEN];
sprintf (tmp,"{%s/nntp}<none>",tcp_host (stream->tcpstream));
mail_valid_net_parse (tmp,&mb);
/* get user name and password */
mm_login (&mb,usr,pwd,(long) 1);
/* try it if user gave one */
if (*pwd && (nntp_send (stream,"AUTHINFO USER",usr) == NNTPWANTPASS) &&
(nntp_send (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED))
/* resend the command */
ret = nntp_send (stream,command,args);
}
return ret;
}
/* NNTP get reply
* Accepts: SEND stream
* Returns: reply code
*/
long nntp_reply (SENDSTREAM *stream)
{
/* flush old reply */
if (stream->reply) fs_give ((void **) &stream->reply);
/* get reply */
if (!(stream->reply = tcp_getline (stream->tcpstream)))
return nntp_fake(stream,NNTPSOFTFATAL,"NNTP connection broken (response)");
if (stream->debug) mm_dlog (stream->reply);
/* handle continuation by recursion */
if (stream->reply[3]=='-') return nntp_reply (stream);
/* return response code */
return (long) atoi (stream->reply);
}
/* NNTP set fake error
* Accepts: SEND stream
* NNTP error code
* error text
* Returns: error code
*/
long nntp_fake (SENDSTREAM *stream,long code,char *text)
{
/* flush any old reply */
if (stream->reply ) fs_give ((void **) &stream->reply);
/* set up pseudo-reply string */
stream->reply = (char *) fs_get (20+strlen (text));
sprintf (stream->reply,"%ld %s",code,text);
return code; /* return error code */
}
/* NNTP filter mail
* Accepts: stream
* string
* Returns: T on success, NIL on failure
*/
long nntp_soutr (void *stream,char *s)
{
char c,*t;
/* "." on first line */
if (s[0] == '.') tcp_soutr (stream,".");
/* find lines beginning with a "." */
while (t = strstr (s,"\015\012.")) {
c = *(t += 3); /* remember next character after "." */
*t = '\0'; /* tie off string */
/* output prefix */
if (!tcp_soutr (stream,s)) return NIL;
*t = c; /* restore delimiter */
s = t - 1; /* push pointer up to the "." */
}
/* output remainder of text */
return *s ? tcp_soutr (stream,s) : T;
}
syntax highlighted by Code2HTML, v. 0.9.1