ftp.nice.ch/pub/next/unix/communication/ft_bbs.1.0.s.tar.gz#/ft_bbs-1.0/news.c

This is news.c in view mode; [Download] [Up]

/* $Id$ */
#ifdef NNTPNEWS

#include "bbs.h"

int browsegroup(grouptyp *grouprecord, newsstattyp *newsrecord,
                const confrecordtyp *confrecord)
{
  int lang, art_anz, sel_anz, cnt, n, p;
  char str[STRLEN+1], sstr[S_STRLEN+1], buf[2*STRLEN+1], gstr[ARG_MAX+1], file[PATH_MAX+1], maddr[STRLEN+1], raddr[STRLEN+1], article[STRLEN+1], *header, *sp, *bgbuf, *splits[MAXARGS+1], key, dokey;
  boolean unread_only, bodyonly;
  newsheadertyp newshdr;
  chrootrecordtyp chrootrec;
  tlistetyp *artliste = (tlistetyp *)0;
  
  lang = confrecord->userrecord.lang;
  unread_only = TRUE;
  newsrecord->group_is_set = FALSE;
  bbslog(LOG_FILE,confrecord,"group %s",grouprecord->groupname);
  cnt = 0;
  do {
    cnt++;
    bgbuf = showmsgline(msg("browsegroup",0,lang));
    if (cnt == 2)  unread_only = FALSE;
    n = nntp_getoverview(grouprecord,unread_only,newsrecord,confrecord);
    if (n == ERR_NONEXT && grouprecord->anz_article > 0) {
      key = getkeyonprompt(msg("browsegroup",1,lang),"JYN",TRUE,FALSE,
			    NULL,confrecord);
      if (key != 'N')  unread_only = FALSE;
    }
    else if (n == ERR_NONEXT && grouprecord->anz_article == 0) {
      writetouser(confrecord,msg("browsegroup",2,lang),
                  grouprecord->groupname);
    }
    else if (n != OK_NOV) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","browsegroup",
		"cannot get overview for group %s",grouprecord->groupname);
    }
  } while (n == ERR_NONEXT && ! unread_only && cnt < 2);
  if (n != OK_NOV)  return -1;

  art_anz = overviewtolist(&artliste,grouprecord,confrecord);
  removeliste(&grouprecord->ov_liste,TRUE,TL_BLOCKLEN);

  sprintf(sstr,msg("browsegroup",3,lang),grouprecord->groupname,art_anz);
  while ((key=newspager_c(artliste,art_anz,sstr,NULL,FALSE,
			  "newsbrowser_artselect",
			  confrecord)) != ESCAPE_KEY) {
    dokey = NO_KEY;
    *gstr = '\0';
    for (p=0,sel_anz=0; p<art_anz; p++) {
      readliste(&artliste,FALSE,p,TL_BLOCKLEN,&sp,confrecord);
      if (*sp == 'T')  sel_anz++;
    }
    cnt = 0;
    for (p=0; p<art_anz; p++) {
      readliste(&artliste,FALSE,p,TL_BLOCKLEN,&sp,confrecord);
      if (*sp == 'T') {
	cnt++;
	if (dokey == NO_KEY) {
	  dokey = getkeyonprompt(msg("browsegroup",4,lang),"LRAFHG",
				  TRUE,FALSE,"newsbrowser_doselect",
				  confrecord);
	}
	*sp = '!';
	sp++;
	strcpy(buf,sp);
	splitstring(splits,buf,'\n',6);
	strcpy(article,splits[1]);
	n = atoi(splits[2]);
	strmaxcpy(str,article,COLS-24);
	bgbuf = showmsgline(msg("browsegroup",5,lang),str);
	bbslog(LOG_FILE,confrecord,"article %s (%d)",article,n);
	if (dokey == 'A' || dokey == 'F' || dokey == 'H') {
	  bodyonly = TRUE;
	}
	else {
	  bodyonly = FALSE;
	}
	if (nntp_getarticle(&header,file,bodyonly,n,newsrecord,
			    confrecord) < 0) {
	  errormsg(E_LOGFILE|E_USER,confrecord,"bbs","browsegroup",
		    "cannot get article %s (%d), group %s",str,n,
		    grouprecord->groupname);
	  continue;
	}
	if (dokey == 'H') {
	  do_shownewsheader(header,confrecord);
	}
	else if (dokey == 'G') {
	  strmaxcat(gstr,file,ARG_MAX);
	  strmaxcat(gstr," ",ARG_MAX);
	}
	else if (dokey == 'R' || dokey == 'L') {
	  parsenewsheader(&newshdr,header);
	  mimerecode(file,newshdr.mime_encoding,confrecord);
	  parseaddrline(maddr,raddr,newshdr.from);
	  sprintf(str,"(%d/%d): ",cnt,sel_anz);
	  if (*raddr != '\0') {
	    strmaxcat(str,raddr,24);
	  }
	  else {
	    strmaxcat(str,maddr,24);
	  }
	  strcat(str,": ");
	  strmaxcat(str,article,COLS);
	  if (do_showfile(file,str,NULL,NULL,confrecord) == ESCAPE_KEY) {
	    key = getkeyonprompt(msg("browsegroup",6,lang),"JYN",TRUE,
				  FALSE,NULL,confrecord);
	    if (key != 'N') {
	      free(header);
	      unlink(file);
	      break;
	    }
	  }
	}
	else if (dokey == 'A' || dokey == 'F') {
	  parsenewsheader(&newshdr,header);  
	  parseaddrline(maddr,raddr,newshdr.from);
	  if (*raddr != '\0') {
	    strmaxcpy(str,raddr,24);
	  }
	  else {
	    strmaxcpy(str,maddr,24);
	  }
	  move(0,0);
	  clear();
	  standout();
	  printw(msg("browsegroup",7,lang),str,article);
	  standend();
	  newsreply(&newshdr,file,newsrecord,confrecord);
	}
	free(header);
	if (dokey != 'G')  unlink(file);
      }
    }
    if (dokey == 'G') {
      strcpy(chrootrec.root,"/");
      getcwd(chrootrec.cwd,PATH_MAX);
      do_get(gstr,&chrootrec,protokollnamen,
	      confrecord->userrecord.protokoll,confrecord);
      n = splitstring(splits,gstr,' ',MAXARGS);
      for (p=0; p < n; p++)  unlink(splits[p]);
    }
  }
  return 0;
}


int newsreply(newsheadertyp *newshdr, const char *body,
              newsstattyp *newsrecord, const confrecordtyp *confrecord)
{
  int lang;
  char zeile[STRLEN+1], groups[STRLEN+1], str[PATH_MAX+2*STRLEN+23],
       scratch[PATH_MAX+1], subject[STRLEN+1], addr[ARG_MAX+1],
       refs[STRLEN+1], path[PATH_MAX+1], key;
  FILE *fp, *sfp;
  
  lang = confrecord->userrecord.lang;
  if ((fp=fopen(body,"r")) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","newsreply",
             "fopen %s: %m",body);
    return -1;
  }
  scratchfilename(scratch,"newsreply.",NULL,confrecord->scratchdir);
  if ((sfp=fopen(scratch,"w")) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","newsreply",
             "fopen %s: %m",scratch);
    return -1;
  }
  fprintf(sfp,"In reply of:\n");
  while (fgetnln(zeile,STRLEN,fp) >= 0) {
    fputs("> ",sfp);
    fputs(zeile,sfp);
    putc('\n',sfp);
  }
  fclose(fp);
  fclose(sfp);

  if (*newshdr->references == '\0') {
    strcpy(subject,"Re: ");
  }
  else {
    *subject = '\0';
    strcpy(refs,newshdr->references);
    strcat(refs," ");
  }
  strmaxcat(subject,newshdr->subject,STRLEN-1);
  strmaxcat(refs,newshdr->msg_id,STRLEN-1);
  if (*newshdr->reply_to != '\0') {
    strcpy(addr,newshdr->reply_to);
  }
  else {
    strcpy(addr,newshdr->from);
  }
  if (*newshdr->followup_to != '\0') {
    strcpy(groups,newshdr->followup_to);
  }
  else {
    strcpy(groups,newshdr->newsgroups);
  }

  key = getkeyonprompt(msg("newsreply",0,lang),"EAB",TRUE,FALSE,NULL,
                       confrecord);
  if (key == 'E' || key == 'B') {
    *str = '\0';
    if (sendmail_c(str,scratch,addr,subject,FALSE,NULL,confrecord) > 1) {
      do_sendmail(str,path,NULL,NULL,confrecord);
    }
  }
  if (key == 'A' || key == 'B') {
    postarticle_c(scratch,groups,subject,refs,newsrecord,confrecord);
  }
  unlink(scratch);
  return 0;
}


int getnewsgrpname(char *group, const confrecordtyp *confrecord)
{
  int n;

  *group = '\0';
  do {
    move(LINES-1,0);
    clrtoeol();
    standout();
    addstr("Newsgroup:");
    standend();
    addch(' ');
    n = readfromuser(group,group,STRLEN,TRUE,confrecord);
    if (n == 0)  return 0;
    n = checkgroup(group,confrecord);
    if (n == 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","getnewsgrpname",
               "Newsgroup %s does not exist",group);
    }
  } while (n == 0);
  return n;
}


int checknewsperms(const confrecordtyp *confrecord)
{
  int n, rw = -1;
  char *psplits[MAXARGS+1], pstr[STRLEN+1];

  strmaxcpy(pstr,confrecord->userrecord.permittions,STRLEN);
  splitstring(psplits,pstr,':',MAXARGS);
  for (n=0; psplits[n] != NULL; n++) {
    if (*psplits[n] == 'P')  return 1;
    if (*psplits[n] == 'N')  rw = 0;
  }
  return rw;
}


int parsenewsheader(newsheadertyp *newshdr, char *hdrbuf)
{
  int k;
  char *sp, *key, *txt, *headers[101];
  static char *nullchar = "";

  newshdr->from = nullchar;
  newshdr->newsgroups = nullchar;
  newshdr->reply_to = nullchar;
  newshdr->followup_to = nullchar;
  newshdr->subject = nullchar;
  newshdr->date = nullchar;
  newshdr->msg_id = nullchar;
  newshdr->references = nullchar;
  newshdr->mime_encoding = nullchar;
  splitstring(headers,hdrbuf,'\n',100);    
  for (k=0; headers[k] != NULL; k++) {
    if ((sp=strchr(headers[k],':')) != NULL) {
      *sp = '\0';
      key = headers[k];
      sp++;
      while (*sp == ' ' && *sp)  sp++;
      txt = sp;
      while (*sp)  sp++;
      if (sp > txt)  sp--;
      while (*sp == ' ' && sp > txt)  *sp-- = '\0';
      lowercases(key);
      if (strcmp(key,"from") == 0) {
        newshdr->from = txt;
      }
      else if (strcmp(key,"newsgroups") == 0) {
        newshdr->newsgroups = txt;
      }
      else if (strcmp(key,"reply-to") == 0) {
        newshdr->reply_to = txt;
      }
      else if (strcmp(key,"followup-to") == 0) {
        newshdr->followup_to = txt;
      }
      else if (strcmp(key,"subject") == 0) {
        newshdr->subject = txt;
      }
      else if (strcmp(key,"date") == 0) {
        newshdr->date = txt;
      }
      else if (strcmp(key,"message-id") == 0) {
        newshdr->msg_id = txt;
      }
      else if (strcmp(key,"references") == 0) {
        newshdr->references = txt;
      }
      else if (strcmp(key,"content-transfer-encoding") == 0) {
        newshdr->mime_encoding = txt;
      }
    }
  }
  return 0;
}


int updatenewsrc(DBASE_DB *newsrcdb, grouptyp *grouprecord,
                 const boolean sync_newsrc, const confrecordtyp *confrecord)
{
  char sstr[81];
  DBASE_DBT dbkey, dbdata;

  DBASE_DATA(dbkey) = grouprecord->groupname;
  DBASE_SIZE(dbkey) = strlen(grouprecord->groupname) + 1;
  sprintf(sstr,"1-%d",grouprecord->last_article);
  DBASE_DATA(dbdata) = sstr;
  DBASE_SIZE(dbdata) = strlen(sstr) + 1;
  if (newsrcdb->put(newsrcdb,&dbkey,&dbdata,0) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","updatenewsrc",
	      "cannot put new last_article into newsrc DB");
    return -1;
  }
  if (sync_newsrc) {
    if (syncnewsrcdb(newsrcdb,confrecord) < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","updatenewsrc",
               "cannot sync to newsrc");
      return -1;
    }
  }
  return 0;
}


int syncnewsrcdb(DBASE_DB *newsrcdb, const confrecordtyp *confrecord)
{
  int anz, n;
  U_INT flags = R_FIRST;
  char str[2*S_STRLEN+3];
  DBASE_DBT dbkey, dbdata;
  tlistetyp *tliste = (tlistetyp *)0;
  
  anz = 0;
  while ((n = newsrcdb->seq(newsrcdb,&dbkey,&dbdata,flags)) != 1) {
    if (n < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","syncnewsrcdb",
               "seq failed: %m");
      return -1;
    }
    flags = R_NEXT;
    sprintf(str,"%s: %s",(char *)DBASE_DATA(dbkey),(char *)DBASE_DATA(dbdata));
    addstrtoliste(&tliste,TRUE,anz++,TL_BLOCKLEN,str,confrecord);
  }
  n = savenewsrc(tliste,anz,confrecord);
  removeliste(&tliste,TRUE,TL_BLOCKLEN);
  return n;
}


DBASE_DB *createnewsrcdb(newsstattyp *newsrecord,
                         const confrecordtyp *confrecord)
{
  int n, k, anz;
  char *sp1, *sp2;
  DBASE_DB *db;
  DBASE_DBT dbkey, dbdata;
  tlistetyp *newsrcliste = (tlistetyp *)0;
  HASHINFO hinf = {256, 8, 1, 512000, NULL, 0};
  
  newsrecord->subscribed_anz = -1;
  newsrecord->newsrcdb = NULL;
  if ((db=dbopen(NULL,O_CREAT|O_TRUNC|O_RDWR,0600,DB_HASH,&hinf)) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","createnewsrcdb",
             "cannot create newsrc-db: %m");
    return NULL;
  }
  if ((anz=getnewsrc(&newsrcliste,confrecord)) < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","createnewsrcdb",
             "cannot get newsrc");
    db->close(db);
    return NULL;
  }
  for (k=0; k < anz; k++) {
    readliste(&newsrcliste,FALSE,k,TL_BLOCKLEN,&sp1,confrecord);
    for (sp2=sp1; *sp2 && *sp2!=':'; sp2++) ;
    if (*sp2)  *sp2++ = '\0';
    while (*sp2==' ')  sp2++;
    DBASE_DATA(dbkey) = sp1;
    DBASE_SIZE(dbkey) = strlen(sp1) + 1;
    DBASE_DATA(dbdata) = sp2;
    DBASE_SIZE(dbdata) = strlen(sp2) + 1;
    n = db->put(db,&dbkey,&dbdata,R_NOOVERWRITE);
    if (n > 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","createnewsrcdb",
               "duplicate key %s",(char *)DBASE_DATA(dbkey));
    }
    else if (n < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","createnewsrcdb",
               "error at key %s: %m",(char *)DBASE_DATA(dbkey));
    }
  }
  removeliste(&newsrcliste,TRUE,TL_BLOCKLEN);
  newsrecord->subscribed_anz = anz;
  newsrecord->newsrcdb = db;
  return db;
}


int lookupnewsrcdb(const DBASE_DB *newsrcdb, const void *key,
                  const SIZE_T keysize, void *data, SIZE_T *datasize,
                  const confrecordtyp *confrecord)
{
  int n;
  DBASE_DBT dbkey, dbval;

  DBASE_SIZE(dbkey) = keysize;
  if (keysize == 0) {
    DBASE_SIZE(dbkey) = strlen((char *)key) + 1;
  }
  if ((DBASE_DATA(dbkey) = malloc(DBASE_SIZE(dbkey))) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","lookupnewsrcdb","malloc: %m");
    return -1;
  }
  memcpy(DBASE_DATA(dbkey),key,DBASE_SIZE(dbkey));
  if (data != NULL) {
    *datasize = 0;
    BZERO(data,1);
  }
  n = newsrcdb->get(newsrcdb,&dbkey,&dbval,0);
  if (n < 0) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","lookupnewsrcdb","user %s: %m",
             (char *)key);
  }
  else if (n == 0 && data != NULL) {
    *datasize = DBASE_SIZE(dbval);
    memcpy(data,DBASE_DATA(dbval),*datasize);
  }
  free(DBASE_DATA(dbkey));
  return n;

}


int newsrcdbtolist(tlistetyp **tliste, const DBASE_DB *newsrcdb,
                   const confrecordtyp *confrecord)
{
  int anz, n;
  U_INT flags = R_FIRST;
  char str[S_STRLEN+1];
  DBASE_DBT dbkey, dbdata;
  
  anz = 0;
  *tliste = (tlistetyp *)0;
  while ((n = newsrcdb->seq(newsrcdb,&dbkey,&dbdata,flags)) != 1) {
    if (n < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","newsrcdbtolist",
               "seq failed: %m");
      return -1;
    }
    flags = R_NEXT;
    strcpy(str,"F");
    strmaxcat(str,(char *)DBASE_DATA(dbkey),S_STRLEN);
    addstrtoliste(tliste,TRUE,anz++,TL_BLOCKLEN,str,confrecord);
  }
  return anz;
}


int overviewtolist(tlistetyp **tliste, const grouptyp *grouprecord,
                   const confrecordtyp *confrecord)
{
  int anz, nr, n, k, p;
  char str[2*STRLEN+1], rstr[STRLEN+1], sstr[21], buf[4*STRLEN+1],
       subj[STRLEN+1], maddr[STRLEN+1], raddr[STRLEN+1], *splits[MAXARGS+1],
       *sp, *sp2;
  DBASE_DB *db;
  DBASE_DBT dbkey, dbdata;
  articletyp art;
  tlistetyp *ov_liste, *sliste = (tlistetyp *)0;
  int articlecmp(const char *, const char *);
  HASHINFO hinf = {256, 8, 1, 1024000, NULL, 0};

  ov_liste = grouprecord->ov_liste;
  if ((db=dbopen(NULL,O_CREAT|O_TRUNC|O_RDWR,0600,DB_HASH,&hinf)) == NULL) {
    errormsg(E_LOGFILE|E_USER,confrecord,"bbs","overviewtolist",
             "cannot create Id DB: %m");
    return -1;
  }
  nr = 1;
  for (p=0; p < grouprecord->ov_anz; p++) {
    readliste(&ov_liste,FALSE,p,TL_BLOCKLEN,&sp,confrecord);
    strcpy(buf,sp);
    if (parseoverviewline(&art,buf) < 0) {
      continue;
    }
    DBASE_DATA(dbkey) = art.msg_id;
    DBASE_SIZE(dbkey) = strlen(art.msg_id) + 1;
    sprintf(sstr,"%04d",nr++);
    DBASE_DATA(dbdata) = sstr;
    DBASE_SIZE(dbdata) = strlen(sstr) + 1;
    if (db->put(db,&dbkey,&dbdata,0) < 0) {
      errormsg(E_LOGFILE|E_USER,confrecord,"bbs","overviewtolist",
               "put failed: %m");
      return -1;
    }
  }
  for (p=0; p < grouprecord->ov_anz; p++) {
    readliste(&ov_liste,FALSE,p,TL_BLOCKLEN,&sp,confrecord);
    strcpy(buf,sp);
    if (parseoverviewline(&art,buf) < 0) {
      continue;
    }
    strcpy(rstr,art.references);
    anz = splitstring(splits,rstr,' ',MAXARGS);
    for (k=0; k < anz; k++) {
      DBASE_DATA(dbkey) = splits[k];
      DBASE_SIZE(dbkey) = strlen(splits[k]) + 1;
      sprintf(sstr,"%04d",nr);
      DBASE_DATA(dbdata) = sstr;
      DBASE_SIZE(dbdata) = strlen(sstr) + 1;
      n = db->put(db,&dbkey,&dbdata,R_NOOVERWRITE);
      if (n < 0) {
	errormsg(E_LOGFILE|E_USER,confrecord,"bbs","overviewtolist",
		  "put failed: %m");
	return -1;
      }
      if (n == 0)  nr++;
    }
  }

  nr = 0;
  for (p=0; p < grouprecord->ov_anz; p++) {
    readliste(&ov_liste,FALSE,p,TL_BLOCKLEN,&sp,confrecord);
    strcpy(buf,sp);
    if (parseoverviewline(&art,buf) < 0) {
      continue;
    }
    strcpy(rstr,art.references);
    anz = splitstring(splits,rstr,' ',MAXARGS);
    splits[anz++] = art.msg_id;
    *str = '\0';
    for (k=0; k < anz; k++) {
      DBASE_DATA(dbkey) = splits[k];
      DBASE_SIZE(dbkey) = strlen(splits[k]) + 1;
      if (db->get(db,&dbkey,&dbdata,0) < 0) {
	errormsg(E_LOGFILE|E_USER,confrecord,"bbs","overviewdbtolist",
		  "get failed: %m");
	return -1;
      }
      strcat(str,DBASE_DATA(dbdata));
/*
      strcat(str,",");
*/
    }
    strcat(str,"\n");
    strcat(str,art.from);
    strcat(str,"\n");
    strcat(str,art.subject);
    strcat(str,"\n");
    sprintf(sstr,"%d",art.reflevel);
    strcat(str,sstr);
    strcat(str,"\n");
    sprintf(sstr,"%d",art.nr);
    strcat(str,sstr);
    strcat(str,"\n");
    strcat(str,art.date);
    addstrtoliste(&sliste,TRUE,nr++,TL_BLOCKLEN,str,confrecord);
  }
  db->close(db);
  anz = nr;

  if (anz > 1)  sort_liste(sliste,TL_BLOCKLEN,0,anz-1,articlecmp,confrecord);

  *tliste = (tlistetyp *)0;
  *subj = '\0';
  for (k=0; k < anz; k++) {
    readliste(&sliste,FALSE,k,TL_BLOCKLEN,&sp,confrecord);
    splitstring(splits,sp,'\n',6);
    parseaddrline(maddr,raddr,splits[1]);
    if (*raddr != '\0') {
      sprintf(str,"F%-18.18s  ",raddr);
    }
    else {
      sprintf(str,"F%-18.18s  ",maddr);
    }
    sp2 = splits[2];
    if (strncasecmp("re: ",sp2,4) == 0)  sp2 += 4;
    for (n=0; n < atoi(splits[3]) && n < 9; n++)  strcat(str,">");
    if (strcmp(sp2,subj) != 0) {
      strcpy(subj,sp2);
      strcat(str,subj);
    }
    strcat(str,"\n");
    strcat(str,splits[2]);
    strcat(str,"\n");
    strcat(str,splits[4]);
    strcat(str,"\n");
    strcat(str,splits[5]);
    addstrtoliste(tliste,TRUE,k,TL_BLOCKLEN,str,confrecord);
  }
  removeliste(&sliste,TRUE,TL_BLOCKLEN);
  return anz;
}


int articlecmp(const char *s1, const char *s2)
{
  return strcmp(s1,s2);
}


int parseoverviewline(articletyp *art, char *line)
{
  int n;
  char *splits[9], *sp1, *sp2;

  n = 0;  
  sp1 = line;
  splits[0] = sp1;
  while (*sp1 && *sp1!='\n') {
    if (*sp1 != '\t') {
      sp1++;
    }
    else {
      n++;
      *sp1++ = '\0';
      if (n < 9)  splits[n] = sp1;
    }
  }
  if (*sp1 == '\n')  *sp1++ = '\0';
  if (n >= 7) {
    art->nr = atoi(splits[0]);
    strmaxcpy(art->subject,splits[1],S_STRLEN);
    strmaxcpy(art->from,splits[2],S_STRLEN);
    strmaxcpy(art->date,splits[3],30);
    strmaxcpy(art->msg_id,splits[4],S_STRLEN);
    strmaxcpy(art->references,splits[5],STRLEN);
    art->bytes = atoi(splits[6]);
    art->lines = atoi(splits[7]);
    art->reflevel = 0;
    if (*art->references != '\0')  art->reflevel++;
    for (sp2=art->references; *sp2; sp2++) {
      if (*sp2 == ' ') {
	while (*sp2==' ')  sp2++;
	art->reflevel++;
      }
    }
    art->read = 'F';
  }
  else {
    n = -1;
  }
  return n;
}

#endif

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.