This is spool.c in view mode; [Download] [Up]
/* Local spool support for slrn added by Olly Betts <olly@mantis.co.uk> */
#include "config.h"
#include "features.h"
#define DEBUG_SPOOL 0
#define DEBUG_SPOOL_FILENAME "SLRNDEBUG"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#include <string.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <slang.h>
#include "jdmacros.h"
#include <time.h>
#include "misc.h"
#include "slrn.h"
#ifndef SLRN_SPOOL_ROOT
# define SLRN_SPOOL_ROOT "/var/spool/news" /* a common place for the newsspool */
#endif
#ifndef SLRN_SPOOL_NOV_ROOT
# define SLRN_SPOOL_NOV_ROOT SLRN_SPOOL_ROOT
#endif
#ifndef SLRN_SPOOL_NOV_FILE
# define SLRN_SPOOL_NOV_FILE ".overview"
#endif
#include <sys/stat.h>
#include <assert.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# define NEED_D_NAMLEN
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#include <limits.h>
#ifndef NNTP_MAX_XOVER_SIZE
# define NNTP_MAX_XOVER_SIZE 1024
#endif
static int spool_put_server_cmd (char *, char *, unsigned int );
static int spool_select_group (char *, int *, int *);
static void spool_close_server (void);
static int spool_has_cmd (char *);
static int spool_initialize_server (void);
static char *spool_read_line (char *, unsigned int );
static int spool_xpat_cmd (char *, int, int, char *);
static int spool_xgtitle_cmd (char *);
static int spool_select_article (int, char *);
static int spool_open_xover (int, int);
static char *spool_read_xover (char *, unsigned int);
static void spool_close_xover (void);
static char *spool_head_from_msgid (char *, char *, unsigned int );
static int spool_xhdr_command (char *, int, char *, unsigned int);
static int spool_list_newsgroups (void);
static int spool_list_active (void);
static void spool_open_suspend_xover (void);
static void spool_close_suspend_xover (void);
static char *spool_get_extra_xover_header (char *);
static char *spool_read_create_xover_line (int, char *);
static Slrn_Server_Obj_Type Spool_Server_Obj;
/* some state that the NNTP server would take care of if we were using one */
static FILE *Spool_fh_local=NULL;
static char *Spool_Group=NULL;
static FILE *Spool_fh_nov=NULL; /* we use the overview file lots, so keep it open */
static int Spool_cur_artnum=0;
static int Spool_max_artnum=0;
static int Spool_fhead=0; /* if non-0 we're emulating "HEAD" so stop on blank line */
static int Spool_fFakingActive=0; /* if non-0 we're doing funky stuff with MH folders */
static int spool_fake_active( char *);
static char *spool_fakeactive_read_line(char *, int);
static int Spool_fakeactive_newsgroups=0;
#if DEBUG_SPOOL
# include <stdarg.h>
static void spool_debug (char *fmt, ...)
{
FILE *fp = NULL;
va_list ap;
if (fp == NULL)
{
fp = fopen (DEBUG_SPOOL_FILENAME, "w");
if (fp == NULL)
return;
}
fprintf (fp, "%lu: ", (unsigned long) clock ());
if (fmt == NULL)
{
fputs ("(NULL)", fp);
}
else
{
va_start(ap, fmt);
vfprintf(fp, fmt, ap);
va_end (ap);
}
putc ('\n', fp);
fflush (fp);
}
#endif
/* close any current file (unless it's the overview file) and NULL the FILE* */
static int spool_fclose_local (void)
{
int res = 0;
Spool_fhead=0;
Spool_fFakingActive=0;
if (Spool_fh_local != NULL)
{
if (Spool_fh_local != Spool_fh_nov)
res = fclose(Spool_fh_local);
Spool_fh_local=NULL;
}
return res;
}
static char *spool_dircat (char *root, char *name, int map_dots)
{
char *spool_group, *p, ch;
unsigned int len;
len = strlen (root);
spool_group = SLMALLOC(strlen(name) + len + 2);
if (spool_group == NULL)
{
slrn_exit_error ("Out of memory.");
}
strcpy (spool_group, root);
p = spool_group + len;
if (len && (*(p - 1) != '/'))
*p++ = '/';
strcpy (p, name);
if (map_dots) while ((ch = *p) != 0)
{
if (ch == '.') *p = '/';
p++;
}
return spool_group;
}
static int spool_find_artnum_from_msgid (char *msgid)
{
char buf [8 * NNTP_MAX_XOVER_SIZE];
char *p;
#if DEBUG_SPOOL
spool_debug ("spool_find_artnum_from_msgid('%s')", msgid);
#endif
spool_open_xover( 1, INT_MAX );
while (NULL != spool_read_xover (buf, sizeof(buf)))
{
char *q;
/* 5th field is message id. */
if (NULL == (p = slrn_strchr(buf, '\t'))) continue;
if (NULL == (p = slrn_strchr(p + 1, '\t'))) continue;
if (NULL == (p = slrn_strchr(p + 1, '\t'))) continue;
if (NULL == (p = slrn_strchr(p + 1, '\t'))) continue;
p++; /* skip tab */
q = slrn_strchr(p,'\t');
if (q != NULL) *q='\0';
if (0 == strcmp(msgid, p))
{
spool_close_xover();
#if DEBUG_SPOOL
spool_debug ("spool_find_artnum_from_msgid() returns %d",atoi(buf));
#endif
return atoi(buf);
}
}
spool_close_xover();
#if DEBUG_SPOOL
spool_debug ("spool_find_artnum_from_msgid() found no match");
#endif
return -1;
}
/* This is a separate function so localspool backend can just close the file */
static void spool_discard_output (char *line, unsigned int len)
{
if (0 == Spool_fFakingActive)
spool_fclose_local ();
/* not critical to shut down part way through, as slrn doesn't try to */
else while (NULL != spool_read_line (line, len))
{
;
}
}
static int spool_nntp_head (char *line, char *buf, unsigned int len)
{
char *p;
spool_fclose_local();
p = slrn_skip_whitespace (line + 5);
/* !HACK! ought to check for "head <msg-id>" (with or without < >) */
if (*p == '<')
{
Spool_cur_artnum = spool_find_artnum_from_msgid (p);
/* !HACK! could check for -1 here */
}
else if (*p)
Spool_cur_artnum = atoi(p);
/* else no number given, so use current article number */
sprintf (buf, "%d", Spool_cur_artnum);
/* ugly temporary use of buffer !HACK! */
p = buf;
Spool_fh_local = fopen (p,"r");
if (NULL == Spool_fh_local)
{
return ERR_NOARTIG; /* No such article in this group */
}
Spool_fhead = 1; /* set flag to stop after headers */
sprintf (buf,"221 %d", Spool_cur_artnum);
/* !HACK! INN also gives message-id in this message */
return OK_HEAD; /* Head follows */
}
static int spool_nntp_next (char *line, char *buf, unsigned int len)
{
int i;
spool_fclose_local();
i = Spool_cur_artnum;
do
{
char num[80];
i++;
sprintf (num,"%d",i);
if (1 == slrn_file_exists (num))
{
Spool_cur_artnum = i;
sprintf(buf,"223 %d",Spool_cur_artnum);
#if DEBUG_SPOOL
spool_debug ("NEXT found article %d",Spool_cur_artnum);
#endif
return OK_NOTEXT; /* No text sent -- stat, next, last */
}
}
while (i <= Spool_max_artnum); /* !HACK! better to find value from overview file or active file in case the group grows while we're in it? */
#if DEBUG_SPOOL
spool_debug ("No NEXT -- %d > %d", Spool_cur_artnum, Spool_max_artnum);
#endif
return ERR_NONEXT; /* No next article in this group */
}
static int spool_nntp_newgroups (char *line, char *buf, unsigned int len)
{
/* expect something like "NEWGROUPS 960328 170939 GMT" GMT is optional */
char *p;
int Y,M,D,h,m,s;
int c;
int fGMT;
int n;
time_t threshold;
struct tm t;
long fpos;
char buf1[512];
int ch;
int found;
int first;
c = sscanf(line+9,"%02d%02d%02d %02d%02d%02d%n",&Y,&M,&D,&h,&m,&s,&n);
assert(c==6); /* group.c should have sanity checked the line */
p = slrn_skip_whitespace(line + 9 + n);
fGMT = (0 == strncmp (p, "GMT", 3));
/* this !HACK! is good until 2051 -- round at 50 cos the RFC says to */
Y += ( Y>50 ? 1900 : 2000 );
/* for full version, use this instead:
* if (Y<=50) Y+=100;
* yr = this_year();
* Y += ((yr-51)/100*100);
*/
#if DEBUG_SPOOL
spool_debug ("%d read, %04d-%02d-%02d %d:%02d:%02d %s",
c,Y,M,D,h,m,s,fGMT?"GMT":"(local)");
#endif
t.tm_year = Y - 1900; /* tm_year is years since 1900 */
t.tm_mon = M - 1; /* tm_mon has January as 0 */
t.tm_mday = D;
t.tm_hour = h;
t.tm_min = m;
t.tm_sec = s;
t.tm_isdst = 0; /* say it's not DST (? !HACK!) */
threshold = mktime( &t );
#if DEBUG_SPOOL
spool_debug ("threshold (local) = %d",threshold);
#endif
if (fGMT)
{
threshold=mktime(gmtime(&threshold)); /* !HACK! is this correct? */
#if DEBUG_SPOOL
spool_debug ("threshold (GMT) = %d",threshold);
#endif
}
spool_fclose_local();
Spool_fh_local = fopen (Slrn_ActiveTimes_File, "r");
if (Spool_fh_local == NULL)
{
/* !HACK! at this point, it would be nice to be able to check for
* recently created directories with readdir() and to return those
* which would be useful for reading MH folders, etc. This would
* be more than a little slow for a true newsspool though.
*
* Hmm, looked into this and it seems that you can't use the mtime
* or ctime to decide when a directory was created. Hmmm.
*/
#if DEBUG_SPOOL
spool_debug ("Couldn't open active.times");
#endif
return ERR_FAULT; /* Program fault, command not performed */
}
/* chunk size to step back through active.times by
* when checking for new groups */
/* 128 should be enough to find the last line in the probably most
* common case of no new groups */
#define SLRN_SPOOL_ACTIVETIMES_STEP 128
/* find start of a line */
fseek (Spool_fh_local, 0, SEEK_END ); /* !HACK! check return value from this and all the rest... */
fpos = ftell(Spool_fh_local);
#if DEBUG_SPOOL
spool_debug ("ftell=%ld errno=%d (%s)",fpos,
errno, strerror(errno));
errno = 0;
#endif
found=0;
first=1;
while (!found)
{
int i, len1;
len1 = SLRN_SPOOL_ACTIVETIMES_STEP;
if (fpos < (long)len1) len1=fpos; /* don't run of the start of the file */
fpos -= len1;
fseek (Spool_fh_local, fpos, SEEK_SET );
if (fpos == 0) break;
#if DEBUG_SPOOL
spool_debug ("ftell=%ld errno=%d (%s)", ftell (Spool_fh_local),
errno, strerror(errno));
#endif
if (first)
{
/* on the first pass, we want to ignore the last byte \n at eof */
--len1;
first=0;
}
for (i = 0; i < len1; i++)
{
ch = getc(Spool_fh_local);
assert(ch!=EOF); /* shouldn't happen */
if (ch != '\n') continue;
while ((i < len1)
&& (NULL != fgets (buf1, sizeof(buf1), Spool_fh_local)))
{
i -= strlen(buf1);
p = buf1;
while (*p && (0 == isspace (*p)))
p++;
if (atol(p) < threshold)/* or <= ? !HACK! */
{
found = 1;
break;
}
}
break;
}
}
fpos=ftell(Spool_fh_local);
#if DEBUG_SPOOL
spool_debug ("ftell=%ld errno=%d (%s)",fpos, errno, strerror(errno));
#endif
while (NULL != fgets( buf1, sizeof(buf1), Spool_fh_local ))
{
p = buf1;
while (*p && (0 == isspace (*p))) p++;
if (atol(p) >= threshold) /* or just > ? !HACK! */
{
fseek( Spool_fh_local, fpos, SEEK_SET );
break;
}
fpos = ftell(Spool_fh_local);
}
return OK_NEWGROUPS; /* New newsgroups follow */
}
typedef struct
{
char *name;
unsigned int len;
int (*f) (char *, char *, unsigned int);
}
Spool_NNTP_Map_Type;
static Spool_NNTP_Map_Type Spool_NNTP_Maps [] =
{
{"NEXT", 4, spool_nntp_next},
{"HEAD", 4, spool_nntp_head},
{"NEWGROUPS", 9, spool_nntp_newgroups},
{NULL, 0, NULL}
};
static int spool_put_server_cmd (char *line, char *buf, unsigned int len)
{
Spool_NNTP_Map_Type *nntpmap;
#if DEBUG_SPOOL
spool_debug ("spool_put_server_cmd('%s')", line);
#endif
nntpmap = Spool_NNTP_Maps;
while (nntpmap->name != NULL)
{
if (!slrn_case_strncmp ((unsigned char *)nntpmap->name,
(unsigned char *) line, nntpmap->len))
return (*nntpmap->f)(line, buf, len);
nntpmap++;
}
#if DEBUG_SPOOL
spool_debug ("Hmmm, didn't know about that command");
#endif
return ERR_COMMAND;
}
static int spool_is_name_all_digits (char *p)
{
char *pmax;
pmax = p + strlen (p);
while (p < pmax)
{
if (!isdigit (*p))
return 0;
p++;
}
return 1;
}
static int spool_read_minmax_from_dp (DIR *dp, int *min, int *max)
{
struct dirent *ep;
char *p;
long l;
long hi = 0;
long lo = LONG_MAX;
/* Scan through all the files, checking the ones with numbers for names */
while ((ep = readdir(dp)) != NULL)
{
p = ep->d_name;
#ifdef NEED_D_NAMLEN
p[ep->d_namlen] = 0;
#endif
if (!isdigit(*p)) continue;
if (0 == spool_is_name_all_digits (p))
continue;
if (0 == (l = atol (p)))
continue;
if (l < lo)
lo = l;
if (l > hi)
hi = l;
}
if ((lo == LONG_MAX)
&& (hi == 0))
return -1;
*min=lo;
*max=hi;
return 0;
}
/* Get the lowest and highest article numbers by the simple method
* or looking at the files in the directory.
* Returns 1 on success, 0 on failure
*/
static int spool_read_minmax_from_dir( int *min, int *max )
{
/* This is adapted from some code in INN's ng.c */
DIR *dp;
/* I suspect this is very unlikely to fail */
if ((dp = opendir(".")) == NULL)
return 0;
if (-1 == spool_read_minmax_from_dp (dp, min, max))
{
*min = 1;
*max = 0;
}
(void) closedir(dp);
return 1;
}
#if SPOOL_ACTIVE_FOR_ART_RANGE
/* Get the lowest and highest article numbers from the active file
* Returns 1 on success, 0 on failure
* (failure => active file didn't open, or the group wasn't in it)
*/
static int spool_read_minmax_from_active( char *name, int *min, int *max )
{
char buf[512];
unsigned int len;
spool_fclose_local();
Spool_fh_local = fopen(Slrn_Active_File,"r");
if (Spool_fh_local == NULL) return 0;
len = strlen(name);
buf[len] = 0; /* init this for test below */
while (NULL != fgets (buf, sizeof(buf), Spool_fh_local))
{
/* quick, crude test first to see if it could possibly be a match */
if ((buf[len] == ' ')
&& (0 == memcmp (buf, name, len)))
{
spool_fclose_local ();
if (2 != sscanf (buf + len + 1, "%d%d", max, min))
return 0;
Spool_max_artnum = *max;
# if DEBUG_SPOOL
spool_debug ("from active:%s %d %d",
name,*min,*max);
# endif
return 1;
}
}
spool_fclose_local();
return 0;
}
#endif
/* Get the lowest and highest article numbers from the overview file
* Returns 1 on success, 0 on failure
*/
static int spool_read_minmax_from_overview( char *name, int *min, int *max )
{
/* chunk size to step back through .overview files by
* when trying to find start of last line */
#define SPOOL_NOV_STEP 1024
/* If there's no .overview file, get min/max info from the active file */
/* ditto if .overview file is empty */
int ch;
long fpos;
int found;
int first;
/* !HACK! this assumes the overview file is rewound */
Spool_Server_Obj.sv_has_xover = ((Spool_fh_nov != NULL)
&& (1 == fscanf (Spool_fh_nov,"%d", min)));
if (0 == Spool_Server_Obj.sv_has_xover)
return 0;
/* find start of last line */
fseek (Spool_fh_nov, 0, SEEK_END); /* !HACK! check return value from this and all the rest... */
fpos = ftell (Spool_fh_nov);
#if DEBUG_SPOOL
spool_debug("ftell=%ld errno=%d (%s)",
fpos, errno, strerror(errno));
errno = 0;
#endif
found=0;
first=1;
while (!found && (fpos > 0))
{
int i, len;
len = SPOOL_NOV_STEP;
/* don't run of the start of the file */
if (fpos < (long)len) len = fpos;
fpos -= len;
fseek(Spool_fh_nov, fpos, SEEK_SET);
#if DEBUG_SPOOL
spool_debug("ftell=%ld errno=%d (%s)",
ftell (Spool_fh_nov), errno, strerror(errno));
errno = 0;
#endif
if (first)
{
/* on the first pass, we want to ignore the last byte \n at eof */
--len;
first = 0;
}
for(i = 0; i < len; i++ )
{
ch = getc(Spool_fh_nov);
assert(ch!=EOF); /* shouldn't happen */
if (ch =='\n')
found = i + 1; /* and keep going in case there's another */
}
}
fseek (Spool_fh_nov, fpos + found, SEEK_SET);
#if DEBUG_SPOOL
spool_debug("ftell=%ld errno=%d (%s)",
ftell (Spool_fh_nov), errno, strerror(errno));
errno = 0;
#endif
fscanf (Spool_fh_nov, "%d", max);
#if DEBUG_SPOOL
spool_debug ("%s %d %d",name,*min,*max);
#endif
return 1;
}
int spool_select_group (char *name, int *min, int *max)
{
char *p, *q;
/* close any open files */
spool_fclose_local();
if (Spool_fh_nov)
fclose(Spool_fh_nov);
if (Spool_Group != NULL) SLFREE(Spool_Group);
Spool_Group = spool_dircat (Slrn_Spool_Root, name, 1);
#if DEBUG_SPOOL
spool_debug ("spool_select_group(%s) spool_group dir = %s", name, Spool_Group);
#endif
/* change directory to the spool directory to make opening articles easier */
if (chdir (Spool_Group))
return -1;
p = spool_dircat (Slrn_Nov_Root, name, 1);
q = spool_dircat (p, Slrn_Nov_File, 0);
Spool_fh_nov = fopen(q,"rb");
SLFREE(q);
SLFREE(p);
if (!spool_read_minmax_from_overview( name, min, max )
#if SPOOL_ACTIVE_FOR_ART_RANGE
&& !spool_read_minmax_from_active( name, min, max )
#endif
&& !spool_read_minmax_from_dir( min, max ))
return -1;
Spool_max_artnum = *max;
#if DEBUG_SPOOL
spool_debug ("Group: %s %d - %d", name, *min, *max);
#endif
return 0;
}
static int Spool_Server_Inited = 0;
static void spool_close_server (void)
{
#if DEBUG_SPOOL
spool_debug ("spool_close_server()");
#endif
if (Spool_Group != NULL)
{
SLFREE(Spool_Group);
Spool_Group = NULL;
}
spool_fclose_local();
if (NULL != Spool_fh_nov)
{
fclose (Spool_fh_nov);
Spool_fh_nov = NULL;
}
Spool_Server_Inited = 0;
}
static int spool_has_cmd (char *cmd)
{
return 0; /* deny everything */
}
int spool_initialize_server (void)
{
int tt_inited = (Slrn_TT_Initialized & 1);
if (Spool_Server_Inited) spool_close_server ();
#if DEBUG_SPOOL
spool_debug ("spool_initialize_server(%s)", host);
#endif
if (2 != slrn_file_exists (Slrn_Spool_Root))
{
if (tt_inited == 0)
slrn_tty_message (2, "Local spool directory '%s' doesn't exist", Slrn_Spool_Root);
else
{
slrn_message ("Local spool directory '%s' doesn't exist", Slrn_Spool_Root);
slrn_smg_refresh ();
}
return -1;
}
/* I think it's better to think that the *server* has XOVER, but
* some (or all) groups may not.
* So set this to 1 here, and then to 0 or 1 in spool_select_group if we
* find an overview file
*/
Spool_Server_Obj.sv_has_xover = 1;
Spool_Server_Inited = 1;
return 0;
}
static char *spool_read_line (char *line, unsigned int len)
{
if (Spool_fFakingActive) return spool_fakeactive_read_line (line, len);
if (!Spool_fh_local || fgets( line, len, Spool_fh_local)==NULL || (Spool_fhead && line[0]=='\n'))
{
spool_fclose_local();
return NULL;
}
len=strlen(line);
if (len && line[len-1]=='\n')
line[len-1]='\0';
/* client_get_server() just stops reading when it takes notice of len */
#if 0
else
{
/* skip rest of line which wouldn't fit */
while (((ch = getc(Spool_fh_local)) != '\n')
&& (ch != EOF))
; /* do nothing */
}
#endif
return line;
}
int spool_xpat_cmd (char *hdr, int rmin, int rmax, char *pat)
{
return -1;
}
int spool_xgtitle_cmd (char *pattern)
{
return -1;
}
int spool_select_article (int n, char *msgid)
{
char num[20];
/* printf("spool_select_article(%d,%s)\n",n,msgid); */
if (n == -1)
{
n = spool_find_artnum_from_msgid(msgid);
if (n == -1)
return -1;
}
Spool_cur_artnum = n;
sprintf (num,"%d",n);
spool_fclose_local();
Spool_fh_local = fopen (num,"rb"); /* fopen the article in question */
if (Spool_fh_local == NULL)
return -1;
return 0;
}
static int Spool_XOver_Done;
static int Spool_XOver_Min;
static int Spool_XOver_Max;
static int Spool_XOver_Next;
static int Spool_Suspend_Xover_For_Kill = 0;
int spool_open_xover (int min, int max)
{
int i, ch;
long fp;
#if DEBUG_SPOOL
spool_debug ("spool_open_xover(%d,%d)", min, max);
#endif
spool_fclose_local();
if (Spool_Server_Obj.sv_has_xover && !Spool_Suspend_Xover_For_Kill)
{
Spool_fh_local = Spool_fh_nov;
if (Spool_fh_local == NULL)
return -1;
/* find first record in range in overview file */
/* first look at the current position and see where we are */
/* this is worth trying as slrn will often read a series of ranges */
fp = ftell( Spool_fh_local );
if ((1 != fscanf(Spool_fh_local,"%d", &i))
|| (i > min))
{
/* looks like we're after the start of the range */
/* therefore we'll have to rescan the file from the start */
rewind (Spool_fh_local);
i = -1;
/* this might be improved by doing some binary-chop style searching */
}
else
{
while (((ch = getc(Spool_fh_local)) != '\n')
&& (ch != EOF))
; /* do nothing */
}
#if DEBUG_SPOOL
spool_debug ("Starting with i=%d",i);
#endif
while (i < min)
{
fp = ftell( Spool_fh_local );
if (1 != fscanf(Spool_fh_local,"%d", &i))
return -1;
while (((ch = getc (Spool_fh_local)) != '\n')
&& (ch != EOF))
; /* do nothing */
}
fseek (Spool_fh_local, fp, SEEK_SET); /* reset to start of line */
}
Spool_XOver_Next = Spool_XOver_Min = min;
Spool_XOver_Max = max;
Spool_XOver_Done = 0;
return 0;
}
char *spool_read_xover (char *the_buf, unsigned int len)
{
char buf [NNTP_MAX_XOVER_SIZE];
int id;
int already_tried = 0;
#if DEBUG_SPOOL
spool_debug ("spool_read_xover");
#endif
if (Spool_Server_Obj.sv_has_xover && !Spool_Suspend_Xover_For_Kill)
{
char *p;
p = spool_read_line (the_buf, len);
/*#ifdef LOCAL*/
/* check if we've reached the end of the requested range */
if ((p != NULL)
&& (atoi(p) > Spool_XOver_Max))
p = NULL;
#if DEBUG_SPOOL
spool_debug (p);
#endif
/*#endif*/
return p;
}
if (Spool_XOver_Next > Spool_XOver_Max) return NULL;
while (Spool_XOver_Done == 0)
{
sprintf (buf, "HEAD %d", Spool_XOver_Next);
if (OK_HEAD != spool_put_server_cmd (buf, buf, sizeof(buf)))
{
already_tried = 0;
/* This is ugly */
/* If a head fails even though server says it has article, jump
* back and go on.
*/
server_is_messed_up_head_failed:
do
{
switch (spool_put_server_cmd ("NEXT", buf, sizeof (buf)))
{
case OK_NOTEXT:
/* OK, got next article */
break;
case ERR_NONEXT:
Spool_XOver_Done = 1;
return NULL;
default:
slrn_exit_error ("Server failed NEXT request.");
}
id = atoi (buf + 4);
/* Try going back 5 articles */
if ((already_tried == 0) && (id < Spool_XOver_Next - 5))
{
sprintf (buf, "HEAD %d", Spool_XOver_Next - 5);
if (OK_HEAD == spool_put_server_cmd (buf, buf, sizeof(buf)))
{
/* read all header */
spool_discard_output(buf, sizeof(buf));
}
already_tried = 1;
}
}
while (id < Spool_XOver_Next);
if (OK_HEAD != spool_put_server_cmd ("HEAD", buf, sizeof(buf)))
{
sprintf (buf, "HEAD %d", id);
if (OK_HEAD != spool_put_server_cmd (buf, buf, sizeof(buf)))
{
already_tried++;
if (already_tried > 10)
{
slrn_exit_error ("Server Error: Head failed.");
}
goto server_is_messed_up_head_failed;
}
}
}
/* extract article number */
id = atoi(buf + 4);
if (id >= Spool_XOver_Min) break;
/* we just did a head. Read it and do next until range is ok */
spool_discard_output(buf, sizeof (buf));
while (id < Spool_XOver_Min)
{
if (OK_NOTEXT == spool_put_server_cmd ("NEXT", buf, sizeof (buf)))
{
id = atoi(buf + 4);
continue;
}
Spool_XOver_Done = 1;
break;
}
}
if (Spool_XOver_Done) return NULL;
if (id > Spool_XOver_Max)
{
/* clear out header text */
Spool_XOver_Done = 1;
spool_discard_output(buf, sizeof (buf));
return NULL;
}
spool_read_create_xover_line (id, the_buf);
if (id > Spool_XOver_Max)
{
Spool_XOver_Done = 1;
return NULL;
}
if (OK_NOTEXT == spool_put_server_cmd ("NEXT", buf, sizeof (buf)))
{
Spool_XOver_Next = atoi(buf + 4);
if (Spool_XOver_Next > Spool_XOver_Max)
{
Spool_XOver_Done = 1;
}
}
else Spool_XOver_Done = 1;
return the_buf;
}
void spool_close_xover ()
{
#if DEBUG_SPOOL
spool_debug ("spool_close_xover");
#endif
Spool_XOver_Done = 1;
}
char *spool_head_from_msgid (char *msgid, char *buf, unsigned int len)
{
int id;
if ((msgid == NULL) || (*msgid == 0)) return NULL;
id=spool_find_artnum_from_msgid(msgid);
sprintf(buf,"%d",id); /* !HACK! brief buf abuse */
Spool_fh_local=fopen(buf,"r");
if (!Spool_fh_local)
return NULL;
Spool_fhead=1; /* set flag to stop after headers */
return spool_read_create_xover_line (id, buf);
}
/* just hacked from the nntp_ version -- could be better coded */
int spool_xhdr_command (char *hdr, int num, char *buf, unsigned int buflen)
{
char cmd[256], tmpbuf[1024];
int found;
unsigned int colon;
sprintf (cmd, "HEAD %d", num);
if (OK_HEAD != spool_put_server_cmd (cmd, buf, buflen))
return -1;
found = 0;
colon = strlen (hdr);
while (NULL != spool_read_line (tmpbuf, sizeof (tmpbuf)))
{
char *b;
if (found
|| slrn_case_strncmp ((unsigned char *) tmpbuf, (unsigned char *) hdr, colon)
|| (tmpbuf[colon] != ':'))
continue;
found = 1;
b = tmpbuf + colon;
if (*b == ' ') b++;
strncpy (buf, b, buflen - 1);
buf[buflen - 1] = 0;
}
return 0;
}
int spool_list_newsgroups (void)
{
spool_fclose_local();
Spool_fh_local=fopen(Slrn_Newsgroups_File,"r");
if (!Spool_fh_local)
{
/* Use readdir() to return a list of newsgroups read from the
* "newsspool" so we can read MH folders, etc. This would be more
* than a little slow for a true newsspool.
*/
spool_fake_active(Slrn_Spool_Root);
Spool_fFakingActive=1;
Spool_fakeactive_newsgroups=1;
return 0;
/* slrn_exit_error("Couldn't open newsgroups file '%s'", NEWSGROUPS); */
}
return 0;
}
int spool_list_active (void)
{
spool_fclose_local();
Spool_fh_local=fopen (Slrn_Active_File,"r");
if (!Spool_fh_local)
{
spool_fake_active(Slrn_Spool_Root);
Spool_fFakingActive=1;
Spool_fakeactive_newsgroups=0;
return 0;
/* Use readdir() to return a list of newsgroups and article ranges read
* from the "newsspool" so we can read MH folders, etc. This would
* be more than a little slow for a true newsspool.
*/
/* slrn_exit_error("Couldn't open active file '%s'", ACTIVE);*/
}
return 0;
}
typedef struct _Spool_DirTree_Type
{
struct _Spool_DirTree_Type *parent;
DIR *dp;
int len;
long lo, hi;
}
Spool_DirTree_Type;
static Spool_DirTree_Type *Spool_Head;
static char Spool_Buf[256];
static char Spool_nBuf[256];
static int Spool_Is_LeafDir;
static void spool_fake_active_in (char *dir)
{
char *p;
DIR *dp;
Spool_DirTree_Type *tmp;
p = Spool_Buf + strlen(Spool_Buf);
if (dir != NULL)
{
*p = '/';
strcpy (p + 1, dir);
}
if ((2 != slrn_file_exists (Spool_Buf))
|| (NULL == (dp = opendir (Spool_Buf))))
{
*p = 0;
return;
}
Spool_Is_LeafDir = 1;
tmp = (Spool_DirTree_Type *) SLMALLOC (sizeof(Spool_DirTree_Type));
if (tmp == NULL)
{
slrn_exit_error ("Out of memory.");
}
tmp->dp = dp;
tmp->parent = Spool_Head;
tmp->hi = 0;
tmp->lo = LONG_MAX;
if (dir == NULL)
tmp->len = 1;
else
{
tmp->len = strlen (dir);
p = Spool_nBuf + strlen(Spool_nBuf);
if (p != Spool_nBuf) *p++ = '.';
strcpy (p, dir);
}
Spool_Head = tmp;
}
static void spool_fake_active_out (void)
{
Spool_DirTree_Type *tmp;
int i;
(void)closedir (Spool_Head->dp);
Spool_Is_LeafDir = 0;
Spool_Buf [strlen(Spool_Buf) - Spool_Head->len - 1] = '\0';
i = strlen(Spool_nBuf) - Spool_Head->len - 1;
if (i < 0) i = 0;
Spool_nBuf[i]='\0';
tmp = Spool_Head;
Spool_Head = Spool_Head->parent;
SLFREE(tmp);
}
static int spool_fake_active (char *path)
{
strcpy(Spool_Buf, path);
*Spool_nBuf='\0';
Spool_Head=NULL;
spool_fake_active_in (NULL);
return 0;
}
static char *spool_fakeactive_read_line(char *line, int len)
{
struct dirent *ep;
char *p;
long l;
emptydir:
if (!Spool_Head)
{
/* we've reached the end of the road */
Spool_fFakingActive = 0;
return NULL;
}
/* Scan through all the files, checking the ones with numbers for names */
while ((ep = readdir(Spool_Head->dp)) != NULL)
{
p = ep->d_name;
#ifdef NEED_D_NAMLEN
p[ep->d_namlen] = 0;
#endif
if ((0 == spool_is_name_all_digits (p))
|| ((l = atol (p)) == 0))
{
if (!(p[0]=='.' && (p[1]=='\0' || (p[1]=='.' && p[2]=='\0'))))
{
spool_fake_active_in(p);
}
continue;
}
if (l < Spool_Head->lo)
Spool_Head->lo = l;
if (l > Spool_Head->hi)
Spool_Head->hi = l;
}
if (Spool_Head->lo == LONG_MAX && Spool_Head->hi==0)
{
/* assume all leaf directories are valid groups */
/* non-leaf directories aren't groups unless they have articles in */
if (!Spool_Is_LeafDir)
{
spool_fake_active_out();
goto emptydir; /* skip empty "groups" */
}
Spool_Head->lo = 1;
}
if (Spool_fakeactive_newsgroups)
{
/* newsgroups: alt.foo A group about foo */
sprintf(line,"%s ?\n",Spool_nBuf); /* !HACK! check len */
}
else
{
/* active: alt.guitar 0000055382 0000055345 y */
sprintf(line,"%s %ld %ld y\n",Spool_nBuf,Spool_Head->hi,Spool_Head->lo); /* !HACK! check len */
}
spool_fake_active_out();
return line;
}
/* exactly the same as the nntp.c versions, apart from nntp_ -> spool_ */
/* RFC850: The required headers are
* Relay-Version, Posting-Version, From, Date, Newsgroups,
* Subject, Message-ID, Path. */
#define MAX_XOVER_SIZE 1024
typedef struct
{
char name[25];
unsigned int colon;
char buf[MAX_XOVER_SIZE + 16];
}
Spool_Header_Xover_Type;
/* These MUST be arranged in XOver format:
* id|subj|from|date|msgid|refs|bytes|line|misc stuff
*
* !!!DO NOT REARRANGE OR ADD ANYTHING WITHOUT UPDATING THE ARRAY REFERENCES!!!
*/
#define SPOOL_NUM_XOVER_HEADERS 7
#if SLRN_HAS_NNTPREAD
extern Spool_Header_Xover_Type Spool_Xover_Headers[]; /* defined in nntp.c */
#else
Spool_Header_Xover_Type Spool_Xover_Headers[SPOOL_NUM_XOVER_HEADERS] =
{
{"Subject:", 8, ""},
{"From:", 5, ""},
{"Date:", 5, ""},
{"Message-ID:", 11, ""},
{"References:", 11, ""},
/* {"Bytes:", 6, ""}, */
{"Lines:", 6, ""},
{"Xref:", 5, ""}
};
#endif
/* The rest of these are for scoring/killing */
#define MAX_EXTRA_XOVER_HEADERS 20
Spool_Header_Xover_Type *Spool_Extra_Xover_Headers[MAX_EXTRA_XOVER_HEADERS];
static unsigned int Spool_Next_Extra_Xover_Header;
void spool_open_suspend_xover (void)
{
Spool_Suspend_Xover_For_Kill = 1;
Spool_Next_Extra_Xover_Header = 0;
}
void spool_close_suspend_xover (void)
{
unsigned int i;
if (Spool_Suspend_Xover_For_Kill == 0) return;
Spool_Suspend_Xover_For_Kill = 0;
for (i = 0; i < MAX_EXTRA_XOVER_HEADERS; i++)
{
if (Spool_Extra_Xover_Headers[i] != NULL)
SLFREE (Spool_Extra_Xover_Headers[i]);
Spool_Extra_Xover_Headers[i] = NULL;
}
Spool_Next_Extra_Xover_Header = 0;
}
char *spool_get_extra_xover_header (char *hdr)
{
unsigned int i;
unsigned int colon = strlen (hdr) + 1;
for (i = 0; i < Spool_Next_Extra_Xover_Header; i++)
{
Spool_Header_Xover_Type *xov = Spool_Extra_Xover_Headers[i];
if ((colon == xov->colon)
&& (!slrn_case_strcmp ((unsigned char *)xov->name,
(unsigned char *)hdr)))
return xov->buf;
}
return NULL;
}
static char *spool_read_create_xover_line (int id, char *the_buf)
{
char *hbuf, *b;
char buf[8 * MAX_XOVER_SIZE];
unsigned int colon = 0;
char *p, ch;
unsigned int group_len;
int i, headers_not_found;
Spool_Header_Xover_Type *xov = NULL;
for (i = 0; i < SPOOL_NUM_XOVER_HEADERS; i++)
{
*Spool_Xover_Headers[i].buf = 0;
}
Spool_Next_Extra_Xover_Header = 0;
headers_not_found = SPOOL_NUM_XOVER_HEADERS;
hbuf = NULL;
while (NULL != spool_read_line (buf, sizeof(buf)))
{
unsigned int maxlen;
ch = *buf;
if ((ch == ' ') || (ch == '\t'))
{
if (hbuf == NULL) continue;
if (hbuf - xov->buf >= MAX_XOVER_SIZE - 2)
{
hbuf = NULL;
continue;
}
*hbuf++ = ' '; /* one blank to separate */
*hbuf = 0;
colon = 0;
/* Drop to add continuation */
}
else
{
if (headers_not_found) i = 0;
else i = SPOOL_NUM_XOVER_HEADERS;
while (i < SPOOL_NUM_XOVER_HEADERS)
{
if ((ch == *Spool_Xover_Headers[i].name)
&& !slrn_case_strncmp ((unsigned char *)Spool_Xover_Headers[i].name,
(unsigned char *)buf, Spool_Xover_Headers[i].colon))
{
headers_not_found--;
xov = Spool_Xover_Headers + i;
colon = xov->colon;
hbuf = xov->buf;
break;
}
i++;
}
if (i == SPOOL_NUM_XOVER_HEADERS)
{
if ((!Spool_Suspend_Xover_For_Kill)
|| (Spool_Next_Extra_Xover_Header == MAX_EXTRA_XOVER_HEADERS))
{
hbuf = NULL;
continue;
}
xov = Spool_Extra_Xover_Headers[Spool_Next_Extra_Xover_Header];
if (xov == NULL)
{
xov = (Spool_Header_Xover_Type *) SLMALLOC (sizeof(Spool_Header_Xover_Type));
if (xov == NULL)
{
hbuf = NULL;
continue;
}
Spool_Extra_Xover_Headers[Spool_Next_Extra_Xover_Header] = xov;
}
b = buf;
while (*b && (*b != ':')) b++;
if (*b != ':')
{
hbuf = NULL;
continue;
}
*b++ = 0;
colon = (b - buf);
strncpy (xov->name, buf, sizeof (xov->name) - 1);
xov->name[sizeof(xov->name) - 1] = 0;
xov->colon = colon;
Spool_Next_Extra_Xover_Header += 1;
hbuf = xov->buf;
}
}
/* trim trailing whitespace */
(void) slrn_trim_string (buf);
/* trim leading whitespace */
b = slrn_skip_whitespace (buf + colon);
maxlen = (MAX_XOVER_SIZE - (unsigned int)(hbuf - xov->buf)) - 1;
strncpy (hbuf, b, maxlen);
hbuf[maxlen] = 0;
while (*hbuf)
{
if (*hbuf == '\t') *hbuf = ' ';
hbuf++;
}
}
/* we should try to extract the id from the Xref header if possible */
if (id == -1)
{
char *xref = Spool_Xover_Headers[6].buf;
if (*xref != 0)
{
group_len = strlen (Slrn_Current_Group_Name);
p = xref;
while ((ch = *p) != 0)
{
if (ch == ' ')
{
p++;
continue;
}
if (!strncmp (p, Slrn_Current_Group_Name, group_len))
{
p += group_len;
if (*p == ':')
{
p++;
id = atoi (p);
break;
}
}
/* skip to next space */
while (((ch = *p) != 0) && (ch != ' ')) p++;
}
}
#if 0
if ((id == -1) && PROBE_XCMD(Has_XPat, "XPAT"))
{
char xpatbuf[256];
char *msgid = Spool_Xover_Headers[3].buf;
if (-1 != spool_xpat_cmd ("Message-Id", Slrn_Server_Min, Slrn_Server_Max, msgid))
{
/* This should only loop once. */
while (spool_read_line (xpatbuf, sizeof (xpatbuf) - 1) != NULL)
{
id = atoi (xpatbuf);
}
}
}
#endif
}
/* put in standard XOVER format:
* id|subj|from|date|msgid|refs|bytes|line|misc stuff
*/
sprintf (the_buf, "%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\tXref: %s",
id,
Spool_Xover_Headers[0].buf, /* subject */
Spool_Xover_Headers[1].buf, /* from */
Spool_Xover_Headers[2].buf, /* date */
Spool_Xover_Headers[3].buf, /* msgid */
Spool_Xover_Headers[4].buf, /* references */
"", /* bytes */
Spool_Xover_Headers[5].buf, /* lines */
Spool_Xover_Headers[6].buf /* xref */
);
return the_buf;
}
char *Slrn_Inn_Root;
char *Slrn_Spool_Root;
char *Slrn_Nov_Root;
char *Slrn_Nov_File;
char *Slrn_Active_File;
char *Slrn_ActiveTimes_File;
char *Slrn_Newsgroups_File;
static int spool_init_objects (void)
{
Spool_Server_Obj.sv_select_group = spool_select_group;
Spool_Server_Obj.sv_read_line = spool_read_line;
Spool_Server_Obj.sv_close = spool_close_server;
Spool_Server_Obj.sv_initialize = spool_initialize_server;
Spool_Server_Obj.sv_select_article = spool_select_article;
Spool_Server_Obj.sv_head_from_msgid = spool_head_from_msgid;
Spool_Server_Obj.sv_read_xover = spool_read_xover;
Spool_Server_Obj.sv_open_xover = spool_open_xover;
Spool_Server_Obj.sv_close_xover = spool_close_xover;
Spool_Server_Obj.sv_put_server_cmd = spool_put_server_cmd;
Spool_Server_Obj.sv_xpat_cmd = spool_xpat_cmd;
Spool_Server_Obj.sv_xhdr_command = spool_xhdr_command;
Spool_Server_Obj.sv_get_extra_xover_header = spool_get_extra_xover_header;
Spool_Server_Obj.sv_close_suspend_xover = spool_close_suspend_xover;
Spool_Server_Obj.sv_open_suspend_xover = spool_open_suspend_xover;
Spool_Server_Obj.sv_xgtitle_cmd = spool_xgtitle_cmd;
Spool_Server_Obj.sv_has_xover = 0;
Spool_Server_Obj.sv_has_cmd = spool_has_cmd;
Spool_Server_Obj.sv_list_newsgroups = spool_list_newsgroups;
Spool_Server_Obj.sv_list_active = spool_list_active;
Slrn_Inn_Root = slrn_make_startup_string (SLRN_SPOOL_INNROOT);
Slrn_Spool_Root = slrn_make_startup_string (SLRN_SPOOL_ROOT);
Slrn_Nov_Root = slrn_make_startup_string (SLRN_SPOOL_NOV_ROOT);
Slrn_Nov_File = slrn_make_startup_string (SLRN_SPOOL_NOV_FILE);
Slrn_Active_File = slrn_make_startup_string (SLRN_SPOOL_ACTIVE);
Slrn_ActiveTimes_File = slrn_make_startup_string (SLRN_SPOOL_ACTIVETIMES);
Slrn_Newsgroups_File = slrn_make_startup_string (SLRN_SPOOL_NEWSGROUPS);
return 0;
}
/* This function is used below. It has a very specific purpose. */
static char *spool_root_dircat (char *file)
{
char *f;
if (*file == '/') return file;
f = spool_dircat (Slrn_Inn_Root, file, 0);
SLFREE (file);
return f;
}
static int spool_select_server_object (void)
{
Slrn_Server_Obj = &Spool_Server_Obj;
Slrn_Active_File = spool_root_dircat (Slrn_Active_File);
Slrn_ActiveTimes_File = spool_root_dircat (Slrn_ActiveTimes_File);
Slrn_Newsgroups_File = spool_root_dircat (Slrn_Newsgroups_File);
if (Spool_Server_Obj.sv_name != NULL)
SLFREE (Spool_Server_Obj.sv_name);
Spool_Server_Obj.sv_name = slrn_make_startup_string (Slrn_Spool_Root);
return 0;
}
static void spool_usage (void)
{
fputs ("--spool options:\n\
",
stdout);
exit (0);
}
static int spool_parse_args (char **argv, int argc)
{
int i;
for (i = 0; i < argc; i++)
{
if (!strcmp (argv[i], "--help"))
spool_usage ();
else break;
}
return i;
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.