1 /* -*- mode: C; mode: fold; -*- */
2 /* Local spool support for slrn added by Olly Betts <olly@mantis.co.uk> */
3 /* Modified by Thomas Schultz:
4  * Copyright (c) 2001-2006 Thomas Schultz <tststs@gmx.de>
5  */
6 #include "config.h"
7 #include "slrnfeat.h"
8 
9 /* define this if you want to trace command-execution in spool.c.
10  * Normally, you only get errors in the debug-file. If you set this,
11  * please keep an eye on your disk-space.
12  */
13 #define DEBUG_SPOOL_TRACE 1
14 
15 #include <stdio.h>
16 #ifndef SEEK_SET
17 # define SEEK_SET	0
18 #endif
19 
20 #ifdef HAVE_STDLIB_H
21 # include <stdlib.h>
22 #endif
23 
24 #include <string.h>
25 #include <errno.h>
26 #include <ctype.h>
27 
28 #ifdef HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 
32 #include <slang.h>
33 #include "jdmacros.h"
34 
35 #include <time.h>
36 
37 #include "misc.h"
38 #include "util.h"
39 #include "slrn.h"
40 #include "slrndir.h"
41 #include "snprintf.h"
42 #include "vfile.h"
43 #include "strutil.h"
44 
45 #ifndef SLRN_SPOOL_ROOT
46 # define SLRN_SPOOL_ROOT "/var/spool/news" /* a common place for the newsspool */
47 #endif
48 
49 #ifndef SLRN_SPOOL_NOV_ROOT
50 # define SLRN_SPOOL_NOV_ROOT SLRN_SPOOL_ROOT
51 #endif
52 
53 #ifndef SLRN_SPOOL_NOV_FILE
54 # define SLRN_SPOOL_NOV_FILE ".overview"
55 #endif
56 
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <assert.h>
60 #include <stdarg.h>
61 
62 #include <limits.h>
63 
64 extern int Slrn_Prefer_Head;
65 char *Slrn_Overviewfmt_File;
66 char *Slrn_Requests_File;
67 
68 static int spool_put_server_cmd (char *, char *, unsigned int );
69 static int spool_select_group (char *, NNTP_Artnum_Type *, NNTP_Artnum_Type *);
70 static int spool_refresh_groups (Slrn_Group_Range_Type *, int);
71 static void spool_close_server (void);
72 static int spool_has_cmd (char *);
73 static int spool_initialize_server (void);
74 static int spool_read_line (char *, unsigned int );
75 static int spool_xpat_cmd (char *, NNTP_Artnum_Type, NNTP_Artnum_Type, char *);
76 static int spool_select_article (NNTP_Artnum_Type, char *);
77 static int spool_get_article_size (NNTP_Artnum_Type);
78 static int spool_one_xhdr_command (char *, NNTP_Artnum_Type, char *, unsigned int);
79 static int spool_xhdr_command (char *, NNTP_Artnum_Type, NNTP_Artnum_Type);
80 static int spool_list (char *);
81 static int spool_list_newsgroups (void);
82 static int spool_list_active (char *);
83 static int spool_send_authinfo (void);
84 static int spool_read_xover (char *, unsigned int);
85 static int spool_read_xpat (char *, unsigned int);
86 static int spool_read_xhdr (char *, unsigned int);
87 static int spool_article_num_exists (NNTP_Artnum_Type);
88 static unsigned int spool_get_bytes (int clear);
89 
90 static Slrn_Server_Obj_Type Spool_Server_Obj;
91 
92 /* some state that the NNTP server would take care of if we were using one */
93 static FILE *Spool_fh_local=NULL;
94 static int Spool_Ignore_Comments=0;	/* ignore comments while reading */
95 static SLRegexp_Type *Spool_Output_Regexp=NULL;
96 static char *Spool_Group=NULL;
97 static char *Spool_Group_Name;
98 
99 static FILE *Spool_fh_nov=NULL; /* we use the overview file lots, so keep it open */
100 static NNTP_Artnum_Type Spool_cur_artnum = 0;
101 
102 /* These are set when the group is selected. */
103 static NNTP_Artnum_Type Spool_Max_Artnum = 0;
104 static NNTP_Artnum_Type Spool_Min_Artnum = 0;
105 
106 static int Spool_Doing_XOver;	       /* if non-zero, reading from ,overview */
107 static int Spool_Doing_XPat;	       /* reading xpat */
108 static char *Spool_XHdr_Field;	       /* when reading xhdr */
109 static int Spool_fhead=0; /* if non-0 we're emulating "HEAD" so stop on blank line */
110 static int Spool_fFakingActive=0; /* if non-0 we're doing funky stuff with MH folders */
111 
112 static int spool_fake_active( char *);
113 static int spool_fakeactive_read_line(char *, int);
114 static int Spool_fakeactive_newsgroups=0;
115 
116 static unsigned int bytes_read=0;
117 
118 /* Note: Please leave debugging output untranslated.
119  *       It cannot be interpreted by the end user anyway. */
120 static void debug_output (char *file, int line, char *fmt, ...) ATTRIBUTE_PRINTF(3,4);
121 
debug_output(char * file,int line,char * fmt,...)122 static void debug_output (char *file, int line, char *fmt, ...)
123 {
124    va_list ap;
125    if (Slrn_Debug_Fp != NULL)
126      {
127         if ((file != NULL) && (line != -1))
128           fprintf (Slrn_Debug_Fp, "%s:%d: ", file, line);
129         else
130 #ifdef DEBUG_SPOOL_TRACE
131           fprintf (Slrn_Debug_Fp, "%lu: ", (unsigned long) clock ());
132 #else
133 	  return;
134 #endif
135 
136         if (fmt == NULL)
137           {
138              fputs ("(NULL)", Slrn_Debug_Fp);
139           }
140         else
141           {
142 	     va_start(ap, fmt);
143 	     vfprintf(Slrn_Debug_Fp, fmt, ap);
144 	     va_end (ap);
145           }
146 
147         putc ('\n', Slrn_Debug_Fp);
148      }
149 }
150 #ifndef __FILE__
151 # define __FILE__ "spool.c"
152 #endif
153 #ifndef __LINE__
154 # define __LINE__ 0
155 #endif
156 
157 /* close any current file (unless it's the overview file) and NULL the FILE* */
spool_fclose_local(void)158 static int spool_fclose_local (void)
159 {
160    int res = 0;
161 
162    Spool_fhead=0;
163    Spool_fFakingActive=0;
164 #if SLANG_VERSION >= 20000
165    if (Spool_Output_Regexp != NULL)
166      SLregexp_free (Spool_Output_Regexp);
167 #endif
168    Spool_Output_Regexp = NULL;
169    Spool_Ignore_Comments=0;
170 
171    if (Spool_fh_local != NULL)
172      {
173 	if (Spool_fh_local != Spool_fh_nov)
174 	  res = fclose(Spool_fh_local);
175 	Spool_fh_local=NULL;
176      }
177    return res;
178 }
179 
spool_open_nov_file(void)180 static FILE *spool_open_nov_file (void)
181 {
182    char *p, *q;
183    FILE *fp;
184 
185    /* The spool_dircat function will exit if it fails to malloc. */
186    p = slrn_spool_dircat (Slrn_Nov_Root, Spool_Group_Name, 1);
187    q = slrn_spool_dircat (p, Slrn_Nov_File, 0);
188 
189    fp = fopen (q,"rb");
190    SLFREE(q);
191    SLFREE(p);
192 
193    return fp;
194 }
195 
overview_file_seek(long fp,NNTP_Artnum_Type cur,NNTP_Artnum_Type dest)196 static int overview_file_seek (long fp, NNTP_Artnum_Type cur, NNTP_Artnum_Type dest)
197 {
198    int ch;
199 
200    while (cur < dest)
201      {
202         if (-1 == (fp = ftell(Spool_fh_local)))
203           {
204              debug_output (__FILE__, __LINE__, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
205              return -1;
206           }
207 	if (1 != fscanf (Spool_fh_local, NNTP_FMT_ARTNUM, &cur))
208 	  {
209 	     spool_fclose_local ();
210 	     return -1;
211 	  }
212 
213 	while (((ch = getc (Spool_fh_local)) != '\n') && (ch != EOF))
214 	  ; /* do nothing */
215      }
216    if (-1 == fseek(Spool_fh_local, fp, SEEK_SET)) /* reset to start of line */
217      {
218         debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
219         return ERR_FAULT;
220      }
221 
222    return 0;
223 }
224 
225 static NNTP_Artnum_Type Spool_XOver_Next;
226 static NNTP_Artnum_Type Spool_XOver_Max;
227 static NNTP_Artnum_Type Spool_XOver_Min;
228 
spool_nntp_xover(NNTP_Artnum_Type min,NNTP_Artnum_Type max)229 static int spool_nntp_xover (NNTP_Artnum_Type min, NNTP_Artnum_Type max)
230 {
231    NNTP_Artnum_Type i;
232    long fp;
233 
234    Spool_Doing_XOver = 0;
235 
236    spool_fclose_local ();
237 
238    if (max > Spool_Max_Artnum)
239      max = Spool_Max_Artnum;
240 
241    if (min < Spool_Min_Artnum)
242      min = Spool_Min_Artnum;
243 
244    if (Spool_Server_Obj.sv_has_xover)
245      {
246 	Spool_fh_local = Spool_fh_nov;
247 	if (Spool_fh_local == NULL)
248 	  return -1;
249 
250 	/* find first record in range in overview file */
251 	/* first look at the current position and see where we are */
252 	/* this is worth trying as slrn will often read a series of ranges */
253 	if (-1 == (fp = ftell (Spool_fh_local)))
254           {
255              debug_output (__FILE__, __LINE__, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
256              return ERR_FAULT;
257           }
258 
259 	if ((1 != fscanf (Spool_fh_local, NNTP_FMT_ARTNUM, &i))
260 	    || (i > min))
261 	  {
262 	     /* looks like we're after the start of the range */
263 	     /* therefore we'll have to rescan the file from the start */
264 	     rewind (Spool_fh_local);
265 	     i = -1;
266 	     /* this might be improved by doing some binary-chop style searching */
267 	  }
268 	else
269 	  {
270 	     int ch;
271 
272 	     while (((ch = getc(Spool_fh_local)) != '\n')
273 		    && (ch != EOF))
274 	       ; /* do nothing */
275 
276 	     if (ch == EOF)
277 	       {
278 		  rewind (Spool_fh_local);
279 		  i = -1;
280 	       }
281 	  }
282 
283 	if (-1 == overview_file_seek (fp, i, min))
284 	  return -1;
285      }
286 
287    Spool_XOver_Next = Spool_XOver_Min = min;
288    Spool_XOver_Max = max;
289    Spool_Doing_XOver = 1;
290 
291    return OK_XOVER;
292 }
293 
spool_read_xover(char * the_buf,unsigned int len)294 static int spool_read_xover (char *the_buf, unsigned int len)
295 {
296    long pos;
297 
298    if (Spool_Doing_XOver == 0)
299      return 0;
300 
301    if (Spool_XOver_Next > Spool_XOver_Max)
302      {
303 	Spool_Doing_XOver = 0;
304 	return 0;
305      }
306 
307    while (1)
308      {
309 	unsigned int buflen;
310 
311 	if (-1 == (pos = ftell (Spool_fh_nov)))
312           {
313              debug_output (__FILE__, __LINE__, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
314              return -1;
315           }
316 
317 	if (NULL == fgets (the_buf, len, Spool_fh_nov))
318 	  {
319 	     Spool_Doing_XOver = 0;
320 	     return 0;
321 	  }
322 
323 	buflen = strlen (the_buf);
324 	if (buflen && (the_buf[buflen - 1] == '\n'))
325 	  the_buf [buflen - 1] = 0;
326 
327 	/* check if we've reached the end of the requested range */
328 	Spool_XOver_Next = NNTP_STR_TO_ARTNUM (the_buf);
329 	if (Spool_XOver_Next > Spool_XOver_Max)
330 	  {
331              if (-1 == fseek(Spool_fh_nov, pos, SEEK_SET))
332                {
333                   debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
334                   return -1;
335                }
336 	     Spool_Doing_XOver = 0;
337 	     return 0;
338 	  }
339 
340 #if 0
341 	/* We now do this check in xover.c and get the article size (in bytes)
342 	 * using the same call to stat(). */
343 	if (Slrn_Spool_Check_Up_On_Nov == 0)
344 	  break;
345 
346 	/* check that the article file actually exists */
347 	/* if not, this nov entry is defunct, so ignore it */
348 	if (0 == spool_article_num_exists (Spool_XOver_Next))
349 	  {
350 	     break;
351 	  }
352 	debug_output (NULL, -1, "Nov entry %ld skipped!", Spool_XOver_Next);
353 #else
354 	break;
355 #endif
356      }
357 
358    Spool_XOver_Next++;
359    return 1;
360 }
361 
spool_find_artnum_from_msgid(char * msgid,NNTP_Artnum_Type * idp)362 static int spool_find_artnum_from_msgid (char *msgid, NNTP_Artnum_Type *idp)
363 {
364    char buf [4096];
365    char *p;
366 
367    debug_output (NULL, -1, "spool_find_artnum_from_msgid('%s')", msgid);
368 
369    if (Slrn_Server_Obj->sv_has_xover == 0)
370      {
371 	NNTP_Artnum_Type n;
372 	unsigned int len = strlen (msgid);
373 
374 	for (n = Spool_Min_Artnum; n <= Spool_Max_Artnum; n++)
375 	  {
376 	     if (-1 == spool_one_xhdr_command ("Message-ID", n, buf, sizeof (buf)))
377 	       continue;
378 
379 	     p = slrn_skip_whitespace (buf);
380 	     if (0 == strncmp (p, msgid, len))
381 	       {
382 		  *idp = n;
383 		  return 0;
384 	       }
385 	  }
386 
387 	return -1;
388      }
389 
390    if (OK_XOVER != spool_nntp_xover (1, NNTP_ARTNUM_TYPE_MAX))
391      return -1;
392 
393    while (1 == spool_read_xover (buf, sizeof(buf)))
394      {
395 	char *q;
396 
397 	/* 5th field is message id. */
398 
399 	if (NULL == (p = slrn_strbyte(buf, '\t'))) continue;
400 	if (NULL == (p = slrn_strbyte(p + 1, '\t'))) continue;
401 	if (NULL == (p = slrn_strbyte(p + 1, '\t'))) continue;
402 	if (NULL == (p = slrn_strbyte(p + 1, '\t'))) continue;
403 
404 	p++; /* skip tab */
405 	q = slrn_strbyte(p,'\t');
406 	if (q != NULL) *q='\0';
407 
408 	if (0 == strcmp(msgid, p))
409 	  {
410 	     debug_output (NULL, -1, ("spool_find_artnum_from_msgid() returns " NNTP_FMT_ARTNUM)
411 			   ,NNTP_STR_TO_ARTNUM(buf));
412 	     Spool_Doing_XOver = 0;
413 	     *idp = NNTP_STR_TO_ARTNUM(buf);
414 	     return 0;
415 	  }
416      }
417 
418    debug_output (NULL, -1, "spool_find_artnum_from_msgid() found no match");
419 
420    Spool_Doing_XOver = 0;
421    return -1;
422 }
423 
spool_open_article_num(NNTP_Artnum_Type num)424 static FILE *spool_open_article_num (NNTP_Artnum_Type num)
425 {
426    char buf [SLRN_MAX_PATH_LEN];
427 
428    slrn_snprintf (buf, sizeof (buf), ("%s/" NNTP_FMT_ARTNUM), Spool_Group, num);
429 
430    return fopen (buf,"r");
431 }
432 
spool_article_num_exists(NNTP_Artnum_Type num)433 static int spool_article_num_exists (NNTP_Artnum_Type num)
434 {
435    char buf [SLRN_MAX_PATH_LEN];
436 
437    slrn_snprintf (buf, sizeof (buf), ("%s/" NNTP_FMT_ARTNUM), Spool_Group, num);
438 
439    if (1 == slrn_file_exists (buf))
440      return 0;
441 
442    return -1;
443 }
444 
spool_get_article_size(NNTP_Artnum_Type num)445 static int spool_get_article_size (NNTP_Artnum_Type num)
446 {
447    char buf [SLRN_MAX_PATH_LEN];
448 
449    slrn_snprintf (buf, sizeof (buf), ("%s/" NNTP_FMT_ARTNUM), Spool_Group, num);
450 
451    return slrn_file_size (buf);
452 }
453 
spool_is_name_all_digits(char * p)454 static int spool_is_name_all_digits (char *p)
455 {
456    char *pmax;
457 
458    pmax = p + strlen (p);
459    while (p < pmax)
460      {
461 	if (!isdigit (*p))
462 	  return 0;
463 	p++;
464      }
465    return 1;
466 }
467 
468 /*{{{ The routines in this fold implement the sv_put_server_cmd */
469 
spool_nntp_head(NNTP_Artnum_Type id,char * msgid,NNTP_Artnum_Type * real_id)470 static int spool_nntp_head (NNTP_Artnum_Type id, char *msgid, NNTP_Artnum_Type *real_id)
471 {
472    spool_fclose_local();
473 
474    if (id == -1)
475      {
476 	if (msgid == NULL)
477 	  id = Spool_cur_artnum;
478 	else
479 	  {
480 	     if (-1 == spool_find_artnum_from_msgid (msgid, &id))
481 	       id = -1;
482 	  }
483      }
484 
485    if (real_id != NULL) *real_id = id;
486 
487    if ((id == -1)
488        || (NULL == (Spool_fh_local = spool_open_article_num (id))))
489      return ERR_NOARTIG; /* No such article in this group */
490 
491    Spool_cur_artnum = id;
492    Spool_fhead = 1; /* set flag to stop after headers */
493 
494    return OK_HEAD; /* Head follows */
495 }
496 
spool_nntp_next(NNTP_Artnum_Type * id)497 static int spool_nntp_next (NNTP_Artnum_Type *id)
498 {
499    NNTP_Artnum_Type i;
500 
501    spool_fclose_local();
502 
503    /* !HACK! better to find value from overview file or active file in case the group grows while we're in it? */
504    for (i = Spool_cur_artnum + 1; i <= Spool_Max_Artnum; i++)
505      {
506 	if (-1 != spool_article_num_exists (i))
507 	  {
508 	     Spool_cur_artnum = i;
509 	     if (id != NULL) *id = i;
510 
511 	     debug_output (NULL, -1, ("NEXT found article " NNTP_FMT_ARTNUM), Spool_cur_artnum);
512 
513 	     return OK_NOTEXT; /* No text sent -- stat, next, last */
514 	  }
515      }
516 
517    debug_output (NULL, -1, ("No NEXT -- " NNTP_FMT_ARTNUM " > " NNTP_FMT_ARTNUM),
518 		 Spool_cur_artnum, Spool_Max_Artnum);
519 
520    return ERR_NONEXT; /* No next article in this group */
521 }
522 
spool_nntp_newgroups(char * line,char * buf,unsigned int len)523 static int spool_nntp_newgroups (char *line, char *buf, unsigned int len)
524 {
525    /* expect something like "NEWGROUPS 960328 170939 GMT" GMT is optional */
526    char *p;
527    int Y,M,D,h,m,s;
528    int c;
529    int fGMT;
530    int n;
531    time_t threshold;
532    long fpos;
533    char buf1[512];
534    int ch;
535    int found;
536    int first;
537 
538    (void) buf; (void) len;
539 
540    /* The %n format specification returns the number of charcters parsed up
541     * to that point.  I do not know how portable this is.
542     */
543    c = sscanf(line+9,"%02d%02d%02d %02d%02d%02d%n",&Y,&M,&D,&h,&m,&s,&n);
544    assert(c==6); /* group.c should have sanity checked the line */
545 
546    p = slrn_skip_whitespace(line + 9 + n);
547    fGMT = (0 == strncmp (p, "GMT", 3));
548 
549    /* this !HACK! is good until 2051 -- round at 50 cos the RFC says to */
550    Y += ( Y>50 ? 1900 : 2000 );
551    /* for full version, use this instead:
552     * if (Y<=50) Y+=100;
553     * yr = this_year();
554     * Y += ((yr-51)/100*100);
555     */
556 
557    if (Y < 1970
558        || M < 1 || M > 12
559        || D < 1 || D > 31
560        || h < 0 || h > 24
561        || m < 0 || m > 59
562        || s < 0 || s > 59)
563      return ERR_FAULT;
564 
565    /* Compute the number of days since beginning of year.
566     * Beware: the calculations involving leap years, etc... are naive. */
567    D--; M--;
568    D += 31 * M;
569    if (M > 1)
570      {
571 	D -= (M * 4 + 27)/10;
572 	if ((Y % 4) == 0) D++;
573      }
574 
575    /* add that to number of days since beginning of time */
576    Y -= 1970;
577    D += Y * 365 + Y / 4;
578    if ((Y % 4) == 3) D++;
579 
580    /* Now convert to secs */
581    s += 60L * (m + 60L * (h + 24L * D));
582    threshold = (time_t) s;
583 
584    if (0 == fGMT)
585      {
586 #ifdef HAVE_TIMEZONE
587 	struct tm *t;
588 	(void) localtime (&threshold); /* This call sets timezone */
589 	threshold += timezone;
590 	t = localtime (&threshold);
591 	if (t->tm_isdst) threshold -= (time_t) 3600;
592 #else
593 # ifdef HAVE_TM_GMTOFF
594 	struct tm *t;
595 	time_t tt;
596 
597 	t = localtime (&threshold);
598 	tt = threshold - t->tm_gmtoff;
599 	if (t->tm_isdst) tt += (time_t) 3600;
600 	t = localtime (&tt);
601 	threshold -= t->tm_gmtoff;
602 # endif /* else: we're out of luck */
603 #endif /* NOT HAVE_TIMEZONE */
604      }
605 
606    debug_output (NULL, -1, "threshold for spool_nntp_newsgrups is %lu", (unsigned long) threshold);
607 
608    spool_fclose_local();
609    Spool_fh_local = fopen (Slrn_ActiveTimes_File, "r");
610    if (Spool_fh_local == NULL)
611      {
612 	/* !HACK! at this point, it would be nice to be able to check for
613 	 * recently created directories with readdir() and to return those
614 	 * which would be useful for reading MH folders, etc.  This would
615 	 * be more than a little slow for a true newsspool though.
616 	 *
617 	 * Hmm, looked into this and it seems that you can't use the mtime
618 	 * or ctime to decide when a directory was created.  Hmmm.
619 	 */
620 	debug_output (NULL, -1, "Couldn't open active.times");
621 	return ERR_FAULT; /* Program fault, command not performed */
622      }
623 
624    /* chunk size to step back through active.times by
625     * when checking for new groups */
626    /* 128 should be enough to find the last line in the probably most
627     * common case of no new groups */
628 #define SLRN_SPOOL_ACTIVETIMES_STEP 128
629 
630    /* find start of a line */
631    if (-1 == fseek (Spool_fh_local, 0, SEEK_END ))
632      {
633         debug_output (NULL, -1, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
634         return ERR_FAULT;
635      }
636    if (-1 == (fpos = ftell (Spool_fh_local)))
637      {
638         debug_output (NULL, -1, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
639         return ERR_FAULT;
640      }
641 
642    found=0;
643    first=1;
644 
645    while (!found)
646      {
647 	int i, len1;
648 
649 	len1 = SLRN_SPOOL_ACTIVETIMES_STEP;
650 
651 	if (fpos < (long)len1) len1=fpos; /* don't run of the start of the file */
652 	fpos -= len1;
653         if (-1 == fseek (Spool_fh_local, fpos, SEEK_SET ))
654           {
655              debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
656              return ERR_FAULT;
657           }
658 	if (fpos == 0) break;
659 
660 	if (first)
661 	  {
662 	     /* on the first pass, we want to ignore the last byte \n at eof */
663 	     --len1;
664 	     first=0;
665 	  }
666 
667 	for (i = 0; i < len1; i++)
668 	  {
669 	     ch = getc(Spool_fh_local);
670 
671 	     assert(ch!=EOF); /* shouldn't happen */
672 	     if (ch != '\n') continue;
673 
674 	     while ((i < len1)
675 		    && (NULL != fgets (buf1, sizeof(buf1), Spool_fh_local)))
676 	       {
677 		  i -= strlen(buf1);
678 		  p = buf1;
679 		  while (*p && (0 == isspace (*p)))
680 		    p++;
681 
682 		  if (atol(p) < threshold)/* or <= ? !HACK! */
683 		    {
684 		       found = 1;
685 		       break;
686 		    }
687 	       }
688 	     break;
689 	  }
690      }
691 
692    if (-1 == (fpos = ftell (Spool_fh_local)))
693      {
694         debug_output (NULL, -1, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
695         return ERR_FAULT;
696      }
697 
698    while (NULL != fgets( buf1, sizeof(buf1), Spool_fh_local ))
699      {
700 	p = buf1;
701 	while (*p && (0 == isspace (*p))) p++;
702 
703 	if (atol(p) >= threshold) /* or just > ? !HACK! */
704 	  {
705              if (-1 == fseek (Spool_fh_local, fpos, SEEK_SET ))
706                {
707                   debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
708                   return ERR_FAULT;
709                }
710 	     break;
711 	  }
712 	if (-1 == (fpos = ftell(Spool_fh_local)))
713           {
714              debug_output (__FILE__, __LINE__, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
715              return ERR_FAULT;
716           }
717      }
718 
719    return OK_NEWGROUPS; /* New newsgroups follow */
720 }
721 
722 typedef struct
723 {
724    char *name;
725    unsigned int len;
726    int (*f) (char *, char *, unsigned int);
727 }
728 Spool_NNTP_Map_Type;
729 
730 static Spool_NNTP_Map_Type Spool_NNTP_Maps [] =
731 {
732      {"NEWGROUPS", 9, spool_nntp_newgroups},
733      {NULL, 0, NULL}
734 };
735 
spool_put_server_cmd(char * line,char * buf,unsigned int len)736 static int spool_put_server_cmd (char *line, char *buf, unsigned int len)
737 {
738    Spool_NNTP_Map_Type *nntpmap;
739 
740    debug_output (NULL, -1, "spool_put_server_cmd('%s')", line);
741 
742    nntpmap = Spool_NNTP_Maps;
743    while (nntpmap->name != NULL)
744      {
745 	if (!slrn_case_strncmp (nntpmap->name,
746 				 line, nntpmap->len))
747 	  return (*nntpmap->f)(line, buf, len);
748 
749 	nntpmap++;
750      }
751 
752    debug_output (NULL, -1, "Hmmm, didn't know about that command ('%s')", line);
753    return ERR_COMMAND;
754 }
755 
756 /*}}}*/
757 
spool_read_minmax_from_dp(Slrn_Dir_Type * dp,NNTP_Artnum_Type * min,NNTP_Artnum_Type * max)758 static int spool_read_minmax_from_dp (Slrn_Dir_Type *dp, NNTP_Artnum_Type *min, NNTP_Artnum_Type *max)
759 {
760    Slrn_Dirent_Type *ep;
761    char *p;
762    NNTP_Artnum_Type l;
763    NNTP_Artnum_Type hi = 0;
764    NNTP_Artnum_Type lo = NNTP_ARTNUM_TYPE_MAX;
765 
766    /* Scan through all the files, checking the ones with numbers for names */
767    while ((ep = slrn_read_dir(dp)) != NULL)
768      {
769 	p = ep->name;
770 	if (!isdigit(*p)) continue;
771 
772 	if (0 == spool_is_name_all_digits (p))
773 	  continue;
774 
775 	if (0 == (l = NNTP_STR_TO_ARTNUM(p)))
776 	  continue;
777 
778 	if (l < lo)
779 	  lo = l;
780 	if (l > hi)
781 	  hi = l;
782      }
783 
784    if ((lo == LONG_MAX)
785        && (hi == 0))
786      return -1;
787 
788    *min=lo;
789    *max=hi;
790 
791    return 0;
792 }
793 
spool_read_minmax_file(NNTP_Artnum_Type * min,NNTP_Artnum_Type * max,char * group_dir)794 static int spool_read_minmax_file (NNTP_Artnum_Type *min, NNTP_Artnum_Type *max, char *group_dir)
795 {
796    char *file;
797    FILE *fp;
798    char buf[512];
799    int status;
800 
801    file = slrn_spool_dircat (group_dir, ".minmax", 0);
802    if (file == NULL)
803      return -1;
804 
805    fp = fopen (file, "r");
806    if (fp == NULL)
807      {
808 	SLFREE (file);
809 	return -1;
810      }
811 
812    status = 0;
813    if ((NULL == fgets (buf, sizeof (buf), fp))
814        || (2 != sscanf (buf, NNTP_FMT_ARTNUM_2, min, max)))
815      status = -1;
816 
817    fclose (fp);
818    SLFREE (file);
819 
820    return status;
821 }
822 
823 /* Get the lowest and highest article numbers by the simple method
824  * or looking at the files in the directory.
825  * Returns 0 on success, -1 on failure
826  */
spool_read_minmax_from_dir(NNTP_Artnum_Type * min,NNTP_Artnum_Type * max,char * dir)827 static int spool_read_minmax_from_dir (NNTP_Artnum_Type *min, NNTP_Artnum_Type *max, char *dir)
828 {
829    Slrn_Dir_Type *dp;
830 
831    if (dir == NULL) dir = ".";
832 
833    /* I suspect this is very unlikely to fail */
834    if ((dp = slrn_open_dir (dir)) == NULL)
835      {
836 	slrn_error (_("Unable to open directory %s"), dir);
837 	return -1;
838      }
839 
840    if (-1 == spool_read_minmax_from_dp (dp, min, max))
841      {
842 	if (-1 == spool_read_minmax_file (min, max, dir))
843 	  {
844 	     *min = 1;
845 	     *max = 0;
846 	  }
847      }
848 
849    (void) slrn_close_dir(dp);
850    return 0;
851 }
852 
853 #if SPOOL_ACTIVE_FOR_ART_RANGE
854 /* Get the lowest and highest article numbers from the active file
855  * Returns 0 on success, -1 on failure
856  * (failure => active file didn't open, or the group wasn't in it)
857  */
spool_read_minmax_from_active(char * name,NNTP_Artnum_Type * min,NNTP_Artnum_Type * max)858 static int spool_read_minmax_from_active( char *name, NNTP_Artnum_Type *min, NNTP_Artnum_Type *max )
859 {
860    char buf[512];
861    unsigned int len;
862 
863    spool_fclose_local();
864    Spool_fh_local = fopen(Slrn_Active_File,"r");
865    if (Spool_fh_local == NULL) return -1;
866 
867    len = strlen(name);
868    buf[len] = 0;		       /* init this for test below */
869 
870    while (NULL != fgets (buf, sizeof(buf), Spool_fh_local))
871      {
872 	/* quick, crude test first to see if it could possibly be a match */
873 	if ((buf[len] == ' ')
874 	    && (0 == memcmp (buf, name, len)))
875 	  {
876 	     spool_fclose_local ();
877 	     if (2 != sscanf (buf + len + 1, NNTP_FMT_ARTNUM NNTP_FMT_ARTNUM, max, min))
878 	       return -1;
879 
880 	     Spool_Max_Artnum = *max;
881 	     debug_output (NULL, -1, "from active:%s " NNTP_FMT_ARTNUM_2, name, *min, *max);
882 	     return 0;
883 	  }
884 	buf[len] = 0;
885      }
886    spool_fclose_local();
887 
888    return -1;
889 }
890 #endif
891 
892 /* Get the lowest and highest article numbers from the overview file
893  * Returns 0 on success, -1 on failure
894  */
spool_read_minmax_from_overview(char * name,NNTP_Artnum_Type * min,NNTP_Artnum_Type * max)895 static int spool_read_minmax_from_overview (char *name, NNTP_Artnum_Type *min, NNTP_Artnum_Type *max)
896 {
897    /* chunk size to step back through .overview files by
898     * when trying to find start of last line */
899 #define SPOOL_NOV_STEP 1024
900    /* If there's no .overview file, get min/max info from the active file */
901    /* ditto if .overview file is empty */
902    int ch;
903    long fpos;
904    int found;
905    int first;
906 
907    (void) name;
908 
909    if (Slrn_Prefer_Head == 2)
910      Spool_Server_Obj.sv_has_xover = 0;
911    else
912      /* !HACK! this assumes the overview file is rewound */
913      Spool_Server_Obj.sv_has_xover = ((Spool_fh_nov != NULL)
914 				      && (1 == fscanf (Spool_fh_nov, NNTP_FMT_ARTNUM, min)));
915 
916    if (0 == Spool_Server_Obj.sv_has_xover)
917      return -1;
918 
919    /* find start of last line */
920    if (-1 == fseek (Spool_fh_nov, 0, SEEK_END))
921      {
922         debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
923         return -1;
924      }
925    if (-1 == (fpos = ftell (Spool_fh_nov)))
926      {
927         debug_output (__FILE__, __LINE__, "ftell returned -1; errno %d (%s).", errno, strerror(errno));
928         return -1;
929      }
930 
931    found=0;
932    first=1;
933 
934    while (!found && (fpos > 0))
935      {
936 	int i, len;
937 
938 	len = SPOOL_NOV_STEP;
939 
940 	/* don't run of the start of the file */
941 	if (fpos < (long)len) len = fpos;
942 
943 	fpos -= len;
944         if (-1 == fseek(Spool_fh_nov, fpos, SEEK_SET))
945           {
946              debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
947              return -1;
948           }
949 
950 	if (first)
951 	  {
952 	     /* on the first pass, we want to ignore the last byte \n at eof */
953 	     --len;
954 	     first = 0;
955 	  }
956 
957 	for(i = 0; i < len; i++ )
958 	  {
959 	     ch = getc(Spool_fh_nov);
960 
961 	     assert(ch!=EOF); /* shouldn't happen */
962 	     if (ch =='\n')
963 	       found = i + 1; /* and keep going in case there's another */
964 	  }
965      }
966 
967    if (-1 == fseek(Spool_fh_nov, fpos + found, SEEK_SET))
968      {
969         debug_output (__FILE__, __LINE__, "fseek returned -1; errno %d (%s).", errno, strerror(errno));
970         return -1;
971      }
972 
973    if (1 != fscanf (Spool_fh_nov, NNTP_FMT_ARTNUM, max))
974      {
975         debug_output (__FILE__, __LINE__, "Hmmm, unable to understand overview file - no integer found?");
976         return -1;
977      }
978 
979    rewind (Spool_fh_nov);
980 
981    debug_output (NULL, -1, ("%s " NNTP_FMT_ARTNUM_2), name,*min,*max);
982    return 0;
983 }
984 
spool_select_group(char * name,NNTP_Artnum_Type * min,NNTP_Artnum_Type * max)985 static int spool_select_group (char *name, NNTP_Artnum_Type *min, NNTP_Artnum_Type *max)
986 {
987    /* close any open files */
988    spool_fclose_local();
989 
990    if (Spool_fh_nov != NULL)
991      {
992 	fclose (Spool_fh_nov);
993 	Spool_fh_nov = NULL;
994      }
995 
996    slrn_free (Spool_Group);
997    slrn_free (Spool_Group_Name);
998 
999    Spool_Group = slrn_spool_dircat (Slrn_Spool_Root, name, 1);
1000    Spool_Group_Name = slrn_safe_strmalloc (name);
1001 
1002    Spool_fh_nov = spool_open_nov_file ();
1003 
1004    if ((-1 == spool_read_minmax_from_overview (name, min, max))
1005 #if SPOOL_ACTIVE_FOR_ART_RANGE
1006        && (-1 == spool_read_minmax_from_active (name, min, max))
1007 #endif
1008        && (-1 == spool_read_minmax_from_dir (min, max, Spool_Group)))
1009      return -1;
1010 
1011    Spool_Max_Artnum = *max;
1012    Spool_Min_Artnum = *min;
1013 
1014    debug_output (NULL, -1, "Group: %s " NNTP_FMT_ARTRANGE, name, *min, *max);
1015    return OK_GROUP;
1016 }
1017 
spool_refresh_groups(Slrn_Group_Range_Type * gr,int n)1018 static int spool_refresh_groups (Slrn_Group_Range_Type *gr, int n)
1019 {
1020    while (n)
1021      {
1022 	if (-1 == spool_select_group (gr->name,
1023 				      &(gr->min), &(gr->max)))
1024 	  gr->min = -1;
1025 	n--;
1026 	gr++;
1027      }
1028    return 0;
1029 }
1030 
spool_current_group(void)1031 static char *spool_current_group (void)
1032 {
1033    return Spool_Group_Name == NULL ? "" : Spool_Group_Name;
1034 }
1035 
1036 static int Spool_Server_Inited = 0;
1037 
spool_close_server(void)1038 static void spool_close_server (void)
1039 {
1040    slrn_free (Spool_Group);
1041    Spool_Group = NULL;
1042 
1043    spool_fclose_local();
1044 
1045    if (NULL != Spool_fh_nov)
1046      {
1047 	fclose (Spool_fh_nov);
1048 	Spool_fh_nov = NULL;
1049      }
1050    Spool_Server_Inited = 0;
1051 }
1052 
spool_has_cmd(char * cmd)1053 static int spool_has_cmd (char *cmd)
1054 {
1055    (void) cmd;
1056    return 0; /* deny everything */
1057 }
1058 
spool_initialize_server(void)1059 static int spool_initialize_server (void)
1060 {
1061    if (Spool_Server_Inited) spool_close_server ();
1062 
1063    if (2 != slrn_file_exists (Slrn_Spool_Root))
1064      {
1065 	slrn_error (_("Local spool directory '%s' doesn't exist."), Slrn_Spool_Root);
1066 	return -1;
1067      }
1068 
1069    /* I think it's better to think that the *server* has XOVER, but
1070     * some (or all) groups may not.
1071     * So set this to 1 here, and then to 0 or 1 in spool_select_group if we
1072     * find an overview file
1073     */
1074    Spool_Server_Obj.sv_has_xover = 1;
1075    Spool_Server_Inited = 1;
1076    return 0;
1077 }
1078 
spool_read_fhlocal(char * line,unsigned int len)1079 static int spool_read_fhlocal (char *line, unsigned int len)
1080 {
1081    do
1082      {
1083 	if ((NULL == Spool_fh_local)
1084 	    || (NULL == fgets (line, len, Spool_fh_local))
1085 	    || (Spool_fhead && (line[0]=='\n')))
1086 	  {
1087 	     spool_fclose_local();
1088 	     return 0;
1089 	  }
1090      }
1091    while (((Spool_Output_Regexp != NULL) &&
1092 	   (NULL == slrn_regexp_match (Spool_Output_Regexp, line))) ||
1093 	  (Spool_Ignore_Comments && (*line == '#')));
1094 
1095    len = strlen(line);
1096 
1097    bytes_read += len;
1098 
1099    if (len && (line [len - 1] == '\n'))
1100      line [len-1] = '\0';
1101 
1102    return 1;
1103 }
1104 
spool_read_line(char * line,unsigned int len)1105 static int spool_read_line (char *line, unsigned int len)
1106 {
1107    if (Spool_Doing_XPat) return spool_read_xpat (line, len);
1108 
1109    if (Spool_Doing_XOver) return spool_read_xover (line, len);
1110 
1111    if (NULL != Spool_XHdr_Field)
1112      {
1113 	char buf[NNTP_BUFFER_SIZE];
1114 	int retval = -1;
1115 
1116 	/* if (buf == NULL) return -1; */
1117 	if ((len > 33) &&
1118 	    (1 == (retval = spool_read_xhdr (buf, sizeof (buf)))))
1119 	  {
1120 	     unsigned int numlen;
1121 	     (void) slrn_snprintf (line, len, (NNTP_FMT_ARTNUM " "), (Spool_XOver_Next - 1));
1122 	     numlen = strlen (line);
1123 	     if (len > numlen)
1124 	       slrn_strncpy (line + numlen, buf, len - numlen);
1125 	  }
1126 	return retval;
1127      }
1128 
1129    if (Spool_fFakingActive) return spool_fakeactive_read_line (line, len);
1130 
1131    return spool_read_fhlocal (line, len);
1132 }
1133 
1134 typedef struct
1135 {
1136    int xover_field;
1137    NNTP_Artnum_Type rmin, rmax;
1138    char header[80];
1139    char pat[256];
1140 }
1141 Spool_XPat_Type;
1142 
1143 static Spool_XPat_Type Spool_XPat_Struct;
1144 
spool_xpat_match(char * str,char * pat)1145 static int spool_xpat_match (char *str, char *pat)
1146 {
1147    /* HACK.  This needs fixed for more general patterns. */
1148    if (NULL == strstr (str, pat))
1149      return -1;
1150 
1151    return 0;
1152 }
1153 
spool_read_xpat(char * buf,unsigned int len)1154 static int spool_read_xpat (char *buf, unsigned int len)
1155 {
1156    char tmpbuf [8192];
1157 
1158    Spool_Doing_XPat = 0;
1159 
1160    if (Spool_XPat_Struct.xover_field == -1)
1161      {
1162 	NNTP_Artnum_Type num;
1163 
1164 	for (num = Spool_XPat_Struct.rmin; num <= Spool_XPat_Struct.rmax; num++)
1165 	  {
1166 	     if (-1 == spool_one_xhdr_command (Spool_XPat_Struct.header, num,
1167 					       tmpbuf, sizeof (tmpbuf)))
1168 	       continue;
1169 
1170 	     if (-1 != spool_xpat_match (tmpbuf, Spool_XPat_Struct.pat))
1171 	       {
1172 		  unsigned int blen;
1173 
1174 		  Spool_Doing_XPat = 1;
1175 		  Spool_XPat_Struct.rmin = num + 1;
1176 
1177 		  slrn_snprintf (buf, len, (NNTP_FMT_ARTNUM " "), num);
1178 		  blen = strlen (buf);
1179 
1180 		  strncpy (buf + blen, tmpbuf, len - blen);
1181 		  buf[len - 1] = 0;
1182 
1183 		  return 1;
1184 	       }
1185 	  }
1186 
1187 	Spool_XPat_Struct.rmin = Spool_XPat_Struct.rmax + 1;
1188      }
1189    else
1190      {
1191 	/* read the overview file until the right field matches the pattern */
1192 	while (NULL != (fgets (tmpbuf, sizeof (tmpbuf), Spool_fh_local)))
1193 	  {
1194 	     /* first field is article number; "+1" skips it */
1195 	     int field = Spool_XPat_Struct.xover_field + 1;
1196 	     NNTP_Artnum_Type num = NNTP_STR_TO_ARTNUM (tmpbuf);
1197 	     char *b = tmpbuf, *end;
1198 
1199 	     if ((num > Spool_XPat_Struct.rmax) || (num < 0))
1200 	       {
1201 		  spool_fclose_local ();
1202 		  return 0;
1203 	       }
1204 
1205 	     while (field)
1206 	       {
1207 		  b = slrn_strbyte (b, '\t');
1208 		  /* If we reach the end of the overview line before finding
1209 		   * the correct field, the overview file must be corrupt. */
1210 		  if (b == NULL)
1211 		    {
1212 		       debug_output (NULL, -1, "Overview file corrupt? Unable to find "
1213 				     "field %d in overview line %s.",
1214 				     Spool_XPat_Struct.xover_field, tmpbuf);
1215 		       spool_fclose_local ();
1216 		       return -1;
1217 		    }
1218 		  b++;
1219 		  field--;
1220 	       }
1221 	     end = slrn_strbyte (b, '\t');
1222 	     if (end != NULL)
1223 	       *end = '\0';
1224 
1225 	     if (-1 != spool_xpat_match (b, Spool_XPat_Struct.pat))
1226 	       {
1227 		  unsigned int blen;
1228 
1229 		  Spool_Doing_XPat = 1;
1230 		  slrn_snprintf (buf, len, (NNTP_FMT_ARTNUM " "), num);
1231 		  blen = strlen (buf);
1232 
1233 		  strncpy (buf + blen, b, len - blen);
1234 		  buf[len - 1] = 0;
1235 		  return 1;
1236 	       }
1237 	  }
1238 	spool_fclose_local ();
1239      }
1240 
1241    return 0;
1242 }
1243 
spool_xpat_cmd(char * hdr,NNTP_Artnum_Type rmin,NNTP_Artnum_Type rmax,char * pat)1244 static int spool_xpat_cmd (char *hdr, NNTP_Artnum_Type rmin, NNTP_Artnum_Type rmax, char *pat)
1245 {
1246    static char *overview_headers [] =
1247      {
1248 	"Subject", "From", "Date", "Message-ID",
1249 	"References", "Bytes", "Lines",
1250 	NULL
1251      };
1252 
1253    spool_fclose_local ();
1254    Spool_Doing_XPat = 0;
1255    memset ((char *) &Spool_XPat_Struct, 0, sizeof (Spool_XPat_Struct));
1256 
1257    if (rmin < Spool_Min_Artnum)
1258      rmin = Spool_Min_Artnum;
1259    if (rmax > Spool_Max_Artnum)
1260      rmax = Spool_Max_Artnum;
1261 
1262    Spool_XPat_Struct.rmin = rmin;
1263    Spool_XPat_Struct.rmax = rmax;
1264 
1265    /* The memset will guarantee that these are NULL terminated. */
1266    strncpy (Spool_XPat_Struct.header, hdr, sizeof (Spool_XPat_Struct.header) - 1);
1267    strncpy (Spool_XPat_Struct.pat, pat, sizeof (Spool_XPat_Struct.pat) - 1);
1268 
1269    Spool_XPat_Struct.xover_field = -1;
1270 
1271    if (Slrn_Server_Obj->sv_has_xover)
1272      {
1273 	int field = 0;
1274 
1275 	while (1)
1276 	  {
1277 	     char *h = overview_headers [field];
1278 
1279 	     if (h == NULL) break;
1280 	     if (0 == slrn_case_strcmp ( h,  hdr))
1281 	       {
1282 		  Spool_XPat_Struct.xover_field = field;
1283 		  break;
1284 	       }
1285 	     field++;
1286 	  }
1287      }
1288 
1289    if (Spool_XPat_Struct.xover_field != -1)
1290      {
1291 	Spool_fh_local = spool_open_nov_file ();
1292 	if (Spool_fh_local == NULL)
1293 	  return ERR_COMMAND;
1294 	if (-1 == overview_file_seek (0, -1, Spool_XPat_Struct.rmin))
1295 	  return -1;
1296      }
1297 
1298    Spool_Doing_XPat = 1;
1299 
1300    return OK_HEAD;
1301 }
1302 
spool_select_article(NNTP_Artnum_Type n,char * msgid)1303 static int spool_select_article (NNTP_Artnum_Type n, char *msgid)
1304 {
1305    /*    printf("spool_select_article(%d,%s)\n",n,msgid); */
1306 
1307    if (n == -1)
1308      {
1309 	if ((msgid == NULL) || (*msgid == 0))
1310 	  return -1;
1311 
1312 	if (-1 == spool_find_artnum_from_msgid (msgid, &n))
1313 	  return ERR_NOARTIG;
1314      }
1315 
1316    spool_fclose_local();
1317 
1318    if (NULL == (Spool_fh_local = spool_open_article_num (n)))
1319      return ERR_NOARTIG;
1320 
1321    Spool_cur_artnum = n;
1322    return OK_ARTICLE;
1323 }
1324 
1325 /* The hdr string should NOT include the ':' */
spool_one_xhdr_command(char * hdr,NNTP_Artnum_Type num,char * buf,unsigned int buflen)1326 static int spool_one_xhdr_command (char *hdr, NNTP_Artnum_Type num, char *buf,
1327 				   unsigned int buflen)
1328 {
1329    char tmpbuf [1024];
1330    unsigned int colon;
1331 
1332    spool_fclose_local ();
1333 
1334    if (NULL == (Spool_fh_local = spool_open_article_num (num)))
1335      return -1;
1336 
1337    Spool_fhead = 1;		       /* stop after headers */
1338 
1339    colon = strlen (hdr);
1340 
1341    while (1 == spool_read_fhlocal (tmpbuf, sizeof (tmpbuf)))
1342      {
1343 	char *b;
1344 	if (slrn_case_strncmp ( tmpbuf,  hdr, colon)
1345 	    || (tmpbuf[colon] != ':'))
1346 	  continue;
1347 
1348 	b = tmpbuf + (colon + 1);
1349 	if (*b == ' ') b++;
1350 	slrn_strncpy (buf, b, buflen);
1351 	while ((1 == spool_read_fhlocal (tmpbuf, sizeof (tmpbuf))) &&
1352 	       ((*tmpbuf == ' ') || (*tmpbuf == '\t')))
1353 	  {
1354 	     unsigned int len = strlen (buf);
1355 	     b = tmpbuf + 1;
1356 	     slrn_strncpy (buf + len, b, buflen - len);
1357 	  }
1358 	return 0;
1359      }
1360 
1361    return -1;
1362 }
1363 
spool_xhdr_command(char * field,NNTP_Artnum_Type min,NNTP_Artnum_Type max)1364 static int spool_xhdr_command (char *field, NNTP_Artnum_Type min, NNTP_Artnum_Type max)
1365 {
1366    debug_output (NULL, -1, ("spool_xhdr_command(%s " NNTP_FMT_ARTNUM_2),
1367 		 field, min, max);
1368 
1369    spool_fclose_local ();
1370 
1371    if (max > Spool_Max_Artnum)
1372      max = Spool_Max_Artnum;
1373 
1374    if (min < Spool_Min_Artnum)
1375      min = Spool_Min_Artnum;
1376 
1377    Spool_XOver_Next = Spool_XOver_Min = min;
1378    Spool_XOver_Max = max;
1379    Spool_XHdr_Field = field;
1380 
1381    return OK_HEAD;
1382 }
1383 
spool_read_xhdr(char * the_buf,unsigned int len)1384 static int spool_read_xhdr (char *the_buf, unsigned int len)
1385 {
1386    int retval = -1;
1387 
1388    if (Spool_XHdr_Field == NULL)
1389      return -1;
1390 
1391    if (Spool_XOver_Next > Spool_XOver_Max)
1392      {
1393 	Spool_XHdr_Field = NULL;
1394 	return 0;
1395      }
1396 
1397    while ((retval == -1) && (Spool_XOver_Next <= Spool_XOver_Max))
1398      retval = spool_one_xhdr_command (Spool_XHdr_Field, Spool_XOver_Next++,
1399 				      the_buf, len);
1400 
1401    if (Spool_XOver_Next > Spool_XOver_Max)
1402      Spool_XHdr_Field = NULL;
1403 
1404    return (retval == -1) ? -1 : 1;
1405 }
1406 
spool_list(char * what)1407 static int spool_list (char *what)
1408 {
1409    if (!slrn_case_strcmp (what,"overview.fmt"))
1410      {
1411 	spool_fclose_local();
1412 	Spool_fh_local=fopen(Slrn_Overviewfmt_File,"r");
1413 	if (Spool_fh_local)
1414 	  {
1415 	     Spool_Ignore_Comments = 1;
1416 	     return OK_GROUPS;
1417 	  }
1418      }
1419    return ERR_FAULT;
1420 }
1421 
spool_list_newsgroups(void)1422 static int spool_list_newsgroups (void)
1423 {
1424    spool_fclose_local();
1425    Spool_fh_local=fopen(Slrn_Newsgroups_File,"r");
1426    if (!Spool_fh_local)
1427      {
1428 	/* Use readdir() to return a list of newsgroups read from the
1429 	 * "newsspool" so we can read MH folders, etc.  This would be more
1430 	 * than a little slow for a true newsspool.
1431 	 */
1432 	spool_fake_active(Slrn_Spool_Root);
1433 	Spool_fFakingActive=1;
1434 	Spool_fakeactive_newsgroups=1;
1435 /*	slrn_exit_error("Couldn't open newsgroups file '%s'", NEWSGROUPS); */
1436      }
1437    return OK_GROUPS;
1438 }
1439 
1440 /* Having an independant buffer for the spool code might spare us mysterious
1441  * bugs, so I'm not simply using slrn_compile_regexp_pattern */
1442 #if SLANG_VERSION < 20000
spool_compile_regexp_pattern(char * pat)1443 static SLRegexp_Type *spool_compile_regexp_pattern (char *pat)
1444 {
1445    static unsigned char compiled_pattern_buf [512];
1446    static SLRegexp_Type re;
1447 
1448    re.pat = (unsigned char *) pat;
1449    re.buf = compiled_pattern_buf;
1450    re.buf_len = sizeof (compiled_pattern_buf);
1451    re.case_sensitive = 1;
1452 
1453    if (0 != SLang_regexp_compile (&re))
1454      {
1455 	slrn_error (_("Invalid regular expression or expression too long."));
1456 	return NULL;
1457      }
1458    return &re;
1459 }
1460 #endif
1461 
spool_list_active(char * pat)1462 static int spool_list_active (char *pat)
1463 {
1464    spool_fclose_local();
1465    Spool_fh_local=fopen (Slrn_Active_File,"r");
1466    if (pat != NULL)
1467      {
1468 #if SLANG_VERSION < 20000
1469 	Spool_Output_Regexp=spool_compile_regexp_pattern (slrn_fix_regexp (pat));
1470 #else
1471 	if (Spool_Output_Regexp != NULL)
1472 	  SLregexp_free (Spool_Output_Regexp);
1473 	Spool_Output_Regexp = SLregexp_compile (slrn_fix_regexp(pat),0);
1474 #endif
1475      }
1476 
1477    if (!Spool_fh_local)
1478      {
1479 	spool_fake_active(Slrn_Spool_Root);
1480 	Spool_fFakingActive=1;
1481 	Spool_fakeactive_newsgroups=0;
1482 	return OK_GROUPS;
1483 	/* Use readdir() to return a list of newsgroups and article ranges read
1484 	 * from the "newsspool" so we can read MH folders, etc.  This would
1485 	 * be more than a little slow for a true newsspool.
1486 	 */
1487 /*	slrn_exit_error("Couldn't open active file '%s'", ACTIVE);*/
1488      }
1489    return OK_GROUPS;
1490 }
1491 
spool_send_authinfo(void)1492 static int spool_send_authinfo (void)
1493 {
1494    return 0;
1495 }
1496 
1497 typedef struct _Spool_DirTree_Type
1498 {
1499    struct _Spool_DirTree_Type *parent;
1500    Slrn_Dir_Type *dp;
1501    int len;
1502    long lo, hi;
1503 }
1504 Spool_DirTree_Type;
1505 
1506 static Spool_DirTree_Type *Spool_Head;
1507 
1508 static char Spool_Buf[256];
1509 static char Spool_nBuf[256];
1510 static int Spool_Is_LeafDir;
1511 
spool_fake_active_in(char * dir)1512 static void spool_fake_active_in (char *dir)
1513 {
1514    char *p;
1515    Slrn_Dir_Type *dp;
1516    Spool_DirTree_Type *tmp;
1517 
1518    p = Spool_Buf + strlen(Spool_Buf);
1519 
1520    if ((dir != NULL) &&
1521        (strlen (dir) < sizeof (Spool_Buf) - (size_t) (Spool_Buf - p + 2)))
1522      {
1523 	*p = SLRN_PATH_SLASH_CHAR;
1524 	strcpy (p + 1, dir); /* safe */
1525      }
1526 
1527    if ((2 != slrn_file_exists (Spool_Buf))
1528        || (NULL == (dp = slrn_open_dir (Spool_Buf))))
1529      {
1530 	*p = 0;
1531 	return;
1532      }
1533 
1534    Spool_Is_LeafDir = 1;
1535    tmp = (Spool_DirTree_Type *) slrn_safe_malloc (sizeof(Spool_DirTree_Type));
1536 
1537    tmp->dp = dp;
1538    tmp->parent = Spool_Head;
1539    tmp->hi = 0;
1540    tmp->lo = LONG_MAX;
1541 
1542    if (dir == NULL)
1543      tmp->len = 1;
1544    else
1545      {
1546 	tmp->len = strlen (dir);
1547 
1548 	p = Spool_nBuf + strlen (Spool_nBuf);
1549 	if (strlen (dir) < sizeof (Spool_nBuf) - (size_t) (Spool_nBuf - p + 2))
1550 	  {
1551 	     if (p != Spool_nBuf) *p++ = '.';
1552 	     strcpy (p, dir); /* safe */
1553 	  }
1554      }
1555 
1556    Spool_Head = tmp;
1557 }
1558 
spool_fake_active_out(void)1559 static void spool_fake_active_out (void)
1560 {
1561    Spool_DirTree_Type *tmp;
1562    int i;
1563 
1564    (void)slrn_close_dir (Spool_Head->dp);
1565    Spool_Is_LeafDir = 0;
1566 
1567    Spool_Buf [strlen(Spool_Buf) - Spool_Head->len - 1] = '\0';
1568 
1569    i = strlen(Spool_nBuf) - Spool_Head->len - 1;
1570 
1571    if (i < 0) i = 0;
1572    Spool_nBuf[i]='\0';
1573 
1574    tmp = Spool_Head;
1575    Spool_Head = Spool_Head->parent;
1576    SLFREE(tmp);
1577 }
1578 
spool_fake_active(char * path)1579 static int spool_fake_active (char *path)
1580 {
1581    slrn_strncpy (Spool_Buf, path, sizeof (Spool_Buf));
1582    *Spool_nBuf='\0';
1583    Spool_Head=NULL;
1584    spool_fake_active_in (NULL);
1585    return 0;
1586 }
1587 
spool_fakeactive_read_line(char * line,int len)1588 static int spool_fakeactive_read_line(char *line, int len)
1589 {
1590    Slrn_Dirent_Type *ep;
1591    char *p;
1592    long l;
1593 
1594    (void) len;
1595 
1596    emptydir:
1597 
1598    if (!Spool_Head)
1599      {
1600       /* we've reached the end of the road */
1601 	Spool_fFakingActive = 0;
1602 	return 0;
1603      }
1604 
1605    /* Scan through all the files, checking the ones with numbers for names */
1606    while ((ep = slrn_read_dir(Spool_Head->dp)) != NULL)
1607      {
1608 	p = ep->name;
1609 
1610 	if ((0 == spool_is_name_all_digits (p))
1611 	    || ((l = atol (p)) == 0))
1612 	  {
1613 	     if (!(p[0]=='.' && (p[1]=='\0' || (p[1]=='.' && p[2]=='\0'))))
1614 	       {
1615 		  spool_fake_active_in(p);
1616 	       }
1617 	     continue;
1618 	  }
1619 	if (l < Spool_Head->lo)
1620 	  Spool_Head->lo = l;
1621 	if (l > Spool_Head->hi)
1622 	  Spool_Head->hi = l;
1623      }
1624 
1625    if (Spool_Head->lo == LONG_MAX && Spool_Head->hi==0)
1626      {
1627 	/* assume all leaf directories are valid groups */
1628 	/* non-leaf directories aren't groups unless they have articles in */
1629 	if (!Spool_Is_LeafDir)
1630 	  {
1631 	     spool_fake_active_out();
1632 	     goto emptydir; /* skip empty "groups" */
1633 	  }
1634 	Spool_Head->lo = 1;
1635      }
1636 
1637    if (Spool_fakeactive_newsgroups)
1638      {
1639       /* newsgroups: alt.foo A group about foo */
1640 	slrn_snprintf (line, len, "%s ?\n", Spool_nBuf);
1641      }
1642    else
1643      {
1644       /* active: alt.guitar 0000055382 0000055345 y */
1645 	slrn_snprintf (line, len, "%s %ld %ld y\n", Spool_nBuf,
1646 		       Spool_Head->hi, Spool_Head->lo);
1647      }
1648    spool_fake_active_out();
1649    return 1;
1650 }
1651 
spool_reset(void)1652 static void spool_reset (void)
1653 {
1654    spool_fclose_local ();
1655 }
1656 
spool_get_bytes(int clear)1657 static unsigned int spool_get_bytes (int clear)
1658 {
1659    unsigned int temp;
1660 
1661    temp = bytes_read;
1662    if (clear)
1663      bytes_read = 0;
1664 
1665    return temp;
1666 }
1667 
1668 char *Slrn_Inn_Root;
1669 char *Slrn_Spool_Root;
1670 char *Slrn_Nov_Root;
1671 char *Slrn_Nov_File;
1672 char *Slrn_Headers_File;
1673 char *Slrn_Active_File;
1674 char *Slrn_ActiveTimes_File;
1675 char *Slrn_Newsgroups_File;
1676 int Slrn_Spool_Check_Up_On_Nov;
1677 
spool_init_objects(void)1678 static int spool_init_objects (void)
1679 {
1680    char *login;
1681    Spool_Server_Obj.sv_select_group = spool_select_group;
1682    Spool_Server_Obj.sv_refresh_groups = spool_refresh_groups;
1683    Spool_Server_Obj.sv_current_group = spool_current_group;
1684    Spool_Server_Obj.sv_read_line = spool_read_line;
1685    Spool_Server_Obj.sv_close = spool_close_server;
1686    Spool_Server_Obj.sv_reset = spool_reset;
1687    Spool_Server_Obj.sv_initialize = spool_initialize_server;
1688    Spool_Server_Obj.sv_select_article = spool_select_article;
1689    Spool_Server_Obj.sv_get_article_size = spool_get_article_size;
1690    Spool_Server_Obj.sv_put_server_cmd = spool_put_server_cmd;
1691    Spool_Server_Obj.sv_xpat_cmd = spool_xpat_cmd;
1692    Spool_Server_Obj.sv_xhdr_command = spool_one_xhdr_command;
1693    Spool_Server_Obj.sv_has_cmd = spool_has_cmd;
1694    Spool_Server_Obj.sv_list = spool_list;
1695    Spool_Server_Obj.sv_list_newsgroups = spool_list_newsgroups;
1696    Spool_Server_Obj.sv_list_active = spool_list_active;
1697    Spool_Server_Obj.sv_send_authinfo = spool_send_authinfo;
1698 
1699    Spool_Server_Obj.sv_has_xhdr = 1;
1700    Spool_Server_Obj.sv_has_xover = 0;
1701    Spool_Server_Obj.sv_nntp_xover = spool_nntp_xover;
1702    Spool_Server_Obj.sv_nntp_xhdr = spool_xhdr_command;
1703    Spool_Server_Obj.sv_nntp_head = spool_nntp_head;
1704    Spool_Server_Obj.sv_nntp_next = spool_nntp_next;
1705    Spool_Server_Obj.sv_nntp_bytes = spool_get_bytes;
1706    Spool_Server_Obj.sv_id = SERVER_ID_UNKNOWN;
1707 
1708    Slrn_Inn_Root = slrn_safe_strmalloc (SLRN_SPOOL_INNROOT);
1709    Slrn_Spool_Root = slrn_safe_strmalloc (SLRN_SPOOL_ROOT);
1710    Slrn_Nov_Root = slrn_safe_strmalloc (SLRN_SPOOL_NOV_ROOT);
1711    Slrn_Nov_File = slrn_safe_strmalloc (SLRN_SPOOL_NOV_FILE);
1712    Slrn_Headers_File = slrn_safe_strmalloc (SLRN_SPOOL_HEADERS);
1713    Slrn_Active_File = slrn_safe_strmalloc (SLRN_SPOOL_ACTIVE);
1714    Slrn_ActiveTimes_File = slrn_safe_strmalloc (SLRN_SPOOL_ACTIVETIMES);
1715    Slrn_Newsgroups_File = slrn_safe_strmalloc (SLRN_SPOOL_NEWSGROUPS);
1716    Slrn_Overviewfmt_File = slrn_safe_strmalloc (SLRN_SPOOL_OVERVIEWFMT);
1717    if ((NULL == (login = Slrn_User_Info.login_name)) ||
1718        (*login == 0))
1719      login = "!unknown";
1720    Slrn_Requests_File = slrn_strdup_printf ("%s/%s", SLRNPULL_REQUESTS_DIR, login);
1721 
1722 #if defined(IBMPC_SYSTEM)
1723    slrn_os2_convert_path (Slrn_Inn_Root);
1724    slrn_os2_convert_path (Slrn_Spool_Root);
1725    slrn_os2_convert_path (Slrn_Nov_Root);
1726    slrn_os2_convert_path (Slrn_Nov_File);
1727    slrn_os2_convert_path (Slrn_Headers_File);
1728    slrn_os2_convert_path (Slrn_Active_File);
1729    slrn_os2_convert_path (Slrn_ActiveTimes_File);
1730    slrn_os2_convert_path (Slrn_Newsgroups_File);
1731    slrn_os2_convert_path (Slrn_Overviewfmt_File);
1732    slrn_os2_convert_path (Slrn_Requests_File);
1733 #endif
1734    return 0;
1735 }
1736 
1737 /* This function is used below.  It has a very specific purpose. */
spool_root_dircat(char * file)1738 static char *spool_root_dircat (char *file)
1739 {
1740    char *f;
1741 
1742    if (slrn_is_absolute_path (file))
1743      return file;
1744 
1745    f = slrn_spool_dircat (Slrn_Inn_Root, file, 0);
1746    SLFREE (file);
1747    return f;
1748 }
1749 
spool_select_server_object(void)1750 static int spool_select_server_object (void)
1751 {
1752    Slrn_Server_Obj = &Spool_Server_Obj;
1753    Slrn_Active_File = spool_root_dircat (Slrn_Active_File);
1754    Slrn_ActiveTimes_File = spool_root_dircat (Slrn_ActiveTimes_File);
1755    Slrn_Newsgroups_File = spool_root_dircat (Slrn_Newsgroups_File);
1756    Slrn_Overviewfmt_File = spool_root_dircat (Slrn_Overviewfmt_File);
1757    Slrn_Requests_File = spool_root_dircat (Slrn_Requests_File);
1758 
1759    slrn_free (Spool_Server_Obj.sv_name);
1760    Spool_Server_Obj.sv_name = slrn_safe_strmalloc (Slrn_Spool_Root);
1761 
1762    return 0;
1763 }
1764 
1765 /* Handling of the additional newsrc-style files in true offline mode: */
slrn_spool_get_no_body_ranges(char * group)1766 Slrn_Range_Type *slrn_spool_get_no_body_ranges (char *group)
1767 {
1768    VFILE *vp;
1769    char *vline;
1770    unsigned int vlen;
1771    Slrn_Range_Type *retval = NULL;
1772    char *p, *q;
1773 
1774    p = slrn_spool_dircat (Slrn_Spool_Root, group, 1);
1775    q = slrn_spool_dircat (p, Slrn_Headers_File, 0);
1776    SLFREE (p);
1777 
1778    vp = vopen (q, 4096, 0);
1779    SLFREE (q);
1780    if (NULL == vp)
1781      return NULL;
1782 
1783    if (NULL != (vline = vgets (vp, &vlen)))
1784      {
1785 	if (vline[vlen-1] == '\n')
1786 	  vline[vlen-1] = 0; /* make sure line is NULL terminated */
1787 	else
1788 	  vline[vlen] = 0;
1789 	retval = slrn_ranges_from_newsrc_line (vline);
1790      }
1791 
1792    vclose (vp);
1793 
1794    return retval;
1795 }
1796 
slrn_spool_get_requested_ranges(char * group)1797 Slrn_Range_Type *slrn_spool_get_requested_ranges (char *group) /*{{{*/
1798 {
1799    VFILE *vp;
1800    char *vline;
1801    unsigned int vlen;
1802    Slrn_Range_Type *retval = NULL;
1803 
1804    if (NULL == (vp = vopen (Slrn_Requests_File, 4096, 0)))
1805      return NULL;
1806 
1807    while (NULL != (vline = vgets (vp, &vlen)))
1808      {
1809 	char *p = vline;
1810 	char *pmax = p + vlen;
1811 
1812 	while ((p < pmax) && (*p != ':'))
1813 	  p++;
1814 
1815 	if ((p == pmax) || (p == vline) ||
1816 	    (strncmp(vline, group, (p-vline))))
1817 	  continue;
1818 
1819 	if (vline[vlen-1] == '\n')
1820 	  vline[vlen-1] = 0;
1821 	else
1822 	  vline[vlen] = 0;
1823 
1824 	retval = slrn_ranges_from_newsrc_line (p+1);
1825 	break;
1826      }
1827    vclose (vp);
1828    return retval;
1829 }
1830 /*}}}*/
1831 
1832 /* Updates the request file with the new ranges
1833  * returns 0 on success, -1 otherwise */
slrn_spool_set_requested_ranges(char * group,Slrn_Range_Type * r)1834 int slrn_spool_set_requested_ranges (char *group, Slrn_Range_Type *r) /*{{{*/
1835 {
1836    char *old_file = NULL;
1837    FILE *fp;
1838    VFILE *vp;
1839    char *vline;
1840    unsigned int vlen;
1841    struct stat filestat;
1842 #ifdef __unix__
1843    int stat_worked = 0;
1844 #endif
1845    int have_old = 0;
1846 
1847    slrn_init_hangup_signals (0);
1848 
1849 #ifdef __unix__
1850    /* Try to preserve file permissions and owner/group. */
1851    stat_worked = (-1 != stat (Slrn_Requests_File, &filestat));
1852 #endif
1853 
1854    /* Save old file (we'll copy most of it; then, it gets deleted) */
1855    have_old = (0 == slrn_create_backup (Slrn_Requests_File));
1856 
1857    if (NULL == (fp = fopen (Slrn_Requests_File, "w")))
1858      {
1859 	if (have_old) slrn_restore_backup (Slrn_Requests_File);
1860 	slrn_init_hangup_signals (1);
1861 	return -1;
1862      }
1863 
1864 #ifdef __unix__
1865 # if !defined(IBMPC_SYSTEM)
1866    /* Try to preserve file permissions and owner/group */
1867 #  ifndef S_IRUSR
1868 #   define S_IRUSR 0400
1869 #   define S_IWUSR 0200
1870 #   define S_IRGRP 0040
1871 #  endif
1872    if (stat_worked)
1873      {
1874 	if (-1 == chmod (Slrn_Requests_File, filestat.st_mode))
1875 	  (void) chmod (Slrn_Requests_File, S_IWUSR | S_IRUSR | S_IRGRP);
1876 
1877 	(void) chown (Slrn_Requests_File, filestat.st_uid, filestat.st_gid);
1878      }
1879    else
1880      (void) chmod (Slrn_Requests_File, S_IWUSR | S_IRUSR | S_IRGRP);
1881 # endif
1882 #endif
1883 
1884    /* Write a line for the current group */
1885    if (r != NULL)
1886      {
1887 	if ((EOF == fputs (group, fp)) ||
1888 	    (EOF == fputs (": ",fp)) ||
1889 	    (-1 == slrn_ranges_to_newsrc_file (r,0,fp)) ||
1890 	    (EOF == fputc ('\n',fp)))
1891 	  goto write_error;
1892      }
1893 
1894    /* Now, open the old file and append the data of all other groups */
1895    old_file = slrn_make_backup_filename (Slrn_Requests_File);
1896    if (NULL != (vp = vopen (old_file, 4096, 0)))
1897      {
1898 	while (NULL != (vline = vgets (vp, &vlen)))
1899 	  {
1900 	     char *p = vline;
1901 	     char *pmax = p + vlen;
1902 
1903 	     while ((p < pmax) && (*p != ':'))
1904 	       p++;
1905 
1906 	     if ((p != vline) && (((p-vline != (int) strlen(group)) ||
1907 				   strncmp(vline, group, (p-vline)))))
1908 	       {
1909 		  /* It seems we may not write past vline[vlen-1] for vgets
1910 		   * to work correctly, so save and reset this. */
1911 		  char ch = vline[vlen];
1912 		  vline[vlen] = '\0';
1913 		  if (EOF == fputs (vline, fp))
1914 		    {
1915 		       vclose (vp);
1916 		       goto write_error;
1917 		    }
1918 		  vline[vlen] = ch;
1919 	       }
1920 	  }
1921 	vclose (vp);
1922      }
1923 
1924    if (-1 == slrn_fclose (fp))
1925      goto write_error;
1926 
1927    if (have_old) slrn_delete_backup (Slrn_Requests_File);
1928 
1929    slrn_init_hangup_signals (1);
1930    SLfree (old_file);
1931    return 0;
1932 
1933    write_error:
1934 
1935    slrn_fclose (fp);
1936    /* Put back orginal file */
1937    if (have_old) slrn_restore_backup (Slrn_Requests_File);
1938 
1939    slrn_init_hangup_signals (1);
1940    SLfree (old_file);
1941    return -1;
1942 }
1943 /*}}}*/
1944