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,§ion,&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