1 /* datasrc.c
2  */
3 /* This software is copyrighted as detailed in the LICENSE file. */
4 
5 
6 #include "EXTERN.h"
7 #include "common.h"
8 #include "list.h"
9 #include "trn.h"
10 #include "hash.h"
11 #include "ngdata.h"
12 #include "nntpclient.h"
13 #include "term.h"
14 #include "env.h"
15 #include "util.h"
16 #include "util2.h"
17 #include "opt.h"
18 #include "intrp.h"
19 #include "init.h"
20 #include "rcstuff.h"
21 #include "edit_dist.h"
22 #include "cache.h"
23 #include "last.h"
24 #include "rt-mt.h"
25 #include "rt-ov.h"
26 #include "rt-util.h"
27 #include "INTERN.h"
28 #include "datasrc.h"
29 #include "datasrc.ih"
30 #include "EXTERN.h"
31 #include "nntp.h"
32 
33 void
datasrc_init()34 datasrc_init()
35 {
36     char** vals = prep_ini_words(datasrc_ini);
37     char* machine = NULL;
38     char* actname = NULL;
39     char* s;
40 
41     datasrc_list = new_list(0,0,sizeof(DATASRC),20,LF_ZERO_MEM,NULL);
42 
43 #ifdef SUPPORT_NNTP
44     nntp_auth_file = savestr(filexp(NNTP_AUTH_FILE));
45 
46     machine = getenv("NNTPSERVER");
47     if (machine && strNE(machine,"local")) {
48 	vals[DI_NNTP_SERVER] = machine;
49 	vals[DI_AUTH_USER] = read_auth_file(nntp_auth_file,
50 					    &vals[DI_AUTH_PASS]);
51 #ifdef USE_GENAUTH
52 	vals[DI_AUTH_COMMAND] = getenv("NNTPAUTH");
53 #endif
54 	vals[DI_FORCE_AUTH] = getenv("NNTP_FORCE_AUTH");
55 	new_datasrc("default",vals);
56     }
57 #endif
58 
59     trnaccess_mem = read_datasrcs(TRNACCESS);
60     s = read_datasrcs(DEFACCESS);
61     if (!trnaccess_mem)
62 	trnaccess_mem = s;
63     else if (s)
64 	free(s);
65 
66 #ifdef SUPPORT_NNTP
67     if (!machine) {
68 	machine = filexp(SERVER_NAME);
69 	if (FILE_REF(machine))
70 	    machine = nntp_servername(machine);
71 	if (strEQ(machine,"local")) {
72 	    machine = NULL;
73 	    actname = ACTIVE;
74 	}
75 #else
76 	actname = ACTIVE;
77 #endif
78 	prep_ini_words(datasrc_ini);	/* re-zero the values */
79 
80 	vals[DI_NNTP_SERVER] = machine;
81 	vals[DI_ACTIVE_FILE] = actname;
82 	vals[DI_SPOOL_DIR] = NEWSSPOOL;
83 	vals[DI_THREAD_DIR] = THREAD_DIR;
84 	vals[DI_OVERVIEW_DIR] = OVERVIEW_DIR;
85 	vals[DI_OVERVIEW_FMT] = OVERVIEW_FMT;
86 	vals[DI_ACTIVE_TIMES] = ACTIVE_TIMES;
87 	vals[DI_GROUP_DESC] = GROUPDESC;
88 #ifdef SUPPORT_NNTP
89 	if (machine) {
90 	    vals[DI_AUTH_USER] = read_auth_file(nntp_auth_file,
91 						&vals[DI_AUTH_PASS]);
92 #ifdef USE_GENAUTH
93 	    vals[DI_AUTH_COMMAND] = getenv("NNTPAUTH");
94 #endif
95 	    vals[DI_FORCE_AUTH] = getenv("NNTP_FORCE_AUTH");
96 	}
97 #endif
98 	new_datasrc("default",vals);
99 #ifdef SUPPORT_NNTP
100     }
101 #endif
102     unprep_ini_words(datasrc_ini);
103 }
104 
105 char*
read_datasrcs(filename)106 read_datasrcs(filename)
107 char* filename;
108 {
109     int fd;
110     char* s;
111     char* section;
112     char* cond;
113     char* filebuf = NULL;
114     char** vals = INI_VALUES(datasrc_ini);
115 
116     if ((fd = open(filexp(filename),0)) >= 0) {
117 	fstat(fd,&filestat);
118 	if (filestat.st_size) {
119 	    int len;
120 	    filebuf = safemalloc((MEM_SIZE)filestat.st_size+2);
121 	    len = read(fd,filebuf,(int)filestat.st_size);
122 	    (filebuf)[len] = '\0';
123 	    prep_ini_data(filebuf,filename);
124 	    s = filebuf;
125 	    while ((s = next_ini_section(s,&section,&cond)) != NULL) {
126 		if (*cond && !check_ini_cond(cond))
127 		    continue;
128 		if (strncaseEQ(section, "group ", 6))
129 		    continue;
130 		s = parse_ini_section(s, datasrc_ini);
131 		if (!s)
132 		    break;
133 		new_datasrc(section,vals);
134 	    }
135 	}
136 	close(fd);
137     }
138     return filebuf;
139 }
140 
141 DATASRC*
get_datasrc(name)142 get_datasrc(name)
143 char* name;
144 {
145     DATASRC* dp;
146     for (dp = datasrc_first(); dp && dp->name; dp = datasrc_next(dp))
147 	if (strEQ(dp->name,name))
148 	    return dp;
149     return NULL;
150 }
151 
152 DATASRC*
new_datasrc(name,vals)153 new_datasrc(name,vals)
154 char* name;
155 char** vals;
156 {
157     DATASRC* dp = datasrc_ptr(datasrc_cnt++);
158     char* v;
159 
160     if (vals[DI_NNTP_SERVER]) {
161 #ifdef SUPPORT_NNTP
162 	dp->flags |= DF_REMOTE;
163 #else
164 	datasrc_cnt--;
165 	return NULL;
166 #endif
167     }
168     else if (!vals[DI_ACTIVE_FILE])
169 	return NULL; /*$$*/
170 
171     dp->name = savestr(name);
172     if (strEQ(name,"default"))
173 	dp->flags |= DF_DEFAULT;
174 
175 #ifdef SUPPORT_NNTP
176     if ((v = vals[DI_NNTP_SERVER]) != NULL) {
177 	char* cp;
178 	dp->newsid = savestr(v);
179 	if ((cp = index(dp->newsid, ';')) != NULL) {
180 	    *cp = '\0';
181 	    dp->nntplink.port_number = atoi(cp+1);
182 	}
183 
184 	if ((v = vals[DI_ACT_REFETCH]) != NULL && *v)
185 	    dp->act_sf.refetch_secs = text2secs(v,defRefetchSecs);
186 	else if (!vals[DI_ACTIVE_FILE])
187 	    dp->act_sf.refetch_secs = defRefetchSecs;
188     }
189     else
190 #endif /* SUPPORT_NNTP */
191 	dp->newsid = savestr(filexp(vals[DI_ACTIVE_FILE]));
192 
193     if (!(dp->spool_dir = file_or_none(vals[DI_SPOOL_DIR])))
194 	dp->spool_dir = savestr(tmpdir);
195 
196     dp->over_dir = dir_or_none(dp,vals[DI_OVERVIEW_DIR],DF_TRY_OVERVIEW);
197     dp->over_fmt = file_or_none(vals[DI_OVERVIEW_FMT]);
198     dp->thread_dir = dir_or_none(dp,vals[DI_THREAD_DIR],DF_TRY_THREAD);
199     dp->grpdesc = dir_or_none(dp,vals[DI_GROUP_DESC],0);
200     dp->extra_name = dir_or_none(dp,vals[DI_ACTIVE_TIMES],DF_ADD_OK);
201 #ifdef SUPPORT_NNTP
202     if (dp->flags & DF_REMOTE) {
203 	/* FYI, we know extra_name to be NULL in this case. */
204 	if (vals[DI_ACTIVE_FILE]) {
205 	    dp->extra_name = savestr(filexp(vals[DI_ACTIVE_FILE]));
206 	    if (stat(dp->extra_name,&filestat) >= 0)
207 		dp->act_sf.lastfetch = filestat.st_mtime;
208 	}
209 	else {
210 	    dp->extra_name = temp_filename();
211 	    dp->flags |= DF_TMPACTFILE;
212 	    if (!dp->act_sf.refetch_secs)
213 		dp->act_sf.refetch_secs = 1;
214 	}
215 
216 	if ((v = vals[DI_DESC_REFETCH]) != NULL && *v)
217 	    dp->desc_sf.refetch_secs = text2secs(v,defRefetchSecs);
218 	else if (!dp->grpdesc)
219 	    dp->desc_sf.refetch_secs = defRefetchSecs;
220 	if (dp->grpdesc) {
221 	    if (stat(dp->grpdesc,&filestat) >= 0)
222 		dp->desc_sf.lastfetch = filestat.st_mtime;
223 	}
224 	else {
225 	    dp->grpdesc = temp_filename();
226 	    dp->flags |= DF_TMPGRPDESC;
227 	    if (!dp->desc_sf.refetch_secs)
228 		dp->desc_sf.refetch_secs = 1;
229 	}
230     }
231     if ((v = vals[DI_FORCE_AUTH]) != NULL && (*v == 'y' || *v == 'Y'))
232 	dp->nntplink.flags |= NNTP_FORCE_AUTH_NEEDED;
233     if ((v = vals[DI_AUTH_USER]) != NULL)
234 	dp->auth_user = savestr(v);
235     if ((v = vals[DI_AUTH_PASS]) != NULL)
236 	dp->auth_pass = savestr(v);
237 #ifdef USE_GENAUTH
238     if ((v = vals[DI_AUTH_COMMAND]) != NULL)
239 	dp->auth_command = savestr(v);
240 #endif
241     if ((v = vals[DI_XHDR_BROKEN]) != NULL && (*v == 'y' || *v == 'Y'))
242 	dp->flags |= DF_XHDR_BROKEN;
243     if ((v = vals[DI_XREFS]) != NULL && (*v == 'n' || *v == 'N'))
244 	dp->flags |= DF_NOXREFS;
245 
246 #endif /* SUPPORT_NNTP */
247 
248     return dp;
249 }
250 
251 static char*
dir_or_none(dp,dir,flag)252 dir_or_none(dp,dir,flag)
253 DATASRC* dp;
254 char* dir;
255 int flag;
256 {
257     if (!dir || !*dir || strEQ(dir, "remote")) {
258 	dp->flags |= flag;
259 #ifdef SUPPORT_NNTP
260 	if (dp->flags & DF_REMOTE)
261 	    return NULL;
262 #endif
263 	if (flag == DF_ADD_OK) {
264 	    char* cp = safemalloc(strlen(dp->newsid)+6+1);
265 	    sprintf(cp,"%s.times",dp->newsid);
266 	    return cp;
267 	}
268 	if (flag == 0) {
269 	    char* cp = rindex(dp->newsid,'/');
270 	    int len;
271 	    if (!cp)
272 		return NULL;
273 	    len = cp - dp->newsid + 1;
274 	    cp = safemalloc(len+10+1);
275 	    strcpy(cp,dp->newsid);
276 	    strcpy(cp+len,"newsgroups");
277 	    return cp;
278 	}
279 	return dp->spool_dir;
280     }
281 
282     if (strEQ(dir, "none"))
283 	return NULL;
284 
285     dp->flags |= flag;
286     dir = filexp(dir);
287     if (strEQ(dir,dp->spool_dir))
288 	return dp->spool_dir;
289     return savestr(dir);
290 }
291 
292 static char*
file_or_none(fn)293 file_or_none(fn)
294 char* fn;
295 {
296     if (!fn || !*fn || strEQ(fn, "none") || strEQ(fn, "remote"))
297 	return NULL;
298     return savestr(filexp(fn));
299 }
300 
301 bool
open_datasrc(dp)302 open_datasrc(dp)
303 DATASRC* dp;
304 {
305     bool success;
306 
307     if (dp->flags & DF_UNAVAILABLE)
308 	return FALSE;
309     set_datasrc(dp);
310     if (dp->flags & DF_OPEN)
311 	return TRUE;
312 #ifdef SUPPORT_NNTP
313     if (dp->flags & DF_REMOTE) {
314 	if (nntp_connect(dp->newsid,1) <= 0) {
315 	    dp->flags |= DF_UNAVAILABLE;
316 	    return FALSE;
317 	}
318 	nntp_allow_timeout = FALSE;
319 	dp->nntplink = nntplink;
320 	if (dp->act_sf.refetch_secs) {
321 	    switch (nntp_list("active", "control", 7)) {
322 	    case 1:
323 		if (strnNE(ser_line, "control ", 8)) {
324 		    strcpy(buf, ser_line);
325 		    dp->act_sf.lastfetch = 0;
326 		    success = actfile_hash(dp);
327 		    break;
328 		}
329 		if (nntp_gets(buf, sizeof buf - 1) > 0
330 		 && !nntp_at_list_end(buf)) {
331 		    nntp_finish_list();
332 		    success = actfile_hash(dp);
333 		    break;
334 		}
335 		/* FALL THROUGH */
336 	    case 0:
337 		dp->flags |= DF_USELISTACT;
338 		if (dp->flags & DF_TMPACTFILE) {
339 		    dp->flags &= ~DF_TMPACTFILE;
340 		    free(dp->extra_name);
341 		    dp->extra_name = NULL;
342 		    dp->act_sf.refetch_secs = 0;
343 		    success = srcfile_open(&dp->act_sf,(char*)NULL,
344 					   (char*)NULL,(char*)NULL);
345 		}
346 		else
347 		    success = actfile_hash(dp);
348 		break;
349 	    case -2:
350 		printf("Failed to open news server %s:\n%s\n", dp->newsid, ser_line);
351 		termdown(2);
352 		success = FALSE;
353 		break;
354 	    default:
355 		success = actfile_hash(dp);
356 		break;
357 	    }
358 	} else
359 	    success = actfile_hash(dp);
360     }
361     else
362 #endif
363 	success = actfile_hash(dp);
364     if (success) {
365 	dp->flags |= DF_OPEN;
366 	if (dp->flags & DF_TRY_OVERVIEW)
367 	    ov_init();
368 	if (dp->flags & DF_TRY_THREAD)
369 	    mt_init();
370     }
371     else
372 	dp->flags |= DF_UNAVAILABLE;
373 #ifdef SUPPORT_NNTP
374     if (dp->flags & DF_REMOTE)
375 	nntp_allow_timeout = TRUE;
376 #endif
377     return success;
378 }
379 
380 void
set_datasrc(dp)381 set_datasrc(dp)
382 DATASRC* dp;
383 {
384 #ifdef SUPPORT_NNTP
385     if (datasrc)
386 	datasrc->nntplink = nntplink;
387     if (dp)
388 	nntplink = dp->nntplink;
389 #endif
390     datasrc = dp;
391 }
392 
393 void
check_datasrcs()394 check_datasrcs()
395 {
396 #ifdef SUPPORT_NNTP
397     DATASRC* dp;
398     time_t now = time((time_t*)NULL);
399     time_t limit;
400 
401     if (datasrc_list) {
402 	for (dp = datasrc_first(); dp && dp->name; dp = datasrc_next(dp)) {
403 	    if ((dp->flags & DF_OPEN) && dp->nntplink.rd_fp != NULL) {
404 		limit = ((dp->flags & DF_ACTIVE)? 30*60 : 10*60);
405 		if (now - dp->nntplink.last_command > limit) {
406 		    DATASRC* save_datasrc = datasrc;
407 		    /*printf("\n*** Closing %s ***\n", dp->name); $$*/
408 		    set_datasrc(dp);
409 		    nntp_close(TRUE);
410 		    dp->nntplink = nntplink;
411 		    set_datasrc(save_datasrc);
412 		}
413 	    }
414 	}
415     }
416 #endif
417 }
418 
419 void
close_datasrc(dp)420 close_datasrc(dp)
421 DATASRC* dp;
422 {
423 #ifdef SUPPORT_NNTP
424     if (dp->flags & DF_REMOTE) {
425 	if (dp->flags & DF_TMPACTFILE)
426 	    UNLINK(dp->extra_name);
427 	else
428 	    srcfile_end_append(&dp->act_sf, dp->extra_name);
429 	if (dp->grpdesc) {
430 	    if (dp->flags & DF_TMPGRPDESC)
431 		UNLINK(dp->grpdesc);
432 	    else
433 		srcfile_end_append(&dp->desc_sf, dp->grpdesc);
434 	}
435     }
436 #endif
437 
438     if (!(dp->flags & DF_OPEN))
439 	return;
440 
441 #ifdef SUPPORT_NNTP
442     if (dp->flags & DF_REMOTE) {
443 	DATASRC* save_datasrc = datasrc;
444 	set_datasrc(dp);
445 	nntp_close(TRUE);
446 	dp->nntplink = nntplink;
447 	set_datasrc(save_datasrc);
448     }
449 #endif
450     srcfile_close(&dp->act_sf);
451     srcfile_close(&dp->desc_sf);
452     dp->flags &= ~DF_OPEN;
453     if (datasrc == dp)
454 	datasrc = NULL;
455 }
456 
457 bool
actfile_hash(dp)458 actfile_hash(dp)
459 DATASRC* dp;
460 {
461     int ret;
462 #ifdef SUPPORT_NNTP
463     if (dp->flags & DF_REMOTE) {
464 	DATASRC* save_datasrc = datasrc;
465 	set_datasrc(dp);
466 	spin_todo = dp->act_sf.recent_cnt;
467 	ret = srcfile_open(&dp->act_sf, dp->extra_name, "active", dp->newsid);
468 	if (spin_count > 0)
469 	    dp->act_sf.recent_cnt = spin_count;
470 	set_datasrc(save_datasrc);
471     }
472     else
473 #endif
474 	ret = srcfile_open(&dp->act_sf, dp->newsid, (char*)NULL, (char*)NULL);
475     return ret;
476 }
477 
478 bool
find_actgrp(dp,outbuf,nam,len,high)479 find_actgrp(dp, outbuf, nam, len, high)
480 DATASRC* dp;
481 register char* outbuf;
482 register char* nam;
483 register int len;
484 ART_NUM high;
485 {
486     HASHDATUM data;
487     ACT_POS act_pos;
488     FILE* fp = dp->act_sf.fp;
489     char* lbp;
490     int lbp_len;
491 
492     /* Do a quick, hashed lookup */
493 
494     outbuf[0] = '\0';
495     data = hashfetch(dp->act_sf.hp, nam, len);
496     if (data.dat_ptr) {
497 	LISTNODE* node = (LISTNODE*)data.dat_ptr;
498 	/*dp->act_sf.lp->recent = node;*/
499 	act_pos = node->low + data.dat_len;
500 	lbp = node->data + data.dat_len;
501 	lbp_len = index(lbp, '\n') - lbp + 1;
502     }
503     else {
504 	lbp = NULL;
505 	lbp_len = 0;
506     }
507 #ifdef SUPPORT_NNTP
508     if ((dp->flags & DF_USELISTACT)
509      && (DATASRC_NNTP_FLAGS(dp) & NNTP_NEW_CMD_OK)) {
510 	DATASRC* save_datasrc = datasrc;
511 	set_datasrc(dp);
512 	switch (nntp_list("active", nam, len)) {
513 	case 0:
514 	    set_datasrc(save_datasrc);
515 	    return 0;
516 	case 1:
517 	    sprintf(outbuf, "%s\n", ser_line);
518 	    nntp_finish_list();
519 	    break;
520 	case -2:
521 	    /*$$$$*/
522 	    break;
523 	}
524 	set_datasrc(save_datasrc);
525 	if (!lbp_len) {
526 	    if (fp)
527 		(void) srcfile_append(&dp->act_sf, outbuf, len);
528 	    return 1;
529 	}
530 # ifndef ANCIENT_NEWS
531 	/* Safely update the low-water mark */
532 	{
533 	    char* f = rindex(outbuf, ' ');
534 	    char* t = lbp + lbp_len;
535 	    while (*--t != ' ') ;
536 	    while (t > lbp) {
537 		if (*--t == ' ')
538 		    break;
539 		if (f[-1] == ' ')
540 		    *t = '0';
541 		else
542 		    *t = *--f;
543 	    }
544 	}
545 # endif
546 	high = (ART_NUM)atol(outbuf+len+1);
547     }
548 #endif
549 
550     if (lbp_len) {
551 #ifdef SUPPORT_NNTP
552 	if ((dp->flags & DF_REMOTE) && dp->act_sf.refetch_secs) {
553 	    int num;
554 	    char* cp;
555 	    if (high && high != (ART_NUM)atol(cp = lbp+len+1)) {
556 		while (isdigit(*cp)) cp++;
557 		while (*--cp != ' ') {
558 		    num = high % 10;
559 		    high = high / 10;
560 		    *cp = '0' + (char)num;
561 		}
562 		fseek(fp, act_pos, 0);
563 		fwrite(lbp, 1, lbp_len, fp);
564 	    }
565 	    goto use_cache;
566 	}
567 #endif
568 
569 	/* hopefully this forces a reread */
570 	fseek(fp,2000000000L,1);
571 
572 	/*$$ if line has changed length or is not there, we should
573 	 * discard/close the active file, and re-open it. $$*/
574 	if (fseek(fp, act_pos, 0) >= 0
575 	 && fgets(outbuf, LBUFLEN, fp) != NULL
576 	 && strnEQ(outbuf, nam, len) && outbuf[len] == ' ') {
577 	    /* Remember the latest info in our cache. */
578 	    (void) bcopy(outbuf, lbp, lbp_len);
579 	    return 1;
580 	}
581       use_cache:
582 	/* Return our cached version */
583 	(void) bcopy(lbp, outbuf, lbp_len);
584 	outbuf[lbp_len] = '\0';
585 	return 1;
586     }
587     return 0;	/* no such group */
588 }
589 
590 char*
find_grpdesc(dp,groupname)591 find_grpdesc(dp, groupname)
592 DATASRC* dp;
593 char* groupname;
594 {
595     HASHDATUM data;
596     int grouplen;
597     int ret;
598 
599     if (!dp->grpdesc)
600 	return nullstr;
601 
602     if (!dp->desc_sf.hp) {
603 #ifdef SUPPORT_NNTP
604 	if ((dp->flags & DF_REMOTE) && dp->desc_sf.refetch_secs) {
605 	    /*DATASRC* save_datasrc = datasrc;*/
606 	    set_datasrc(dp);
607 	    if ((dp->flags & (DF_TMPGRPDESC|DF_NOXGTITLE)) == DF_TMPGRPDESC
608 	     && netspeed < 5) {
609 		(void)srcfile_open(&dp->desc_sf,(char*)NULL,/*$$check return?*/
610 				   (char*)NULL,(char*)NULL);
611 		grouplen = strlen(groupname);
612 		goto try_xgtitle;
613 	    }
614 	    spin_todo = dp->desc_sf.recent_cnt;
615 	    ret = srcfile_open(&dp->desc_sf, dp->grpdesc,
616 			       "newsgroups", dp->newsid);
617 	    if (spin_count > 0)
618 		dp->desc_sf.recent_cnt = spin_count;
619 	    /*set_datasrc(save_datasrc);*/
620 	}
621 	else
622 #endif
623 	    ret = srcfile_open(&dp->desc_sf, dp->grpdesc,
624 			       (char*)NULL, (char*)NULL);
625 	if (!ret) {
626 #ifdef SUPPORT_NNTP
627 	    if (dp->flags & DF_TMPGRPDESC) {
628 		dp->flags &= ~DF_TMPGRPDESC;
629 		UNLINK(dp->grpdesc);
630 	    }
631 #endif
632 	    free(dp->grpdesc);
633 	    dp->grpdesc = NULL;
634 	    return nullstr;
635 	}
636 #ifdef SUPPORT_NNTP
637 	if (ret == 2 || !dp->desc_sf.refetch_secs)
638 	    dp->flags |= DF_NOXGTITLE;
639 #endif
640     }
641 
642     grouplen = strlen(groupname);
643     data = hashfetch(dp->desc_sf.hp, groupname, grouplen);
644     if (data.dat_ptr) {
645 	LISTNODE* node = (LISTNODE*)data.dat_ptr;
646 	/*dp->act_sf.lp->recent = node;*/
647 	return node->data + data.dat_len + grouplen + 1;
648     }
649 
650 #ifdef SUPPORT_NNTP
651   try_xgtitle:
652 
653     if ((dp->flags & (DF_REMOTE|DF_NOXGTITLE)) == DF_REMOTE) {
654 	set_datasrc(dp);
655 	sprintf(ser_line, "XGTITLE %s", groupname);
656 	if (nntp_command(ser_line) > 0 && nntp_check() > 0) {
657 	    nntp_gets(buf, sizeof buf - 1);
658 	    if (nntp_at_list_end(buf))
659 		sprintf(buf, "%s \n", groupname);
660 	    else {
661 		nntp_finish_list();
662 		strcat(buf, "\n");
663 	    }
664 	    groupname = srcfile_append(&dp->desc_sf, buf, grouplen);
665 	    return groupname+grouplen+1;
666 	}
667 	dp->flags |= DF_NOXGTITLE;
668 	if (dp->desc_sf.lp->high == -1) {
669 	    srcfile_close(&dp->desc_sf);
670 	    if (dp->flags & DF_TMPGRPDESC)
671 		return find_grpdesc(dp, groupname);
672 	    free(dp->grpdesc);
673 	    dp->grpdesc = NULL;
674 	}
675     }
676 #endif
677     return nullstr;
678 }
679 
680 int
srcfile_open(sfp,filename,fetchcmd,server)681 srcfile_open(sfp, filename, fetchcmd, server)
682 SRCFILE* sfp;
683 char* filename;
684 char* fetchcmd;
685 char* server;
686 {
687     register unsigned offset;
688     register char* s;
689     HASHDATUM data;
690     long node_low;
691     int keylen, linelen;
692     FILE* fp;
693     char* lbp;
694 #ifdef SUPPORT_NNTP
695     time_t now = time((time_t*)NULL);
696     bool use_buffered_nntp_gets = 0;
697 
698     if (!filename)
699 	fp = NULL;
700     else if (server) {
701 	if (!sfp->refetch_secs) {
702 	    server = NULL;
703 	    fp = fopen(filename, "r");
704 	    spin_todo = 0;
705 	}
706 	else if (now - sfp->lastfetch > sfp->refetch_secs
707 	      && (sfp->refetch_secs != 2 || !sfp->lastfetch)) {
708 	    fp = fopen(filename, "w+");
709 	    if (fp) {
710 		printf("Getting %s file from %s.", fetchcmd, server);
711 		fflush(stdout);
712 		/* tell server we want the file */
713 		if (!(nntplink.flags & NNTP_NEW_CMD_OK))
714 		    use_buffered_nntp_gets = 1;
715 		else if (nntp_list(fetchcmd, nullstr, 0) < 0) {
716 		    printf("\nCan't get %s file from server: \n%s\n",
717 			   fetchcmd, ser_line) FLUSH;
718 		    termdown(2);
719 		    fclose(fp);
720 		    return 0;
721 		}
722 		sfp->lastfetch = now;
723 		if (netspeed > 8)
724 		    spin_todo = 0;
725 	    }
726 	}
727 	else {
728 	    server = NULL;
729 	    fp = fopen(filename, "r+");
730 	    if (!fp) {
731 		sfp->refetch_secs = 0;
732 		fp = fopen(filename, "r");
733 	    }
734 	    spin_todo = 0;
735 	}
736 	if (sfp->refetch_secs & 3)
737 	    sfp->refetch_secs += 365L*24*60*60;
738     }
739     else
740 #endif
741     {
742 	fp = fopen(filename, "r");
743 	spin_todo = 0;
744     }
745 
746     if (filename && fp == NULL) {
747 	printf(cantopen, filename) FLUSH;
748 	termdown(1);
749 	return 0;
750     }
751     setspin(spin_todo > 0? SPIN_BARGRAPH : SPIN_FOREGROUND);
752 
753     srcfile_close(sfp);
754 
755     /* Create a list with one character per item using a large chunk size. */
756     sfp->lp = new_list(0, 0, 1, SRCFILE_CHUNK_SIZE, 0, NULL);
757     sfp->hp = hashcreate(3001, srcfile_cmp);
758     sfp->fp = fp;
759 
760 #ifdef SUPPORT_NNTP
761     if (!filename) {
762 	(void) listnum2listitem(sfp->lp, 0);
763 	sfp->lp->high = -1;
764 	setspin(SPIN_OFF);
765 	return 1;
766     }
767 #endif
768 
769     lbp = listnum2listitem(sfp->lp, 0);
770     data.dat_ptr = (char*)sfp->lp->first;
771 
772     for (offset = 0, node_low = 0; ; offset += linelen, lbp += linelen) {
773 #ifdef SUPPORT_NNTP
774 	if (server) {
775 	    if (use_buffered_nntp_gets)
776 		use_buffered_nntp_gets = 0;
777 	    else if (nntp_gets(buf, sizeof buf - 1) < 0) {
778 		printf("\nError getting %s file.\n", fetchcmd) FLUSH;
779 		termdown(2);
780 		srcfile_close(sfp);
781 		setspin(SPIN_OFF);
782 		return 0;
783 	    }
784 	    if (nntp_at_list_end(buf))
785 		break;
786 	    strcat(buf,"\n");
787 	    fputs(buf, fp);
788 	    spin(200 * netspeed);
789 	}
790 #endif
791 	ElseIf (!fgets(buf, sizeof buf, fp))
792 	    break;
793 
794 	for (s = buf; *s && !isspace(*s); s++) ;
795 	if (!*s) {
796 	    linelen = 0;
797 	    continue;
798 	}
799 	keylen = s - buf;
800 	if (*++s != '\n' && isspace(*s)) {
801 	    while (*++s != '\n' && isspace(*s)) ;
802 	    strcpy(buf+keylen+1, s);
803 	    s = buf+keylen+1;
804 	}
805 	for (s++; *s && *s != '\n'; s++) {
806 	    if (AT_GREY_SPACE(s))
807 		*s = ' ';
808 	}
809 	linelen = s - buf + 1;
810 	if (*s != '\n') {
811 	    if (linelen == sizeof buf) {
812 		linelen = 0;
813 		continue;
814 	    }
815 	    *s++ = '\n';
816 	    *s = '\0';
817 	}
818 	if (offset + linelen > SRCFILE_CHUNK_SIZE) {
819 	    LISTNODE* node = sfp->lp->recent;
820 	    node_low += offset;
821 	    node->high = node_low - 1;
822 	    node->data_high = node->data + offset - 1;
823 	    offset = 0;
824 	    lbp = listnum2listitem(sfp->lp, node_low);
825 	    data.dat_ptr = (char*)sfp->lp->recent;
826 	}
827 	data.dat_len = offset;
828 	(void) bcopy(buf, lbp, linelen);
829 	hashstore(sfp->hp, buf, keylen, data);
830     }
831     sfp->lp->high = node_low + offset - 1;
832     setspin(SPIN_OFF);
833 
834 #ifdef SUPPORT_NNTP
835     if (server) {
836 	fflush(fp);
837 	if (ferror(fp)) {
838 	    printf("\nError writing the %s file %s.\n",fetchcmd,filename) FLUSH;
839 	    termdown(2);
840 	    srcfile_close(sfp);
841 	    return 0;
842 	}
843 	newline();
844     }
845 #endif
846     fseek(fp,0L,0);
847 
848     return server? 2 : 1;
849 }
850 
851 #ifdef SUPPORT_NNTP
852 char*
srcfile_append(sfp,bp,keylen)853 srcfile_append(sfp, bp, keylen)
854 SRCFILE* sfp;
855 char* bp;
856 int keylen;
857 {
858     LISTNODE* node;
859     long pos;
860     HASHDATUM data;
861     char* s;
862     char* lbp;
863     int linelen;
864 
865     pos = sfp->lp->high + 1;
866     lbp = listnum2listitem(sfp->lp, pos);
867     node = sfp->lp->recent;
868     data.dat_len = pos - node->low;
869 
870     s = bp + keylen + 1;
871     if (sfp->fp && sfp->refetch_secs && *s != '\n') {
872 	fseek(sfp->fp, 0, 2);
873 	fputs(bp, sfp->fp);
874     }
875 
876     if (*s != '\n' && isspace(*s)) {
877 	while (*++s != '\n' && isspace(*s)) ;
878 	strcpy(bp+keylen+1, s);
879 	s = bp+keylen+1;
880     }
881     for (s++; *s && *s != '\n'; s++) {
882 	if (AT_GREY_SPACE(s))
883 	    *s = ' ';
884     }
885     linelen = s - bp + 1;
886     if (*s != '\n') {
887 	*s++ = '\n';
888 	*s = '\0';
889     }
890     if (data.dat_len + linelen > SRCFILE_CHUNK_SIZE) {
891 	node->high = pos - 1;
892 	node->data_high = node->data + data.dat_len - 1;
893 	lbp = listnum2listitem(sfp->lp, pos);
894 	node = sfp->lp->recent;
895 	data.dat_len = 0;
896     }
897     data.dat_ptr = (char*)node;
898     (void) bcopy(bp, lbp, linelen);
899     hashstore(sfp->hp, bp, keylen, data);
900     sfp->lp->high = pos + linelen - 1;
901 
902     return lbp;
903 }
904 #endif /* SUPPORT_NNTP */
905 
906 #ifdef SUPPORT_NNTP
907 void
srcfile_end_append(sfp,filename)908 srcfile_end_append(sfp, filename)
909 SRCFILE* sfp;
910 char* filename;
911 {
912     if (sfp->fp && sfp->refetch_secs) {
913 	fflush(sfp->fp);
914 
915 	if (sfp->lastfetch) {
916 	    struct utimbuf ut;
917 	    time(&ut.actime);
918 	    ut.modtime = sfp->lastfetch;
919 	    (void) utime(filename, &ut);
920 	}
921     }
922 }
923 #endif /* SUPPORT_NNTP */
924 
925 void
srcfile_close(sfp)926 srcfile_close(sfp)
927 SRCFILE* sfp;
928 {
929     if (sfp->fp) {
930 	fclose(sfp->fp);
931 	sfp->fp = NULL;
932     }
933     if (sfp->lp) {
934 	delete_list(sfp->lp);
935 	sfp->lp = NULL;
936     }
937     if (sfp->hp) {
938 	hashdestroy(sfp->hp);
939 	sfp->hp = NULL;
940     }
941 }
942 
943 static int
srcfile_cmp(key,keylen,data)944 srcfile_cmp(key, keylen, data)
945 char* key;
946 int keylen;
947 HASHDATUM data;
948 {
949     /* We already know that the lengths are equal, just compare the strings */
950     return bcmp(key, ((LISTNODE*)data.dat_ptr)->data + data.dat_len, keylen);
951 }
952 
953 #ifdef EDIT_DISTANCE
954 
955 /* Edit Distance extension to trn
956  *
957  *	Mark Maimone (mwm@cmu.edu)
958  *	Carnegie Mellon Computer Science
959  *	9 May 1993
960  *
961  *	This code helps trn handle typos in newsgroup names much more
962  *   gracefully.  Instead of "... does not exist!!", it will pick the
963  *   nearest one, or offer you a choice if there are several options.
964  */
965 
966 /* find_close_match -- Finds the closest match for the string given in
967  * global ngname.  If found, the result will be the corrected string
968  * returned in that global.
969  *
970  * We compare the (presumably misspelled) newsgroup name with all legal
971  * newsgroups, using the Edit Distance metric.  The edit distance between
972  * two strings is the minimum number of simple operations required to
973  * convert one string to another (the implementation here supports INSERT,
974  * DELETE, CHANGE and SWAP).  This gives every legal newsgroup an integer
975  * rank.
976  *
977  * You might want to present all of the closest matches, and let the user
978  * choose among them.  But because I'm lazy I chose to only keep track of
979  * all with newsgroups with the *single* smallest error, in array ngptrs[].
980  * A more flexible approach would keep around the 10 best matches, whether
981  * or not they had precisely the same edit distance, but oh well.
982  */
983 
984 static char** ngptrs;		/* List of potential matches */
985 static int ngn;			/* Length of list in ngptrs[] */
986 static int best_match;		/* Value of best match */
987 
988 int
find_close_match()989 find_close_match()
990 {
991     DATASRC* dp;
992     int ret = 0;
993 
994     best_match = -1;
995     ngptrs = (char**)safemalloc(MAX_NG * sizeof (char*));
996     ngn = 0;
997 
998     /* Iterate over all legal newsgroups */
999     for (dp = datasrc_first(); dp && dp->name; dp = datasrc_next(dp)) {
1000 	if (dp->flags & DF_OPEN) {
1001 	    if (dp->act_sf.hp)
1002 		hashwalk(dp->act_sf.hp, check_distance, 0);
1003 	    else
1004 		ret = -1;
1005 	}
1006     }
1007 
1008     if (ret < 0) {
1009 	hashwalk(newsrc_hash, check_distance, 1);
1010 	ret = 0;
1011     }
1012 
1013     /* ngn is the number of possibilities.  If there's just one, go with it. */
1014 
1015     switch (ngn) {
1016         case 0:
1017 	    break;
1018 	case 1: {
1019 	    char* cp = index(ngptrs[0], ' ');
1020 	    if (cp)
1021 		*cp = '\0';
1022 #ifdef VERBOSE
1023 	    IF(verbose)
1024 		printf("(I assume you meant %s)\n", ngptrs[0]) FLUSH;
1025 	    ELSE
1026 #endif
1027 #ifdef TERSE
1028 		printf("(Using %s)\n", ngptrs[0]) FLUSH;
1029 #endif
1030 	    set_ngname(ngptrs[0]);
1031 	    if (cp)
1032 		*cp = ' ';
1033 	    ret = 1;
1034 	    break;
1035 	}
1036 	default:
1037 	    ret = get_near_miss();
1038 	    break;
1039     }
1040     free((char*)ngptrs);
1041     return ret;
1042 }
1043 
1044 static int
check_distance(len,data,newsrc_ptr)1045 check_distance(len, data, newsrc_ptr)
1046 int len;
1047 HASHDATUM* data;
1048 int newsrc_ptr;
1049 {
1050     int value;
1051     char* name;
1052 
1053     if (newsrc_ptr)
1054 	name = ((NGDATA*)data->dat_ptr)->rcline;
1055     else
1056 	name = ((LISTNODE*)data->dat_ptr)->data + data->dat_len;
1057 
1058     /* Efficiency: don't call edit_dist when the lengths are too different. */
1059     if (len < ngname_len) {
1060 	if (ngname_len - len > LENGTH_HACK)
1061 	    return 0;
1062     }
1063     else {
1064 	if (len - ngname_len > LENGTH_HACK)
1065 	    return 0;
1066     }
1067 
1068     value = edit_distn(ngname, ngname_len, name, len);
1069     if (value > MIN_DIST)
1070 	return 0;
1071 
1072     if (value < best_match)
1073 	ngn = 0;
1074     if (best_match < 0 || value <= best_match) {
1075 	int i;
1076 	for (i = 0; i < ngn; i++) {
1077 	    if (strEQ(name, ngptrs[i]))
1078 		return 0;
1079 	}
1080 	best_match = value;
1081 	if (ngn < MAX_NG)
1082 	    ngptrs[ngn++] = name;
1083     }
1084     return 0;
1085 }
1086 
1087 /* Now we've got several potential matches, and have to choose between them
1088 ** somehow.  Again, results will be returned in global ngname.
1089 */
1090 static int
get_near_miss()1091 get_near_miss()
1092 {
1093     char promptbuf[256];
1094     char options[MAX_NG+10];
1095     char* op = options;
1096     int i;
1097 
1098 #ifdef VERBOSE
1099     IF(verbose)
1100 	printf("However, here are some close matches:\n") FLUSH;
1101 #endif
1102     if (ngn > 9)
1103 	ngn = 9;	/* Since we're using single digits.... */
1104     for (i = 0; i < ngn; i++) {
1105 	char* cp = index(ngptrs[i], ' ');
1106 	if (cp)
1107 	    *cp = '\0';
1108 	printf("  %d.  %s\n", i+1, ngptrs[i]);
1109 	sprintf(op++, "%d", i+1);	/* Expensive, but avoids ASCII deps */
1110 	if (cp)
1111 	    *cp = ' ';
1112     }
1113     *op++ = 'n';
1114     *op = '\0';
1115 
1116 #ifdef VERBOSE
1117     IF(verbose)
1118 	sprintf(promptbuf, "Which of these would you like?");
1119     ELSE
1120 #endif
1121 #ifdef TERSE
1122 	sprintf(promptbuf, "Which?");
1123 #endif
1124 reask:
1125     in_char(promptbuf, 'A', options);
1126 #ifdef VERIFY
1127     printcmd();
1128 #endif
1129     putchar('\n') FLUSH;
1130     switch (*buf) {
1131         case 'n':
1132 	case 'N':
1133 	case 'q':
1134 	case 'Q':
1135 	case 'x':
1136 	case 'X':
1137 	    return 0;
1138 	case 'h':
1139 	case 'H':
1140 #ifdef VERBOSE
1141 	    IF(verbose)
1142 		fputs("  You entered an illegal newsgroup name, and these are the nearest possible\n  matches.  If you want one of these, then enter its number.  Otherwise\n  just say 'n'.\n", stdout) FLUSH;
1143 	    ELSE
1144 #endif
1145 #ifdef TERSE
1146 		fputs("Illegal newsgroup, enter a number or 'n'.\n", stdout) FLUSH;
1147 #endif
1148 	    goto reask;
1149 	default:
1150 	    if (isdigit(*buf)) {
1151 		char* s = index(options, *buf);
1152 
1153 		i = s ? (s - options) : ngn;
1154 		if (i >= 0 && i < ngn) {
1155 		    char* cp = index(ngptrs[i], ' ');
1156 		    if (cp)
1157 			*cp = '\0';
1158 		    set_ngname(ngptrs[i]);
1159 		    if (cp)
1160 			*cp = ' ';
1161 		    return 1;
1162 		}
1163 	    }
1164 	    fputs(hforhelp, stdout) FLUSH;
1165 	    break;
1166     }
1167 
1168     settle_down();
1169     goto reask;
1170 }
1171 
1172 #endif /* EDIT_DISTANCE */
1173