1 /*
2  * $LynxId: HTNews.c,v 1.69 2011/06/11 12:10:55 tom Exp $
3  *
4  *			NEWS ACCESS				HTNews.c
5  *			===========
6  *
7  * History:
8  *	26 Sep 90	Written TBL
9  *	29 Nov 91	Downgraded to C, for portable implementation.
10  */
11 
12 #include <HTUtils.h>		/* Coding convention macros */
13 
14 #ifndef DISABLE_NEWS
15 
16 /* Implements:
17 */
18 #include <HTNews.h>
19 
20 #include <HTCJK.h>
21 #include <HTMIME.h>
22 #include <HTFont.h>
23 #include <HTFormat.h>
24 #include <HTTCP.h>
25 #include <LYUtils.h>
26 #include <LYStrings.h>
27 
28 #define NEWS_PORT 119		/* See rfc977 */
29 #define SNEWS_PORT 563		/* See Lou Montulli */
30 #define APPEND			/* Use append methods */
31 int HTNewsChunkSize = 30;	/* Number of articles for quick display */
32 int HTNewsMaxChunk = 40;	/* Largest number of articles in one window */
33 
34 #ifndef DEFAULT_NEWS_HOST
35 #define DEFAULT_NEWS_HOST "news"
36 #endif /* DEFAULT_NEWS_HOST */
37 
38 #ifndef NEWS_SERVER_FILE
39 #define NEWS_SERVER_FILE "/usr/local/lib/rn/server"
40 #endif /* NEWS_SERVER_FILE */
41 
42 #ifndef NEWS_AUTH_FILE
43 #define NEWS_AUTH_FILE ".newsauth"
44 #endif /* NEWS_AUTH_FILE */
45 
46 #ifdef USE_SSL
47 static SSL *Handle = NULL;
48 static int channel_s = 1;
49 
50 #define NEWS_NETWRITE(sock, buff, size) \
51 	(Handle ? SSL_write(Handle, buff, size) : NETWRITE(sock, buff, size))
52 #define NEWS_NETCLOSE(sock) \
53 	{ (void)NETCLOSE(sock); if (Handle) { SSL_free(Handle); Handle = NULL; } }
54 static int HTNewsGetCharacter(void);
55 
56 #define NEXT_CHAR HTNewsGetCharacter()
57 #else
58 #define NEWS_NETWRITE  NETWRITE
59 #define NEWS_NETCLOSE  NETCLOSE
60 #define NEXT_CHAR HTGetCharacter()
61 #endif /* USE_SSL */
62 
63 #include <HTML.h>
64 #include <HTAccess.h>
65 #include <HTParse.h>
66 #include <HTFormat.h>
67 #include <HTAlert.h>
68 
69 #include <LYNews.h>
70 #include <LYGlobalDefs.h>
71 #include <LYLeaks.h>
72 
73 #define SnipIn(d,fmt,len,s)      sprintf(d, fmt,      (int)sizeof(d)-len, s)
74 #define SnipIn2(d,fmt,tag,len,s) sprintf(d, fmt, tag, (int)sizeof(d)-len, s)
75 
76 struct _HTStructured {
77     const HTStructuredClass *isa;
78     /* ... */
79 };
80 
81 #define LINE_LENGTH 512		/* Maximum length of line of ARTICLE etc */
82 #define GROUP_NAME_LENGTH	256	/* Maximum length of group name */
83 
84 /*
85  *  Module-wide variables.
86  */
87 char *HTNewsHost = NULL;	/* Default host */
88 static char *NewsHost = NULL;	/* Current host */
89 static char *NewsHREF = NULL;	/* Current HREF prefix */
90 static int s;			/* Socket for NewsHost */
91 static int HTCanPost = FALSE;	/* Current POST permission */
92 static char response_text[LINE_LENGTH + 1];	/* Last response */
93 
94 static HTStructured *target;	/* The output sink */
95 static HTStructuredClass targetClass;	/* Copy of fn addresses */
96 static HTStream *rawtarget = NULL;	/* The output sink for rawtext */
97 static HTStreamClass rawtargetClass;	/* Copy of fn addresses */
98 static int diagnostic;		/* level: 0=none 2=source */
99 static BOOL rawtext = NO;	/* Flag: HEAD or -mime_headers */
100 static HTList *NNTP_AuthInfo = NULL;	/* AUTHINFO database */
101 static char *name = NULL;
102 static char *address = NULL;
103 static char *dbuf = NULL;	/* dynamic buffer for long messages etc. */
104 
105 #define PUTC(c) (*targetClass.put_character)(target, c)
106 #define PUTS(s) (*targetClass.put_string)(target, s)
107 #define RAW_PUTS(s) (*rawtargetClass.put_string)(rawtarget, s)
108 #define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
109 #define END(e) (*targetClass.end_element)(target, e, 0)
110 #define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
111 			(*targetClass.end_element)(target, e, 0)
112 #define FREE_TARGET if (rawtext) (*rawtargetClass._free)(rawtarget); \
113 			else (*targetClass._free)(target)
114 #define ABORT_TARGET if (rawtext) (*rawtargetClass._abort)(rawtarget, NULL); \
115 			else (*targetClass._abort)(target, NULL)
116 
117 typedef struct _NNTPAuth {
118     char *host;
119     char *user;
120     char *pass;
121 } NNTPAuth;
122 
123 #ifdef LY_FIND_LEAKS
free_news_globals(void)124 static void free_news_globals(void)
125 {
126     if (s >= 0) {
127 	NEWS_NETCLOSE(s);
128 	s = -1;
129     }
130     FREE(HTNewsHost);
131     FREE(NewsHost);
132     FREE(NewsHREF);
133     FREE(name);
134     FREE(address);
135     FREE(dbuf);
136 }
137 #endif /* LY_FIND_LEAKS */
138 
free_NNTP_AuthInfo(void)139 static void free_NNTP_AuthInfo(void)
140 {
141     HTList *cur = NNTP_AuthInfo;
142     NNTPAuth *auth = NULL;
143 
144     if (!cur)
145 	return;
146 
147     while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
148 	FREE(auth->host);
149 	FREE(auth->user);
150 	FREE(auth->pass);
151 	FREE(auth);
152     }
153     HTList_delete(NNTP_AuthInfo);
154     NNTP_AuthInfo = NULL;
155     return;
156 }
157 
158 /*
159  * Initialize the authentication list by loading the user's $HOME/.newsauth
160  * file.  That file is part of tin's configuration and is used by a few other
161  * programs.
162  */
load_NNTP_AuthInfo(void)163 static void load_NNTP_AuthInfo(void)
164 {
165     FILE *fp;
166     char fname[LY_MAXPATH];
167     char buffer[LINE_LENGTH + 1];
168 
169     LYAddPathToHome(fname, sizeof(fname), NEWS_AUTH_FILE);
170 
171     if ((fp = fopen(fname, "r")) != 0) {
172 	while (fgets(buffer, (int) sizeof(buffer), fp) != 0) {
173 	    char the_host[LINE_LENGTH + 1];
174 	    char the_pass[LINE_LENGTH + 1];
175 	    char the_user[LINE_LENGTH + 1];
176 
177 	    if (sscanf(buffer, "%s%s%s", the_host, the_pass, the_user) == 3
178 		&& strlen(the_host) != 0
179 		&& strlen(the_pass) != 0
180 		&& strlen(the_user) != 0) {
181 		NNTPAuth *auth = typecalloc(NNTPAuth);
182 
183 		if (auth == NULL)
184 		    break;
185 		StrAllocCopy(auth->host, the_host);
186 		StrAllocCopy(auth->pass, the_pass);
187 		StrAllocCopy(auth->user, the_user);
188 
189 		HTList_appendObject(NNTP_AuthInfo, auth);
190 	    }
191 	}
192 	fclose(fp);
193     }
194 }
195 
HTGetNewsHost(void)196 const char *HTGetNewsHost(void)
197 {
198     return HTNewsHost;
199 }
200 
HTSetNewsHost(const char * value)201 void HTSetNewsHost(const char *value)
202 {
203     StrAllocCopy(HTNewsHost, value);
204 }
205 
206 /*	Initialisation for this module
207  *	------------------------------
208  *
209  *	Except on the NeXT, we pick up the NewsHost name from
210  *
211  *	1.	Environment variable NNTPSERVER
212  *	2.	File NEWS_SERVER_FILE
213  *	3.	Compilation time macro DEFAULT_NEWS_HOST
214  *	4.	Default to "news"
215  *
216  *	On the NeXT, we pick up the NewsHost name from, in order:
217  *
218  *	1.	WorldWideWeb default "NewsHost"
219  *	2.	Global default "NewsHost"
220  *	3.	News default "NewsHost"
221  *	4.	Compilation time macro DEFAULT_NEWS_HOST
222  *	5.	Default to "news"
223  */
224 static BOOL initialized = NO;
initialize(void)225 static BOOL initialize(void)
226 {
227 #ifdef NeXTStep
228     char *cp = NULL;
229 #endif
230 
231     /*
232      * Get name of Host.
233      */
234 #ifdef NeXTStep
235     if ((cp = NXGetDefaultValue("WorldWideWeb", "NewsHost")) == 0) {
236 	if ((cp = NXGetDefaultValue("News", "NewsHost")) == 0) {
237 	    StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
238 	}
239     }
240     if (cp) {
241 	StrAllocCopy(HTNewsHost, cp);
242 	cp = NULL;
243     }
244 #else
245     if (LYGetEnv("NNTPSERVER")) {
246 	StrAllocCopy(HTNewsHost, LYGetEnv("NNTPSERVER"));
247 	CTRACE((tfp, "HTNews: NNTPSERVER defined as `%s'\n",
248 		HTNewsHost));
249     } else {
250 	FILE *fp = fopen(NEWS_SERVER_FILE, TXT_R);
251 
252 	if (fp) {
253 	    char server_name[MAXHOSTNAMELEN + 1];
254 
255 	    if (fgets(server_name, (int) sizeof server_name, fp) != NULL) {
256 		char *p = strchr(server_name, '\n');
257 
258 		if (p != NULL)
259 		    *p = '\0';
260 		StrAllocCopy(HTNewsHost, server_name);
261 		CTRACE((tfp, "HTNews: File %s defines news host as `%s'\n",
262 			NEWS_SERVER_FILE, HTNewsHost));
263 	    }
264 	    fclose(fp);
265 	}
266     }
267     if (!HTNewsHost)
268 	StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
269 #endif /* NeXTStep */
270 
271     s = -1;			/* Disconnected */
272 #ifdef LY_FIND_LEAKS
273     atexit(free_news_globals);
274 #endif
275     return YES;
276 }
277 
278 /*	Send NNTP Command line to remote host & Check Response
279  *	------------------------------------------------------
280  *
281  * On entry,
282  *	command points to the command to be sent, including CRLF, or is null
283  *		pointer if no command to be sent.
284  * On exit,
285  *	Negative status indicates transmission error, socket closed.
286  *	Positive status is an NNTP status.
287  */
response(char * command)288 static int response(char *command)
289 {
290     int result;
291     char *p = response_text;
292     int ich;
293 
294     if (command) {
295 	int status;
296 	int length = (int) strlen(command);
297 
298 	CTRACE((tfp, "NNTP command to be sent: %s", command));
299 #ifdef NOT_ASCII
300 	{
301 	    const char *p2;
302 	    char *q;
303 	    char ascii[LINE_LENGTH + 1];
304 
305 	    for (p2 = command, q = ascii; *p2; p2++, q++) {
306 		*q = TOASCII(*p2);
307 	    }
308 	    status = NEWS_NETWRITE(s, ascii, length);
309 	}
310 #else
311 	status = (int) NEWS_NETWRITE(s, (char *) command, length);
312 #endif /* NOT_ASCII */
313 	if (status < 0) {
314 	    CTRACE((tfp, "HTNews: Unable to send command. Disconnecting.\n"));
315 	    NEWS_NETCLOSE(s);
316 	    s = -1;
317 	    return status;
318 	}			/* if bad status */
319     }
320     /* if command to be sent */
321     for (;;) {
322 	ich = NEXT_CHAR;
323 	if (((*p++ = (char) ich) == LF) ||
324 	    (p == &response_text[LINE_LENGTH])) {
325 	    *--p = '\0';	/* Terminate the string */
326 	    CTRACE((tfp, "NNTP Response: %s\n", response_text));
327 	    sscanf(response_text, "%d", &result);
328 	    return result;
329 	}
330 	/* if end of line */
331 	if (ich == EOF) {
332 	    *(p - 1) = '\0';
333 	    if (interrupted_in_htgetcharacter) {
334 		CTRACE((tfp,
335 			"HTNews: Interrupted on read, closing socket %d\n",
336 			s));
337 	    } else {
338 		CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n",
339 			s));
340 	    }
341 	    NEWS_NETCLOSE(s);	/* End of file, close socket */
342 	    s = -1;
343 	    if (interrupted_in_htgetcharacter) {
344 		interrupted_in_htgetcharacter = 0;
345 		return (HT_INTERRUPTED);
346 	    }
347 	    return ((int) EOF);	/* End of file on response */
348 	}
349     }				/* Loop over characters */
350 }
351 
352 /*	Case insensitive string comparisons
353  *	-----------------------------------
354  *
355  * On entry,
356  *	template must be already in upper case.
357  *	unknown may be in upper or lower or mixed case to match.
358  */
match(const char * unknown,const char * ctemplate)359 static BOOL match(const char *unknown, const char *ctemplate)
360 {
361     const char *u = unknown;
362     const char *t = ctemplate;
363 
364     for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) ;		/* Find mismatch or end */
365     return (BOOL) (*t == 0);	/* OK if end of template */
366 }
367 
368 typedef enum {
369     NNTPAUTH_ERROR = 0,		/* general failure */
370     NNTPAUTH_OK = 281,		/* authenticated successfully */
371     NNTPAUTH_CLOSE = 502	/* server probably closed connection */
372 } NNTPAuthResult;
373 
374 /*
375  *  This function handles nntp authentication. - FM
376  */
HTHandleAuthInfo(char * host)377 static NNTPAuthResult HTHandleAuthInfo(char *host)
378 {
379     HTList *cur = NULL;
380     NNTPAuth *auth = NULL;
381     char *UserName = NULL;
382     char *PassWord = NULL;
383     char *msg = NULL;
384     char buffer[512];
385     int status, tries;
386 
387     /*
388      * Make sure we have a host.  - FM
389      */
390     if (isEmpty(host))
391 	return NNTPAUTH_ERROR;
392 
393     /*
394      * Check for an existing authorization entry.  - FM
395      */
396     if (NNTP_AuthInfo == NULL) {
397 	NNTP_AuthInfo = HTList_new();
398 	load_NNTP_AuthInfo();
399 #ifdef LY_FIND_LEAKS
400 	atexit(free_NNTP_AuthInfo);
401 #endif
402     }
403 
404     cur = NNTP_AuthInfo;
405     while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
406 	if (!strcmp(auth->host, host)) {
407 	    UserName = auth->user;
408 	    PassWord = auth->pass;
409 	    break;
410 	}
411     }
412 
413     /*
414      * Handle the username.  - FM
415      */
416     buffer[sizeof(buffer) - 1] = '\0';
417     tries = 3;
418 
419     while (tries) {
420 	if (UserName == NULL) {
421 	    HTSprintf0(&msg, gettext("Username for news host '%s':"), host);
422 	    UserName = HTPrompt(msg, NULL);
423 	    FREE(msg);
424 	    if (!(UserName && *UserName)) {
425 		FREE(UserName);
426 		return NNTPAUTH_ERROR;
427 	    }
428 	}
429 	sprintf(buffer, "AUTHINFO USER %.*s%c%c",
430 		(int) sizeof(buffer) - 17, UserName, CR, LF);
431 	if ((status = response(buffer)) < 0) {
432 	    if (status == HT_INTERRUPTED)
433 		_HTProgress(CONNECTION_INTERRUPTED);
434 	    else
435 		HTAlert(FAILED_CONNECTION_CLOSED);
436 	    if (auth) {
437 		if (auth->user != UserName) {
438 		    FREE(auth->user);
439 		    auth->user = UserName;
440 		}
441 	    } else {
442 		FREE(UserName);
443 	    }
444 	    return NNTPAUTH_CLOSE;
445 	}
446 	if (status == 281) {
447 	    /*
448 	     * Username is accepted and no password is required.  - FM
449 	     */
450 	    if (auth) {
451 		if (auth->user != UserName) {
452 		    FREE(auth->user);
453 		    auth->user = UserName;
454 		}
455 	    } else {
456 		/*
457 		 * Store the accepted username and no password.  - FM
458 		 */
459 		if ((auth = typecalloc(NNTPAuth)) != NULL) {
460 		    StrAllocCopy(auth->host, host);
461 		    auth->user = UserName;
462 		    HTList_appendObject(NNTP_AuthInfo, auth);
463 		}
464 	    }
465 	    return NNTPAUTH_OK;
466 	}
467 	if (status != 381) {
468 	    /*
469 	     * Not success, nor a request for the password, so it must be an
470 	     * error.  - FM
471 	     */
472 	    HTAlert(response_text);
473 	    tries--;
474 	    if ((tries > 0) && HTConfirm(gettext("Change username?"))) {
475 		if (!auth || auth->user != UserName) {
476 		    FREE(UserName);
477 		}
478 		if ((UserName = HTPrompt(gettext("Username:"), UserName))
479 		    != NULL &&
480 		    *UserName) {
481 		    continue;
482 		}
483 	    }
484 	    if (auth) {
485 		if (auth->user != UserName) {
486 		    FREE(auth->user);
487 		}
488 		FREE(auth->pass);
489 	    }
490 	    FREE(UserName);
491 	    return NNTPAUTH_ERROR;
492 	}
493 	break;
494     }
495 
496     if (status == 381) {
497 	/*
498 	 * Handle the password.  - FM
499 	 */
500 	tries = 3;
501 	while (tries) {
502 	    if (PassWord == NULL) {
503 		HTSprintf0(&msg, gettext("Password for news host '%s':"), host);
504 		PassWord = HTPromptPassword(msg);
505 		FREE(msg);
506 		if (!(PassWord && *PassWord)) {
507 		    FREE(PassWord);
508 		    return NNTPAUTH_ERROR;
509 		}
510 	    }
511 	    sprintf(buffer, "AUTHINFO PASS %.*s%c%c",
512 		    (int) sizeof(buffer) - 17, PassWord, CR, LF);
513 	    if ((status = response(buffer)) < 0) {
514 		if (status == HT_INTERRUPTED) {
515 		    _HTProgress(CONNECTION_INTERRUPTED);
516 		} else {
517 		    HTAlert(FAILED_CONNECTION_CLOSED);
518 		}
519 		if (auth) {
520 		    if (auth->user != UserName) {
521 			FREE(auth->user);
522 			auth->user = UserName;
523 		    }
524 		    if (auth->pass != PassWord) {
525 			FREE(auth->pass);
526 			auth->pass = PassWord;
527 		    }
528 		} else {
529 		    FREE(UserName);
530 		    FREE(PassWord);
531 		}
532 		return NNTPAUTH_CLOSE;
533 	    }
534 	    if (status == 502) {
535 		/*
536 		 * That's what INN's nnrpd returns.  It closes the connection
537 		 * after this.  - kw
538 		 */
539 		HTAlert(response_text);
540 		if (auth) {
541 		    if (auth->user == UserName)
542 			UserName = NULL;
543 		    FREE(auth->user);
544 		    if (auth->pass == PassWord)
545 			PassWord = NULL;
546 		    FREE(auth->pass);
547 		}
548 		FREE(UserName);
549 		FREE(PassWord);
550 		return NNTPAUTH_CLOSE;
551 	    }
552 	    if (status == 281) {
553 		/*
554 		 * Password also is accepted, and everything has been stored.
555 		 * - FM
556 		 */
557 		if (auth) {
558 		    if (auth->user != UserName) {
559 			FREE(auth->user);
560 			auth->user = UserName;
561 		    }
562 		    if (auth->pass != PassWord) {
563 			FREE(auth->pass);
564 			auth->pass = PassWord;
565 		    }
566 		} else {
567 		    if ((auth = typecalloc(NNTPAuth)) != NULL) {
568 			StrAllocCopy(auth->host, host);
569 			auth->user = UserName;
570 			auth->pass = PassWord;
571 			HTList_appendObject(NNTP_AuthInfo, auth);
572 		    }
573 		}
574 		return NNTPAUTH_OK;
575 	    }
576 	    /*
577 	     * Not success, so it must be an error.  - FM
578 	     */
579 	    HTAlert(response_text);
580 	    if (!auth || auth->pass != PassWord) {
581 		FREE(PassWord);
582 	    } else {
583 		PassWord = NULL;
584 	    }
585 	    tries--;
586 	    if ((tries > 0) && HTConfirm(gettext("Change password?"))) {
587 		continue;
588 	    }
589 	    if (auth) {
590 		if (auth->user == UserName)
591 		    UserName = NULL;
592 		FREE(auth->user);
593 		FREE(auth->pass);
594 	    }
595 	    FREE(UserName);
596 	    break;
597 	}
598     }
599 
600     return NNTPAUTH_ERROR;
601 }
602 
603 /*	Find Author's name in mail address
604  *	----------------------------------
605  *
606  * On exit,
607  *	Returns allocated string which cannot be freed by the
608  *	calling function, and is reallocated on subsequent calls
609  *	to this function.
610  *
611  * For example, returns "Tim Berners-Lee" if given any of
612  *	" Tim Berners-Lee <tim@online.cern.ch> "
613  *  or	" tim@online.cern.ch ( Tim Berners-Lee ) "
614  */
author_name(char * email)615 static char *author_name(char *email)
616 {
617     char *p, *e;
618 
619     StrAllocCopy(name, email);
620     CTRACE((tfp, "Trying to find name in: %s\n", name));
621 
622     if ((p = strrchr(name, '(')) && (e = strrchr(name, ')'))) {
623 	if (e > p) {
624 	    *e = '\0';		/* Chop off everything after the ')'  */
625 	    return HTStrip(p + 1);	/* Remove leading and trailing spaces */
626 	}
627     }
628 
629     if ((p = strrchr(name, '<')) && (e = strrchr(name, '>'))) {
630 	if (e++ > p) {
631 	    while ((*p++ = *e++) != 0)	/* Remove <...> */
632 		;
633 	    return HTStrip(name);	/* Remove leading and trailing spaces */
634 	}
635     }
636 
637     return HTStrip(name);	/* Default to the whole thing */
638 }
639 
640 /*	Find Author's mail address
641  *	--------------------------
642  *
643  * On exit,
644  *	Returns allocated string which cannot be freed by the
645  *	calling function, and is reallocated on subsequent calls
646  *	to this function.
647  *
648  * For example, returns "montulli@spaced.out.galaxy.net" if given any of
649  *	" Lou Montulli <montulli@spaced.out.galaxy.net> "
650  *  or	" montulli@spaced.out.galaxy.net ( Lou "The Stud" Montulli ) "
651  */
author_address(char * email)652 static char *author_address(char *email)
653 {
654     char *p, *at, *e;
655 
656     StrAllocCopy(address, email);
657     CTRACE((tfp, "Trying to find address in: %s\n", address));
658 
659     if ((p = strrchr(address, '<'))) {
660 	if ((e = strrchr(p, '>')) && (at = strrchr(p, '@'))) {
661 	    if (at < e) {
662 		*e = '\0';	/* Remove > */
663 		return HTStrip(p + 1);	/* Remove leading and trailing spaces */
664 	    }
665 	}
666     }
667 
668     if ((p = strrchr(address, '(')) &&
669 	(e = strrchr(address, ')')) && (at = strchr(address, '@'))) {
670 	if (e > p && at < e) {
671 	    *p = '\0';		/* Chop off everything after the ')'  */
672 	    return HTStrip(address);	/* Remove leading and trailing spaces */
673 	}
674     }
675 
676     if ((at = strrchr(address, '@')) && at > address) {
677 	p = (at - 1);
678 	e = (at + 1);
679 	while (p > address && !isspace(UCH(*p)))
680 	    p--;
681 	while (*e && !isspace(UCH(*e)))
682 	    e++;
683 	*e = 0;
684 	return HTStrip(p);
685     }
686 
687     /*
688      * Default to the first word.
689      */
690     p = address;
691     while (isspace(UCH(*p)))
692 	p++;			/* find first non-space */
693     e = p;
694     while (!isspace(UCH(*e)) && *e != '\0')
695 	e++;			/* find next space or end */
696     *e = '\0';			/* terminate space */
697 
698     return (p);
699 }
700 
701 /*	Start anchor element
702  *	--------------------
703  */
start_anchor(const char * href)704 static void start_anchor(const char *href)
705 {
706     BOOL present[HTML_A_ATTRIBUTES];
707     const char *value[HTML_A_ATTRIBUTES];
708     int i;
709 
710     for (i = 0; i < HTML_A_ATTRIBUTES; i++)
711 	present[i] = (BOOL) (i == HTML_A_HREF);
712     value[HTML_A_HREF] = href;
713     (*targetClass.start_element) (target, HTML_A, present, value, -1, 0);
714 }
715 
716 /*	Start link element
717  *	------------------
718  */
start_link(const char * href,const char * rev)719 static void start_link(const char *href, const char *rev)
720 {
721     BOOL present[HTML_LINK_ATTRIBUTES];
722     const char *value[HTML_LINK_ATTRIBUTES];
723     int i;
724 
725     for (i = 0; i < HTML_LINK_ATTRIBUTES; i++)
726 	present[i] = (BOOL) (i == HTML_LINK_HREF || i == HTML_LINK_REV);
727     value[HTML_LINK_HREF] = href;
728     value[HTML_LINK_REV] = rev;
729     (*targetClass.start_element) (target, HTML_LINK, present, value, -1, 0);
730 }
731 
732 /*	Start list element
733  *	------------------
734  */
start_list(int seqnum)735 static void start_list(int seqnum)
736 {
737     BOOL present[HTML_OL_ATTRIBUTES];
738     const char *value[HTML_OL_ATTRIBUTES];
739     char SeqNum[20];
740     int i;
741 
742     for (i = 0; i < HTML_OL_ATTRIBUTES; i++)
743 	present[i] = (BOOL) (i == HTML_OL_SEQNUM || i == HTML_OL_START);
744     sprintf(SeqNum, "%d", seqnum);
745     value[HTML_OL_SEQNUM] = SeqNum;
746     value[HTML_OL_START] = SeqNum;
747     (*targetClass.start_element) (target, HTML_OL, present, value, -1, 0);
748 }
749 
750 /*	Paste in an Anchor
751  *	------------------
752  *
753  *
754  * On entry,
755  *	HT	has a selection of zero length at the end.
756  *	text	points to the text to be put into the file, 0 terminated.
757  *	addr	points to the hypertext reference address,
758  *		terminated by white space, comma, NULL or '>'
759  */
write_anchor(const char * text,const char * addr)760 static void write_anchor(const char *text, const char *addr)
761 {
762     char href[LINE_LENGTH + 1];
763     const char *p;
764     char *q;
765 
766     for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++) {
767 	;
768     }
769     if (strlen(NewsHREF) + (size_t) (p - addr) + 1 < sizeof(href)) {
770 	q = href;
771 	strcpy(q, NewsHREF);
772 	/* Make complete hypertext reference */
773 	StrNCat(q, addr, (size_t) (p - addr));
774     } else {
775 	q = NULL;
776 	HTSprintf0(&q, "%s%.*s", NewsHREF, (int) (p - addr), addr);
777     }
778 
779     start_anchor(q);
780     PUTS(text);
781     END(HTML_A);
782 
783     if (q != href)
784 	FREE(q);
785 }
786 
787 /*	Write list of anchors
788  *	---------------------
789  *
790  *	We take a pointer to a list of objects, and write out each,
791  *	generating an anchor for each.
792  *
793  * On entry,
794  *	HT	has a selection of zero length at the end.
795  *	text	points to a comma or space separated list of addresses.
796  * On exit,
797  *	*text	is NOT any more chopped up into substrings.
798  */
write_anchors(char * text)799 static void write_anchors(char *text)
800 {
801     char *start = text;
802     char *end;
803     char c;
804 
805     for (;;) {
806 	for (; *start && (WHITE(*start)); start++) ;	/* Find start */
807 	if (!*start)
808 	    return;		/* (Done) */
809 	for (end = start;
810 	     *end && (*end != ' ') && (*end != ','); end++) ;	/* Find end */
811 	if (*end)
812 	    end++;		/* Include comma or space but not NULL */
813 	c = *end;
814 	*end = '\0';
815 	if (*start == '<')
816 	    write_anchor(start, start + 1);
817 	else
818 	    write_anchor(start, start);
819 	START(HTML_BR);
820 	*end = c;
821 	start = end;		/* Point to next one */
822     }
823 }
824 
825 /*	Abort the connection					abort_socket
826  *	--------------------
827  */
abort_socket(void)828 static void abort_socket(void)
829 {
830     CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n", s));
831     NEWS_NETCLOSE(s);		/* End of file, close socket */
832     if (rawtext) {
833 	RAW_PUTS("Network Error: connection lost\n");
834     } else {
835 	PUTS("Network Error: connection lost");
836 	PUTC('\n');
837     }
838     s = -1;			/* End of file on response */
839 }
840 
841 /*
842  *  Determine if a line is a valid header line.			valid_header
843  *  -------------------------------------------
844  */
valid_header(char * line)845 static BOOLEAN valid_header(char *line)
846 {
847     char *colon, *space;
848 
849     /*
850      * Blank or tab in first position implies this is a continuation header.
851      */
852     if (line[0] == ' ' || line[0] == '\t')
853 	return (TRUE);
854 
855     /*
856      * Just check for initial letter, colon, and space to make sure we discard
857      * only invalid headers.
858      */
859     colon = strchr(line, ':');
860     space = strchr(line, ' ');
861     if (isalpha(UCH(line[0])) && colon && space == colon + 1)
862 	return (TRUE);
863 
864     /*
865      * Anything else is a bad header -- it should be ignored.
866      */
867     return (FALSE);
868 }
869 
870 /*	post in an Article					post_article
871  *	------------------
872  *			(added by FM, modeled on Lynx' previous mini inews)
873  *
874  *	Note the termination condition of a single dot on a line by itself.
875  *
876  *  On entry,
877  *	s		Global socket number is OK
878  *	postfile	file with header and article to post.
879  */
post_article(char * postfile)880 static void post_article(char *postfile)
881 {
882     char line[512];
883     char buf[512];
884     char crlf[3];
885     char *cp;
886     int status;
887     FILE *fd;
888     int in_header = 1, seen_header = 0, seen_fromline = 0;
889     int blen = 0, llen = 0;
890 
891     /*
892      * Open the temporary file with the nntp headers and message body.  - FM
893      */
894     if ((fd = fopen(NonNull(postfile), TXT_R)) == NULL) {
895 	HTAlert(FAILED_CANNOT_OPEN_POST);
896 	return;
897     }
898 
899     /*
900      * Read the temporary file and post in maximum 512 byte chunks.  - FM
901      */
902     buf[0] = '\0';
903     sprintf(crlf, "%c%c", CR, LF);
904     while (fgets(line, (int) sizeof(line) - 2, fd) != NULL) {
905 	if ((cp = strchr(line, '\n')) != NULL)
906 	    *cp = '\0';
907 	if (line[0] == '.') {
908 	    /*
909 	     * A single '.' means end of transmission for nntp.  Lead dots on
910 	     * lines normally are trimmed and the EOF is not registered if the
911 	     * dot was not followed by CRLF.  We prepend an extra dot for any
912 	     * line beginning with one, to retain the one intended, as well as
913 	     * avoid a false EOF signal.  We know we have room for it in the
914 	     * buffer, because we normally send when it would exceed 510.  - FM
915 	     */
916 	    strcat(buf, ".");
917 	    blen++;
918 	}
919 	llen = (int) strlen(line);
920 	if (in_header && !strncasecomp(line, "From:", 5)) {
921 	    seen_header = 1;
922 	    seen_fromline = 1;
923 	}
924 	if (in_header && line[0] == '\0') {
925 	    if (seen_header) {
926 		in_header = 0;
927 		if (!seen_fromline) {
928 		    if (blen >= (int) sizeof(buf) - 35) {
929 			IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
930 			buf[blen = 0] = 0;
931 		    }
932 		    strcat(buf, "From: anonymous@nowhere.you.know");
933 		    strcat(buf, crlf);
934 		    blen += 34;
935 		}
936 	    } else {
937 		continue;
938 	    }
939 	} else if (in_header) {
940 	    if (valid_header(line)) {
941 		seen_header = 1;
942 	    } else {
943 		continue;
944 	    }
945 	}
946 	strcat(line, crlf);
947 	llen += 2;
948 	if ((blen + llen) >= (int) sizeof(buf) - 1) {
949 	    IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
950 	    buf[blen = 0] = 0;
951 	}
952 	strcat(buf, line);
953 	blen += llen;
954     }
955     fclose(fd);
956     HTSYS_remove(postfile);
957 
958     /*
959      * Send the nntp EOF and get the server's response.  - FM
960      */
961     if (blen >= (int) sizeof(buf) - 4) {
962 	IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
963 	buf[blen = 0] = 0;
964     }
965     strcat(buf, ".");
966     strcat(buf, crlf);
967     blen += 3;
968     IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
969 
970     status = response(NULL);
971     if (status == 240) {
972 	/*
973 	 * Successful post.  - FM
974 	 */
975 	HTProgress(response_text);
976     } else {
977 	/*
978 	 * Shucks, something went wrong.  - FM
979 	 */
980 	HTAlert(response_text);
981     }
982 }
983 
984 #ifdef NEWS_DEBUG
985 /* for DEBUG 1997/11/07 (Fri) 17:20:16 */
debug_print(unsigned char * p)986 void debug_print(unsigned char *p)
987 {
988     while (*p) {
989 	if (*p == '\0')
990 	    break;
991 	if (*p == 0x1b)
992 	    printf("[ESC]");
993 	else if (*p == '\n')
994 	    printf("[NL]");
995 	else if (*p < ' ' || *p >= 0x80)
996 	    printf("(%02x)", *p);
997 	else
998 	    putchar(*p);
999 	p++;
1000     }
1001     printf("]\n");
1002 }
1003 #endif
1004 
decode_mime(char ** str)1005 static char *decode_mime(char **str)
1006 {
1007     static char empty[] = "";
1008 
1009 #ifdef SH_EX
1010     if (HTCJK != JAPANESE)
1011 	return *str;
1012 #endif
1013     HTmmdecode(str, *str);
1014     return HTrjis(str, *str) ? *str : empty;
1015 }
1016 
1017 /*	Read in an Article					read_article
1018  *	------------------
1019  *
1020  *	Note the termination condition of a single dot on a line by itself.
1021  *	RFC 977 specifies that the line "folding" of RFC850 is not used, so we
1022  *	do not handle it here.
1023  *
1024  * On entry,
1025  *	s	Global socket number is OK
1026  *	HT	Global hypertext object is ready for appending text
1027  */
read_article(HTParentAnchor * thisanchor)1028 static int read_article(HTParentAnchor *thisanchor)
1029 {
1030     char line[LINE_LENGTH + 1];
1031     char *full_line = NULL;
1032     char *subject = NULL;	/* Subject string           */
1033     char *from = NULL;		/* From string              */
1034     char *replyto = NULL;	/* Reply-to string          */
1035     char *date = NULL;		/* Date string              */
1036     char *organization = NULL;	/* Organization string      */
1037     char *references = NULL;	/* Hrefs for other articles */
1038     char *newsgroups = NULL;	/* Newsgroups list          */
1039     char *followupto = NULL;	/* Followup list            */
1040     char *href = NULL;
1041     char *p = line;
1042     char *cp;
1043     const char *ccp;
1044     BOOL done = NO;
1045 
1046     /*
1047      * Read in the HEADer of the article.
1048      *
1049      * The header fields are either ignored, or formatted and put into the
1050      * text.
1051      */
1052     if (!diagnostic && !rawtext) {
1053 	while (!done) {
1054 	    int ich = NEXT_CHAR;
1055 
1056 	    *p++ = (char) ich;
1057 	    if (ich == EOF) {
1058 		if (interrupted_in_htgetcharacter) {
1059 		    interrupted_in_htgetcharacter = 0;
1060 		    CTRACE((tfp,
1061 			    "HTNews: Interrupted on read, closing socket %d\n",
1062 			    s));
1063 		    NEWS_NETCLOSE(s);
1064 		    s = -1;
1065 		    return (HT_INTERRUPTED);
1066 		}
1067 		abort_socket();	/* End of file, close socket */
1068 		return (HT_LOADED);	/* End of file on response */
1069 	    }
1070 	    if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
1071 		*--p = '\0';	/* Terminate the string */
1072 		CTRACE((tfp, "H %s\n", line));
1073 
1074 		if (line[0] == '\t' || line[0] == ' ') {
1075 		    int i = 0;
1076 
1077 		    while (line[i]) {
1078 			if (line[i] == '\t')
1079 			    line[i] = ' ';
1080 			i++;
1081 		    }
1082 		    if (full_line == NULL) {
1083 			StrAllocCopy(full_line, line);
1084 		    } else {
1085 			StrAllocCat(full_line, line);
1086 		    }
1087 		} else {
1088 		    StrAllocCopy(full_line, line);
1089 		}
1090 
1091 		if (full_line[0] == '.') {
1092 		    /*
1093 		     * End of article?
1094 		     */
1095 		    if (UCH(full_line[1]) < ' ') {
1096 			done = YES;
1097 			break;
1098 		    }
1099 		} else if (UCH(full_line[0]) < ' ') {
1100 		    break;	/* End of Header? */
1101 
1102 		} else if (match(full_line, "SUBJECT:")) {
1103 		    StrAllocCopy(subject, HTStrip(strchr(full_line, ':') + 1));
1104 		    decode_mime(&subject);
1105 		} else if (match(full_line, "DATE:")) {
1106 		    StrAllocCopy(date, HTStrip(strchr(full_line, ':') + 1));
1107 
1108 		} else if (match(full_line, "ORGANIZATION:")) {
1109 		    StrAllocCopy(organization,
1110 				 HTStrip(strchr(full_line, ':') + 1));
1111 		    decode_mime(&organization);
1112 
1113 		} else if (match(full_line, "FROM:")) {
1114 		    StrAllocCopy(from, HTStrip(strchr(full_line, ':') + 1));
1115 		    decode_mime(&from);
1116 
1117 		} else if (match(full_line, "REPLY-TO:")) {
1118 		    StrAllocCopy(replyto, HTStrip(strchr(full_line, ':') + 1));
1119 		    decode_mime(&replyto);
1120 
1121 		} else if (match(full_line, "NEWSGROUPS:")) {
1122 		    StrAllocCopy(newsgroups, HTStrip(strchr(full_line, ':') + 1));
1123 
1124 		} else if (match(full_line, "REFERENCES:")) {
1125 		    StrAllocCopy(references, HTStrip(strchr(full_line, ':') + 1));
1126 
1127 		} else if (match(full_line, "FOLLOWUP-TO:")) {
1128 		    StrAllocCopy(followupto, HTStrip(strchr(full_line, ':') + 1));
1129 
1130 		} else if (match(full_line, "MESSAGE-ID:")) {
1131 		    char *msgid = HTStrip(full_line + 11);
1132 
1133 		    if (msgid[0] == '<' && msgid[strlen(msgid) - 1] == '>') {
1134 			msgid[strlen(msgid) - 1] = '\0';	/* Chop > */
1135 			msgid++;	/* Chop < */
1136 			HTAnchor_setMessageID(thisanchor, msgid);
1137 		    }
1138 
1139 		}		/* end if match */
1140 		p = line;	/* Restart at beginning */
1141 	    }			/* if end of line */
1142 	}			/* Loop over characters */
1143 	FREE(full_line);
1144 
1145 	START(HTML_HEAD);
1146 	PUTC('\n');
1147 	START(HTML_TITLE);
1148 	if (subject && *subject != '\0')
1149 	    PUTS(subject);
1150 	else
1151 	    PUTS("No Subject");
1152 	END(HTML_TITLE);
1153 	PUTC('\n');
1154 	/*
1155 	 * Put in the owner as a link rel.
1156 	 */
1157 	if (from || replyto) {
1158 	    char *temp = NULL;
1159 
1160 	    StrAllocCopy(temp, author_address(replyto ? replyto : from));
1161 	    StrAllocCopy(href, STR_MAILTO_URL);
1162 	    if (strchr(temp, '%') || strchr(temp, '?')) {
1163 		cp = HTEscape(temp, URL_XPALPHAS);
1164 		StrAllocCat(href, cp);
1165 		FREE(cp);
1166 	    } else {
1167 		StrAllocCat(href, temp);
1168 	    }
1169 	    start_link(href, "made");
1170 	    PUTC('\n');
1171 	    FREE(temp);
1172 	}
1173 	END(HTML_HEAD);
1174 	PUTC('\n');
1175 
1176 	START(HTML_H1);
1177 	if (subject && *subject != '\0')
1178 	    PUTS(subject);
1179 	else
1180 	    PUTS("No Subject");
1181 	END(HTML_H1);
1182 	PUTC('\n');
1183 
1184 	if (subject)
1185 	    FREE(subject);
1186 
1187 	START(HTML_DLC);
1188 	PUTC('\n');
1189 
1190 	if (from || replyto) {
1191 	    START(HTML_DT);
1192 	    START(HTML_B);
1193 	    PUTS("From:");
1194 	    END(HTML_B);
1195 	    PUTC(' ');
1196 	    if (from)
1197 		PUTS(from);
1198 	    else
1199 		PUTS(replyto);
1200 	    MAYBE_END(HTML_DT);
1201 	    PUTC('\n');
1202 
1203 	    if (!replyto)
1204 		StrAllocCopy(replyto, from);
1205 	    START(HTML_DT);
1206 	    START(HTML_B);
1207 	    PUTS("Reply to:");
1208 	    END(HTML_B);
1209 	    PUTC(' ');
1210 	    start_anchor(href);
1211 	    if (*replyto != '<')
1212 		PUTS(author_name(replyto));
1213 	    else
1214 		PUTS(author_address(replyto));
1215 	    END(HTML_A);
1216 	    START(HTML_BR);
1217 	    MAYBE_END(HTML_DT);
1218 	    PUTC('\n');
1219 
1220 	    FREE(from);
1221 	    FREE(replyto);
1222 	}
1223 
1224 	if (date) {
1225 	    START(HTML_DT);
1226 	    START(HTML_B);
1227 	    PUTS("Date:");
1228 	    END(HTML_B);
1229 	    PUTC(' ');
1230 	    PUTS(date);
1231 	    MAYBE_END(HTML_DT);
1232 	    PUTC('\n');
1233 	    FREE(date);
1234 	}
1235 
1236 	if (organization) {
1237 	    START(HTML_DT);
1238 	    START(HTML_B);
1239 	    PUTS("Organization:");
1240 	    END(HTML_B);
1241 	    PUTC(' ');
1242 	    PUTS(organization);
1243 	    MAYBE_END(HTML_DT);
1244 	    PUTC('\n');
1245 	    FREE(organization);
1246 	}
1247 
1248 	/* sanitize some headers - kw */
1249 	if (newsgroups &&
1250 	    ((cp = strchr(newsgroups, '/')) ||
1251 	     (cp = strchr(newsgroups, '(')))) {
1252 	    *cp = '\0';
1253 	}
1254 	if (newsgroups && !*newsgroups) {
1255 	    FREE(newsgroups);
1256 	}
1257 	if (followupto &&
1258 	    ((cp = strchr(followupto, '/')) ||
1259 	     (cp = strchr(followupto, '(')))) {
1260 	    *cp = '\0';
1261 	}
1262 	if (followupto && !*followupto) {
1263 	    FREE(followupto);
1264 	}
1265 
1266 	if (newsgroups && HTCanPost) {
1267 	    START(HTML_DT);
1268 	    START(HTML_B);
1269 	    PUTS("Newsgroups:");
1270 	    END(HTML_B);
1271 	    PUTC('\n');
1272 	    MAYBE_END(HTML_DT);
1273 	    START(HTML_DD);
1274 	    write_anchors(newsgroups);
1275 	    MAYBE_END(HTML_DD);
1276 	    PUTC('\n');
1277 	}
1278 
1279 	if (followupto && !strcasecomp(followupto, "poster")) {
1280 	    /*
1281 	     * "Followup-To:  poster" has special meaning.  Don't use it to
1282 	     * construct a newsreply link.  -kw
1283 	     */
1284 	    START(HTML_DT);
1285 	    START(HTML_B);
1286 	    PUTS("Followup to:");
1287 	    END(HTML_B);
1288 	    PUTC(' ');
1289 	    if (href) {
1290 		start_anchor(href);
1291 		PUTS("poster");
1292 		END(HTML_A);
1293 	    } else {
1294 		PUTS("poster");
1295 	    }
1296 	    MAYBE_END(HTML_DT);
1297 	    PUTC('\n');
1298 	    FREE(followupto);
1299 	}
1300 
1301 	if (newsgroups && HTCanPost) {
1302 	    /*
1303 	     * We have permission to POST to this host, so add a link for
1304 	     * posting followups for this article.  - FM
1305 	     */
1306 	    if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
1307 		StrAllocCopy(href, "snewsreply://");
1308 	    else
1309 		StrAllocCopy(href, "newsreply://");
1310 	    StrAllocCat(href, NewsHost);
1311 	    StrAllocCat(href, "/");
1312 	    StrAllocCat(href, (followupto ? followupto : newsgroups));
1313 	    if (*href == 'n' &&
1314 		(ccp = HTAnchor_messageID(thisanchor)) && *ccp) {
1315 		StrAllocCat(href, ";ref=");
1316 		if (strchr(ccp, '<') || strchr(ccp, '&') ||
1317 		    strchr(ccp, ' ') || strchr(ccp, ':') ||
1318 		    strchr(ccp, '/') || strchr(ccp, '%') ||
1319 		    strchr(ccp, ';')) {
1320 		    char *cp1 = HTEscape(ccp, URL_XPALPHAS);
1321 
1322 		    StrAllocCat(href, cp1);
1323 		    FREE(cp1);
1324 		} else {
1325 		    StrAllocCat(href, ccp);
1326 		}
1327 	    }
1328 
1329 	    START(HTML_DT);
1330 	    START(HTML_B);
1331 	    PUTS("Followup to:");
1332 	    END(HTML_B);
1333 	    PUTC(' ');
1334 	    start_anchor(href);
1335 	    if (strchr((followupto ? followupto : newsgroups), ',')) {
1336 		PUTS("newsgroups");
1337 	    } else {
1338 		PUTS("newsgroup");
1339 	    }
1340 	    END(HTML_A);
1341 	    MAYBE_END(HTML_DT);
1342 	    PUTC('\n');
1343 	}
1344 	FREE(newsgroups);
1345 	FREE(followupto);
1346 
1347 	if (references) {
1348 	    START(HTML_DT);
1349 	    START(HTML_B);
1350 	    PUTS("References:");
1351 	    END(HTML_B);
1352 	    MAYBE_END(HTML_DT);
1353 	    PUTC('\n');
1354 	    START(HTML_DD);
1355 	    write_anchors(references);
1356 	    MAYBE_END(HTML_DD);
1357 	    PUTC('\n');
1358 	    FREE(references);
1359 	}
1360 
1361 	END(HTML_DLC);
1362 	PUTC('\n');
1363 	FREE(href);
1364     }
1365 
1366     if (rawtext) {
1367 	/*
1368 	 * No tags, and never do a PUTC.  - kw
1369 	 */
1370 	;
1371     } else if (diagnostic) {
1372 	/*
1373 	 * Read in the HEAD and BODY of the Article as XMP formatted text.  -
1374 	 * FM
1375 	 */
1376 	START(HTML_XMP);
1377 	PUTC('\n');
1378     } else {
1379 	/*
1380 	 * Read in the BODY of the Article as PRE formatted text.  - FM
1381 	 */
1382 	START(HTML_PRE);
1383 	PUTC('\n');
1384     }
1385 
1386     p = line;
1387     while (!done) {
1388 	int ich = NEXT_CHAR;
1389 
1390 	*p++ = (char) ich;
1391 	if (ich == EOF) {
1392 	    if (interrupted_in_htgetcharacter) {
1393 		interrupted_in_htgetcharacter = 0;
1394 		CTRACE((tfp,
1395 			"HTNews: Interrupted on read, closing socket %d\n",
1396 			s));
1397 		NEWS_NETCLOSE(s);
1398 		s = -1;
1399 		return (HT_INTERRUPTED);
1400 	    }
1401 	    abort_socket();	/* End of file, close socket */
1402 	    return (HT_LOADED);	/* End of file on response */
1403 	}
1404 	if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
1405 	    *p = '\0';		/* Terminate the string */
1406 	    CTRACE((tfp, "B %s", line));
1407 #ifdef NEWS_DEBUG		/* 1997/11/09 (Sun) 15:56:11 */
1408 	    debug_print(line);	/* @@@ */
1409 #endif
1410 	    if (line[0] == '.') {
1411 		/*
1412 		 * End of article?
1413 		 */
1414 		if (UCH(line[1]) < ' ') {
1415 		    break;
1416 		} else {	/* Line starts with dot */
1417 		    if (rawtext) {
1418 			RAW_PUTS(&line[1]);
1419 		    } else {
1420 			PUTS(&line[1]);		/* Ignore first dot */
1421 		    }
1422 		}
1423 	    } else {
1424 		if (rawtext) {
1425 		    RAW_PUTS(line);
1426 		} else if (diagnostic || !scan_for_buried_news_references) {
1427 		    /*
1428 		     * All lines are passed as unmodified source.  - FM
1429 		     */
1430 		    PUTS(line);
1431 		} else {
1432 		    /*
1433 		     * Normal lines are scanned for buried references to other
1434 		     * articles.  Unfortunately, it could pick up mail
1435 		     * addresses as well!  It also can corrupt uuencoded
1436 		     * messages!  So we don't do this when fetching articles as
1437 		     * WWW_SOURCE or when downloading (diagnostic is TRUE) or
1438 		     * if the client has set scan_for_buried_news_references to
1439 		     * FALSE.  Otherwise, we convert all "<...@...>" strings
1440 		     * preceded by "rticle " to "news:...@..." links, and any
1441 		     * strings that look like URLs to links.  - FM
1442 		     */
1443 		    char *l = line;
1444 		    char *p2;
1445 
1446 		    while ((p2 = strstr(l, "rticle <")) != NULL) {
1447 			char *q = strrchr(p2, '>');
1448 			char *at = strrchr(p2, '@');
1449 
1450 			if (q && at && at < q) {
1451 			    char c = q[1];
1452 
1453 			    q[1] = 0;	/* chop up */
1454 			    p2 += 7;
1455 			    *p2 = 0;
1456 			    while (*l) {
1457 				if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
1458 				    StrNCmp(l, "snews://", 8) &&
1459 				    StrNCmp(l, "nntp://", 7) &&
1460 				    StrNCmp(l, "snewspost:", 10) &&
1461 				    StrNCmp(l, "snewsreply:", 11) &&
1462 				    StrNCmp(l, "newspost:", 9) &&
1463 				    StrNCmp(l, "newsreply:", 10) &&
1464 				    StrNCmp(l, "ftp://", 6) &&
1465 				    StrNCmp(l, "file:/", 6) &&
1466 				    StrNCmp(l, "finger://", 9) &&
1467 				    StrNCmp(l, "http://", 7) &&
1468 				    StrNCmp(l, "https://", 8) &&
1469 				    StrNCmp(l, "wais://", 7) &&
1470 				    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
1471 				    StrNCmp(l, "cso://", 6) &&
1472 				    StrNCmp(l, "gopher://", 9)) {
1473 				    PUTC(*l++);
1474 				} else {
1475 				    StrAllocCopy(href, l);
1476 				    start_anchor(strtok(href, " \r\n\t,>)\""));
1477 				    while (*l && !strchr(" \r\n\t,>)\"", *l))
1478 					PUTC(*l++);
1479 				    END(HTML_A);
1480 				    FREE(href);
1481 				}
1482 			    }
1483 			    *p2 = '<';	/* again */
1484 			    *q = 0;
1485 			    start_anchor(p2 + 1);
1486 			    *q = '>';	/* again */
1487 			    PUTS(p2);
1488 			    END(HTML_A);
1489 			    q[1] = c;	/* again */
1490 			    l = q + 1;
1491 			} else {
1492 			    break;	/* line has unmatched <> */
1493 			}
1494 		    }
1495 		    while (*l) {	/* Last bit of the line */
1496 			if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
1497 			    StrNCmp(l, "snews://", 8) &&
1498 			    StrNCmp(l, "nntp://", 7) &&
1499 			    StrNCmp(l, "snewspost:", 10) &&
1500 			    StrNCmp(l, "snewsreply:", 11) &&
1501 			    StrNCmp(l, "newspost:", 9) &&
1502 			    StrNCmp(l, "newsreply:", 10) &&
1503 			    StrNCmp(l, "ftp://", 6) &&
1504 			    StrNCmp(l, "file:/", 6) &&
1505 			    StrNCmp(l, "finger://", 9) &&
1506 			    StrNCmp(l, "http://", 7) &&
1507 			    StrNCmp(l, "https://", 8) &&
1508 			    StrNCmp(l, "wais://", 7) &&
1509 			    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
1510 			    StrNCmp(l, "cso://", 6) &&
1511 			    StrNCmp(l, "gopher://", 9))
1512 			    PUTC(*l++);
1513 			else {
1514 			    StrAllocCopy(href, l);
1515 			    start_anchor(strtok(href, " \r\n\t,>)\""));
1516 			    while (*l && !strchr(" \r\n\t,>)\"", *l))
1517 				PUTC(*l++);
1518 			    END(HTML_A);
1519 			    FREE(href);
1520 			}
1521 		    }
1522 		}		/* if diagnostic or not scan_for_buried_news_references */
1523 	    }			/* if not dot */
1524 	    p = line;		/* Restart at beginning */
1525 	}			/* if end of line */
1526     }				/* Loop over characters */
1527 
1528     if (rawtext)
1529 	return (HT_LOADED);
1530 
1531     if (diagnostic)
1532 	END(HTML_XMP);
1533     else
1534 	END(HTML_PRE);
1535     PUTC('\n');
1536     return (HT_LOADED);
1537 }
1538 
1539 /*	Read in a List of Newsgroups
1540  *	----------------------------
1541  *
1542  *  Note the termination condition of a single dot on a line by itself.
1543  *  RFC 977 specifies that the line "folding" of RFC850 is not used,
1544  *  so we do not handle it here.
1545  */
read_list(char * arg)1546 static int read_list(char *arg)
1547 {
1548     char line[LINE_LENGTH + 1];
1549     char *p;
1550     BOOL done = NO;
1551     BOOL head = NO;
1552     BOOL tail = NO;
1553     BOOL skip_this_line = NO;
1554     BOOL skip_rest_of_line = NO;
1555     int listing = 0;
1556     char *pattern = NULL;
1557     int len = 0;
1558 
1559     /*
1560      * Support head or tail matches for groups to list.  - FM
1561      */
1562     if (arg && strlen(arg) > 1) {
1563 	if (*arg == '*') {
1564 	    tail = YES;
1565 	    StrAllocCopy(pattern, (arg + 1));
1566 	} else if (arg[strlen(arg) - 1] == '*') {
1567 	    head = YES;
1568 	    StrAllocCopy(pattern, arg);
1569 	    pattern[strlen(pattern) - 1] = '\0';
1570 	}
1571 	if (tail || head) {
1572 	    len = (int) strlen(pattern);
1573 	}
1574 
1575     }
1576 
1577     /*
1578      * Read the server's reply.
1579      *
1580      * The lines are scanned for newsgroup names and descriptions.
1581      */
1582     START(HTML_HEAD);
1583     PUTC('\n');
1584     START(HTML_TITLE);
1585     PUTS("Newsgroups");
1586     END(HTML_TITLE);
1587     PUTC('\n');
1588     END(HTML_HEAD);
1589     PUTC('\n');
1590     START(HTML_H1);
1591     PUTS("Newsgroups");
1592     END(HTML_H1);
1593     PUTC('\n');
1594     p = line;
1595     START(HTML_DLC);
1596     PUTC('\n');
1597     while (!done) {
1598 	int ich = NEXT_CHAR;
1599 	char ch = (char) ich;
1600 
1601 	if (ich == EOF) {
1602 	    if (interrupted_in_htgetcharacter) {
1603 		interrupted_in_htgetcharacter = 0;
1604 		CTRACE((tfp,
1605 			"HTNews: Interrupted on read, closing socket %d\n",
1606 			s));
1607 		NEWS_NETCLOSE(s);
1608 		s = -1;
1609 		return (HT_INTERRUPTED);
1610 	    }
1611 	    abort_socket();	/* End of file, close socket */
1612 	    FREE(pattern);
1613 	    return (HT_LOADED);	/* End of file on response */
1614 	} else if (skip_this_line) {
1615 	    if (ch == LF) {
1616 		skip_this_line = skip_rest_of_line = NO;
1617 		p = line;
1618 	    }
1619 	    continue;
1620 	} else if (skip_rest_of_line) {
1621 	    if (ch != LF) {
1622 		continue;
1623 	    }
1624 	} else if (p == &line[LINE_LENGTH]) {
1625 	    CTRACE((tfp, "b %.*s%c[...]\n", (LINE_LENGTH), line, ch));
1626 	    *p = '\0';
1627 	    if (ch == LF) {
1628 		;		/* Will be dealt with below */
1629 	    } else if (WHITE(ch)) {
1630 		ch = LF;	/* May treat as line without description */
1631 		skip_this_line = YES;	/* ...and ignore until LF */
1632 	    } else if (strchr(line, ' ') == NULL &&
1633 		       strchr(line, '\t') == NULL) {
1634 		/* No separator found */
1635 		CTRACE((tfp, "HTNews..... group name too long, discarding.\n"));
1636 		skip_this_line = YES;	/* ignore whole line */
1637 		continue;
1638 	    } else {
1639 		skip_rest_of_line = YES;	/* skip until ch == LF found */
1640 	    }
1641 	} else {
1642 	    *p++ = ch;
1643 	}
1644 	if (ch == LF) {
1645 	    skip_rest_of_line = NO;	/* done, reset flag */
1646 	    *p = '\0';		/* Terminate the string */
1647 	    CTRACE((tfp, "B %s", line));
1648 	    if (line[0] == '.') {
1649 		/*
1650 		 * End of article?
1651 		 */
1652 		if (UCH(line[1]) < ' ') {
1653 		    break;
1654 		} else {	/* Line starts with dot */
1655 		    START(HTML_DT);
1656 		    PUTS(&line[1]);
1657 		    MAYBE_END(HTML_DT);
1658 		}
1659 	    } else if (line[0] == '#') {	/* Comment? */
1660 		p = line;	/* Restart at beginning */
1661 		continue;
1662 	    } else {
1663 		/*
1664 		 * Normal lines are scanned for references to newsgroups.
1665 		 */
1666 		int i = 0;
1667 
1668 		/* find whitespace if it exits */
1669 		for (; line[i] != '\0' && !WHITE(line[i]); i++) ;	/* null body */
1670 
1671 		if (line[i] != '\0') {
1672 		    line[i] = '\0';
1673 		    if ((head && strncasecomp(line, pattern, len)) ||
1674 			(tail && (i < len ||
1675 				  strcasecomp((line + (i - len)), pattern)))) {
1676 			p = line;	/* Restart at beginning */
1677 			continue;
1678 		    }
1679 		    START(HTML_DT);
1680 		    write_anchor(line, line);
1681 		    listing++;
1682 		    MAYBE_END(HTML_DT);
1683 		    PUTC('\n');
1684 		    START(HTML_DD);
1685 		    PUTS(&line[i + 1]);		/* put description */
1686 		    MAYBE_END(HTML_DD);
1687 		} else {
1688 		    if ((head && strncasecomp(line, pattern, len)) ||
1689 			(tail && (i < len ||
1690 				  strcasecomp((line + (i - len)), pattern)))) {
1691 			p = line;	/* Restart at beginning */
1692 			continue;
1693 		    }
1694 		    START(HTML_DT);
1695 		    write_anchor(line, line);
1696 		    MAYBE_END(HTML_DT);
1697 		    listing++;
1698 		}
1699 	    }			/* if not dot */
1700 	    p = line;		/* Restart at beginning */
1701 	}			/* if end of line */
1702     }				/* Loop over characters */
1703     if (!listing) {
1704 	char *msg = NULL;
1705 
1706 	START(HTML_DT);
1707 	HTSprintf0(&msg, gettext("No matches for: %s"), arg);
1708 	PUTS(msg);
1709 	MAYBE_END(HTML_DT);
1710 	FREE(msg);
1711     }
1712     END(HTML_DLC);
1713     PUTC('\n');
1714     FREE(pattern);
1715     return (HT_LOADED);
1716 }
1717 
1718 /*	Read in a Newsgroup
1719  *	-------------------
1720  *
1721  *  Unfortunately, we have to ask for each article one by one if we
1722  *  want more than one field.
1723  *
1724  */
read_group(const char * groupName,int first_required,int last_required)1725 static int read_group(const char *groupName,
1726 		      int first_required,
1727 		      int last_required)
1728 {
1729     char line[LINE_LENGTH + 1];
1730     char *author = NULL;
1731     char *subject = NULL;
1732     char *date = NULL;
1733     int i;
1734     char *p;
1735     BOOL done;
1736 
1737     char buffer[LINE_LENGTH + 1];
1738     char *temp = NULL;
1739     char *reference = NULL;	/* Href for article */
1740     int art;			/* Article number WITHIN GROUP */
1741     int status, count, first, last;	/* Response fields */
1742 
1743     START(HTML_HEAD);
1744     PUTC('\n');
1745     START(HTML_TITLE);
1746     PUTS("Newsgroup ");
1747     PUTS(groupName);
1748     END(HTML_TITLE);
1749     PUTC('\n');
1750     END(HTML_HEAD);
1751     PUTC('\n');
1752 
1753     sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
1754     CTRACE((tfp, "Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
1755 	    status, count, first, last, first_required, last_required));
1756     if (last == 0) {
1757 	PUTS(gettext("\nNo articles in this group.\n"));
1758 	goto add_post;
1759     }
1760 #define FAST_THRESHOLD 100	/* Above this, read IDs fast */
1761 #define CHOP_THRESHOLD 50	/* Above this, chop off the rest */
1762 
1763     if (first_required < first)
1764 	first_required = first;	/* clip */
1765     if ((last_required == 0) || (last_required > last))
1766 	last_required = last;
1767 
1768     if (last_required < first_required) {
1769 	PUTS(gettext("\nNo articles in this range.\n"));
1770 	goto add_post;
1771     }
1772 
1773     if (last_required - first_required + 1 > HTNewsMaxChunk) {	/* Trim this block */
1774 	first_required = last_required - HTNewsChunkSize + 1;
1775     }
1776     CTRACE((tfp, "    Chunk will be (%d-%d)\n",
1777 	    first_required, last_required));
1778 
1779     /*
1780      * Set window title.
1781      */
1782     HTSprintf0(&temp, gettext("%s,  Articles %d-%d"),
1783 	       groupName, first_required, last_required);
1784     START(HTML_H1);
1785     PUTS(temp);
1786     FREE(temp);
1787     END(HTML_H1);
1788     PUTC('\n');
1789 
1790     /*
1791      * Link to earlier articles.
1792      */
1793     if (first_required > first) {
1794 	int before;		/* Start of one before */
1795 
1796 	if (first_required - HTNewsMaxChunk <= first)
1797 	    before = first;
1798 	else
1799 	    before = first_required - HTNewsChunkSize;
1800 	HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
1801 		   before, first_required - 1);
1802 	CTRACE((tfp, "    Block before is %s\n", dbuf));
1803 	PUTC('(');
1804 	start_anchor(dbuf);
1805 	PUTS(gettext("Earlier articles"));
1806 	END(HTML_A);
1807 	PUTS("...)\n");
1808 	START(HTML_P);
1809 	PUTC('\n');
1810     }
1811 
1812     done = NO;
1813 
1814 /*#define USE_XHDR*/
1815 #ifdef USE_XHDR
1816     if (count > FAST_THRESHOLD) {
1817 	HTSprintf0(&temp,
1818 		   gettext("\nThere are about %d articles currently available in %s, IDs as follows:\n\n"),
1819 		   count, groupName);
1820 	PUTS(temp);
1821 	FREE(temp);
1822 	sprintf(buffer, "XHDR Message-ID %d-%d%c%c", first, last, CR, LF);
1823 	status = response(buffer);
1824 	if (status == 221) {
1825 	    p = line;
1826 	    while (!done) {
1827 		int ich = NEXT_CHAR;
1828 
1829 		*p++ = ich;
1830 		if (ich == EOF) {
1831 		    if (interrupted_in_htgetcharacter) {
1832 			interrupted_in_htgetcharacter = 0;
1833 			CTRACE((tfp,
1834 				"HTNews: Interrupted on read, closing socket %d\n",
1835 				s));
1836 			NEWS_NETCLOSE(s);
1837 			s = -1;
1838 			return (HT_INTERRUPTED);
1839 		    }
1840 		    abort_socket();	/* End of file, close socket */
1841 		    return (HT_LOADED);		/* End of file on response */
1842 		}
1843 		if (((char) ich == '\n') || (p == &line[LINE_LENGTH])) {
1844 		    *p = '\0';	/* Terminate the string */
1845 		    CTRACE((tfp, "X %s", line));
1846 		    if (line[0] == '.') {
1847 			/*
1848 			 * End of article?
1849 			 */
1850 			if (UCH(line[1]) < ' ') {
1851 			    done = YES;
1852 			    break;
1853 			} else {	/* Line starts with dot */
1854 			    /* Ignore strange line */
1855 			}
1856 		    } else {
1857 			/*
1858 			 * Normal lines are scanned for references to articles.
1859 			 */
1860 			char *space = strchr(line, ' ');
1861 
1862 			if (space++)
1863 			    write_anchor(space, space);
1864 		    }		/* if not dot */
1865 		    p = line;	/* Restart at beginning */
1866 		}		/* if end of line */
1867 	    }			/* Loop over characters */
1868 
1869 	    /* leaving loop with "done" set */
1870 	}			/* Good status */
1871     }
1872 #endif /* USE_XHDR */
1873 
1874     /*
1875      * Read newsgroup using individual fields.
1876      */
1877     if (!done) {
1878 	START(HTML_B);
1879 	if (first == first_required && last == last_required)
1880 	    PUTS(gettext("All available articles in "));
1881 	else
1882 	    PUTS("Articles in ");
1883 	PUTS(groupName);
1884 	END(HTML_B);
1885 	PUTC('\n');
1886 	if (LYListNewsNumbers)
1887 	    start_list(first_required);
1888 	else
1889 	    START(HTML_UL);
1890 	for (art = first_required; art <= last_required; art++) {
1891 /*#define OVERLAP*/
1892 #ifdef OVERLAP
1893 	    /*
1894 	     * With this code we try to keep the server running flat out by
1895 	     * queuing just one extra command ahead of time.  We assume (1)
1896 	     * that the server won't abort if it gets input during output, and
1897 	     * (2) that TCP buffering is enough for the two commands.  Both
1898 	     * these assumptions seem very reasonable.  However, we HAVE had a
1899 	     * hangup with a loaded server.
1900 	     */
1901 	    if (art == first_required) {
1902 		if (art == last_required) {	/* Only one */
1903 		    sprintf(buffer, "HEAD %d%c%c",
1904 			    art, CR, LF);
1905 		    status = response(buffer);
1906 		} else {	/* First of many */
1907 		    sprintf(buffer, "HEAD %d%c%cHEAD %d%c%c",
1908 			    art, CR, LF, art + 1, CR, LF);
1909 		    status = response(buffer);
1910 		}
1911 	    } else if (art == last_required) {	/* Last of many */
1912 		status = response(NULL);
1913 	    } else {		/* Middle of many */
1914 		sprintf(buffer, "HEAD %d%c%c", art + 1, CR, LF);
1915 		status = response(buffer);
1916 	    }
1917 #else /* Not OVERLAP: */
1918 	    sprintf(buffer, "HEAD %d%c%c", art, CR, LF);
1919 	    status = response(buffer);
1920 #endif /* OVERLAP */
1921 	    /*
1922 	     * Check for a good response (221) for the HEAD request, and if so,
1923 	     * parse it.  Otherwise, indicate the error so that the number of
1924 	     * listings corresponds to what's claimed for the range, and if we
1925 	     * are listing numbers via an ordered list, they stay in synchrony
1926 	     * with the article numbers.  - FM
1927 	     */
1928 	    if (status == 221) {	/* Head follows - parse it: */
1929 		p = line;	/* Write pointer */
1930 		done = NO;
1931 		while (!done) {
1932 		    int ich = NEXT_CHAR;
1933 
1934 		    *p++ = (char) ich;
1935 		    if (ich == EOF) {
1936 			if (interrupted_in_htgetcharacter) {
1937 			    interrupted_in_htgetcharacter = 0;
1938 			    CTRACE((tfp,
1939 				    "HTNews: Interrupted on read, closing socket %d\n",
1940 				    s));
1941 			    NEWS_NETCLOSE(s);
1942 			    s = -1;
1943 			    return (HT_INTERRUPTED);
1944 			}
1945 			abort_socket();		/* End of file, close socket */
1946 			return (HT_LOADED);	/* End of file on response */
1947 		    }
1948 		    if (((char) ich == LF) ||
1949 			(p == &line[LINE_LENGTH])) {
1950 
1951 			*--p = '\0';	/* Terminate  & chop LF */
1952 			p = line;	/* Restart at beginning */
1953 			CTRACE((tfp, "G %s\n", line));
1954 			switch (line[0]) {
1955 
1956 			case '.':
1957 			    /*
1958 			     * End of article?
1959 			     */
1960 			    done = (BOOL) (UCH(line[1]) < ' ');
1961 			    break;
1962 
1963 			case 'S':
1964 			case 's':
1965 			    if (match(line, "SUBJECT:")) {
1966 				StrAllocCopy(subject, line + 9);
1967 				decode_mime(&subject);
1968 			    }
1969 			    break;
1970 
1971 			case 'M':
1972 			case 'm':
1973 			    if (match(line, "MESSAGE-ID:")) {
1974 				char *addr = HTStrip(line + 11) + 1;	/* Chop < */
1975 
1976 				addr[strlen(addr) - 1] = '\0';	/* Chop > */
1977 				StrAllocCopy(reference, addr);
1978 			    }
1979 			    break;
1980 
1981 			case 'f':
1982 			case 'F':
1983 			    if (match(line, "FROM:")) {
1984 				char *p2;
1985 
1986 				StrAllocCopy(author, strchr(line, ':') + 1);
1987 				decode_mime(&author);
1988 				p2 = author + strlen(author) - 1;
1989 				if (*p2 == LF)
1990 				    *p2 = '\0';		/* Chop off newline */
1991 			    }
1992 			    break;
1993 
1994 			case 'd':
1995 			case 'D':
1996 			    if (LYListNewsDates && match(line, "DATE:")) {
1997 				StrAllocCopy(date,
1998 					     HTStrip(strchr(line, ':') + 1));
1999 			    }
2000 			    break;
2001 
2002 			}	/* end switch on first character */
2003 		    }		/* if end of line */
2004 		}		/* Loop over characters */
2005 
2006 		PUTC('\n');
2007 		START(HTML_LI);
2008 		p = decode_mime(&subject);
2009 		HTSprintf0(&temp, "\"%s\"", NonNull(p));
2010 		if (reference) {
2011 		    write_anchor(temp, reference);
2012 		    FREE(reference);
2013 		} else {
2014 		    PUTS(temp);
2015 		}
2016 		FREE(temp);
2017 
2018 		if (author != NULL) {
2019 		    PUTS(" - ");
2020 		    if (LYListNewsDates)
2021 			START(HTML_I);
2022 		    PUTS(decode_mime(&author));
2023 		    if (LYListNewsDates)
2024 			END(HTML_I);
2025 		    FREE(author);
2026 		}
2027 		if (date) {
2028 		    if (!diagnostic) {
2029 			for (i = 0; date[i]; i++) {
2030 			    if (date[i] == ' ') {
2031 				date[i] = HT_NON_BREAK_SPACE;
2032 			    }
2033 			}
2034 		    }
2035 		    sprintf(buffer, " [%.*s]", (int) (sizeof(buffer) - 4), date);
2036 		    PUTS(buffer);
2037 		    FREE(date);
2038 		}
2039 		MAYBE_END(HTML_LI);
2040 		/*
2041 		 * Indicate progress!  @@@@@@
2042 		 */
2043 	    } else if (status == HT_INTERRUPTED) {
2044 		interrupted_in_htgetcharacter = 0;
2045 		CTRACE((tfp,
2046 			"HTNews: Interrupted on read, closing socket %d\n",
2047 			s));
2048 		NEWS_NETCLOSE(s);
2049 		s = -1;
2050 		return (HT_INTERRUPTED);
2051 	    } else {
2052 		/*
2053 		 * Use the response text on error.  - FM
2054 		 */
2055 		PUTC('\n');
2056 		START(HTML_LI);
2057 		START(HTML_I);
2058 		if (LYListNewsNumbers)
2059 		    LYStrNCpy(buffer, "Status:", sizeof(buffer) - 1);
2060 		else
2061 		    sprintf(buffer, "Status (ARTICLE %d):", art);
2062 		PUTS(buffer);
2063 		END(HTML_I);
2064 		PUTC(' ');
2065 		PUTS(response_text);
2066 		MAYBE_END(HTML_LI);
2067 	    }			/* Handle response to HEAD request */
2068 	}			/* Loop over article */
2069 	FREE(author);
2070 	FREE(subject);
2071     }				/* If read headers */
2072     PUTC('\n');
2073     if (LYListNewsNumbers)
2074 	END(HTML_OL);
2075     else
2076 	END(HTML_UL);
2077     PUTC('\n');
2078 
2079     /*
2080      * Link to later articles.
2081      */
2082     if (last_required < last) {
2083 	int after;		/* End of article after */
2084 
2085 	after = last_required + HTNewsChunkSize;
2086 	if (after == last)
2087 	    HTSprintf0(&dbuf, "%s%s", NewsHREF, groupName);	/* original group */
2088 	else
2089 	    HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
2090 		       last_required + 1, after);
2091 	CTRACE((tfp, "    Block after is %s\n", dbuf));
2092 	PUTC('(');
2093 	start_anchor(dbuf);
2094 	PUTS(gettext("Later articles"));
2095 	END(HTML_A);
2096 	PUTS("...)\n");
2097     }
2098 
2099   add_post:
2100     if (HTCanPost) {
2101 	/*
2102 	 * We have permission to POST to this host, so add a link for posting
2103 	 * messages to this newsgroup.  - FM
2104 	 */
2105 	char *href = NULL;
2106 
2107 	START(HTML_HR);
2108 	PUTC('\n');
2109 	if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
2110 	    StrAllocCopy(href, "snewspost://");
2111 	else
2112 	    StrAllocCopy(href, "newspost://");
2113 	StrAllocCat(href, NewsHost);
2114 	StrAllocCat(href, "/");
2115 	StrAllocCat(href, groupName);
2116 	start_anchor(href);
2117 	PUTS(gettext("Post to "));
2118 	PUTS(groupName);
2119 	END(HTML_A);
2120 	FREE(href);
2121     } else {
2122 	START(HTML_HR);
2123     }
2124     PUTC('\n');
2125     return (HT_LOADED);
2126 }
2127 
2128 /*	Load by name.						HTLoadNews
2129  *	=============
2130  */
HTLoadNews(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * stream)2131 static int HTLoadNews(const char *arg,
2132 		      HTParentAnchor *anAnchor,
2133 		      HTFormat format_out,
2134 		      HTStream *stream)
2135 {
2136     char command[262];		/* The whole command */
2137     char proxycmd[260];		/* The proxy command */
2138     char groupName[GROUP_NAME_LENGTH];	/* Just the group name */
2139     int status;			/* tcp return */
2140     int retries;		/* A count of how hard we have tried */
2141     BOOL normal_url;		/* Flag: "news:" or "nntp:" (physical) URL */
2142     BOOL group_wanted;		/* Flag: group was asked for, not article */
2143     BOOL list_wanted;		/* Flag: list was asked for, not article */
2144     BOOL post_wanted;		/* Flag: new post to group was asked for */
2145     BOOL reply_wanted;		/* Flag: followup post was asked for */
2146     BOOL spost_wanted;		/* Flag: new SSL post to group was asked for */
2147     BOOL sreply_wanted;		/* Flag: followup SSL post was asked for */
2148     BOOL head_wanted = NO;	/* Flag: want HEAD of single article */
2149     int first, last;		/* First and last articles asked for */
2150     char *cp = 0;
2151     char *ListArg = NULL;
2152     char *ProxyHost = NULL;
2153     char *ProxyHREF = NULL;
2154     char *postfile = NULL;
2155 
2156 #ifdef USE_SSL
2157     char SSLprogress[256];
2158 #endif /* USE_SSL */
2159 
2160     diagnostic = (format_out == WWW_SOURCE ||	/* set global flag */
2161 		  format_out == HTAtom_for("www/download") ||
2162 		  format_out == HTAtom_for("www/dump"));
2163     rawtext = NO;
2164 
2165     CTRACE((tfp, "HTNews: Looking for %s\n", arg));
2166 
2167     if (!initialized)
2168 	initialized = initialize();
2169     if (!initialized)
2170 	return -1;		/* FAIL */
2171 
2172     FREE(NewsHREF);
2173     command[0] = '\0';
2174     command[sizeof(command) - 1] = '\0';
2175     proxycmd[0] = '\0';
2176     proxycmd[sizeof(proxycmd) - 1] = '\0';
2177 
2178     {
2179 	const char *p1;
2180 
2181 	/*
2182 	 * We will ask for the document, omitting the host name & anchor.
2183 	 *
2184 	 * Syntax of address is
2185 	 * xxx@yyy                 Article
2186 	 * <xxx@yyy>               Same article
2187 	 * xxxxx                   News group (no "@")
2188 	 * group/n1-n2             Articles n1 to n2 in group
2189 	 */
2190 	normal_url = (BOOL) (!StrNCmp(arg, STR_NEWS_URL, LEN_NEWS_URL) ||
2191 			     !StrNCmp(arg, "nntp:", 5));
2192 	spost_wanted = (BOOL) (!normal_url && strstr(arg, "snewspost:") != NULL);
2193 	sreply_wanted = (BOOL) (!(normal_url || spost_wanted) &&
2194 				strstr(arg, "snewsreply:") != NULL);
2195 	post_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted) &&
2196 			      strstr(arg, "newspost:") != NULL);
2197 	reply_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted ||
2198 				 post_wanted) &&
2199 			       strstr(arg, "newsreply:") != NULL);
2200 	group_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
2201 				  post_wanted || reply_wanted) &&
2202 				strchr(arg, '@') == NULL) &&
2203 			       (strchr(arg, '*') == NULL));
2204 	list_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
2205 				 post_wanted || reply_wanted ||
2206 				 group_wanted) &&
2207 			       strchr(arg, '@') == NULL) &&
2208 			      (strchr(arg, '*') != NULL));
2209 
2210 #ifndef USE_SSL
2211 	if (!strncasecomp(arg, "snewspost:", 10) ||
2212 	    !strncasecomp(arg, "snewsreply:", 11)) {
2213 	    HTAlert(FAILED_CANNOT_POST_SSL);
2214 	    return HT_NOT_LOADED;
2215 	}
2216 #endif /* !USE_SSL */
2217 	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
2218 	    /*
2219 	     * Make sure we have a non-zero path for the newsgroup(s).  - FM
2220 	     */
2221 	    if ((p1 = strrchr(arg, '/')) != NULL) {
2222 		p1++;
2223 	    } else if ((p1 = strrchr(arg, ':')) != NULL) {
2224 		p1++;
2225 	    }
2226 	    if (!(p1 && *p1)) {
2227 		HTAlert(WWW_ILLEGAL_URL_MESSAGE);
2228 		return (HT_NO_DATA);
2229 	    }
2230 	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
2231 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
2232 		    NEWS_NETCLOSE(s);
2233 		    s = -1;
2234 		}
2235 		StrAllocCopy(NewsHost, HTNewsHost);
2236 	    } else {
2237 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
2238 		    NEWS_NETCLOSE(s);
2239 		    s = -1;
2240 		}
2241 		StrAllocCopy(NewsHost, cp);
2242 	    }
2243 	    FREE(cp);
2244 	    HTSprintf0(&NewsHREF, "%s://%.*s/",
2245 		       (post_wanted ?
2246 			"newspost" :
2247 			(reply_wanted ?
2248 			 "newreply" :
2249 			 (spost_wanted ?
2250 			  "snewspost" : "snewsreply"))),
2251 		       (int) sizeof(command) - 15, NewsHost);
2252 
2253 	    /*
2254 	     * If the SSL daemon is being used as a proxy, reset p1 to the
2255 	     * start of the proxied URL rather than to the start of the
2256 	     * newsgroup(s).  - FM
2257 	     */
2258 	    if (spost_wanted && strncasecomp(arg, "snewspost:", 10))
2259 		p1 = strstr(arg, "snewspost:");
2260 	    if (sreply_wanted && strncasecomp(arg, "snewsreply:", 11))
2261 		p1 = strstr(arg, "snewsreply:");
2262 
2263 	    /* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
2264 	    /*
2265 	     * Don't use HTParse because news:  access doesn't follow
2266 	     * traditional rules.  For instance, if the article reference
2267 	     * contains a '#', the rest of it is lost -- JFG 10/7/92, from a
2268 	     * bug report
2269 	     */
2270 	} else if (isNNTP_URL(arg)) {
2271 	    if (((*(arg + 5) == '\0') ||
2272 		 (!strcmp((arg + 5), "/") ||
2273 		  !strcmp((arg + 5), "//") ||
2274 		  !strcmp((arg + 5), "///"))) ||
2275 		((!StrNCmp((arg + 5), "//", 2)) &&
2276 		 (!(cp = strchr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
2277 		p1 = "*";
2278 		group_wanted = FALSE;
2279 		list_wanted = TRUE;
2280 	    } else if (*(arg + 5) != '/') {
2281 		p1 = (arg + 5);
2282 	    } else if (*(arg + 5) == '/' && *(arg + 6) != '/') {
2283 		p1 = (arg + 6);
2284 	    } else {
2285 		p1 = (cp + 1);
2286 	    }
2287 	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
2288 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
2289 		    NEWS_NETCLOSE(s);
2290 		    s = -1;
2291 		}
2292 		StrAllocCopy(NewsHost, HTNewsHost);
2293 	    } else {
2294 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
2295 		    NEWS_NETCLOSE(s);
2296 		    s = -1;
2297 		}
2298 		StrAllocCopy(NewsHost, cp);
2299 	    }
2300 	    FREE(cp);
2301 	    SnipIn2(command, "%s//%.*s/", STR_NNTP_URL, 9, NewsHost);
2302 	    StrAllocCopy(NewsHREF, command);
2303 	} else if (!strncasecomp(arg, STR_SNEWS_URL, 6)) {
2304 #ifdef USE_SSL
2305 	    if (((*(arg + 6) == '\0') ||
2306 		 (!strcmp((arg + 6), "/") ||
2307 		  !strcmp((arg + 6), "//") ||
2308 		  !strcmp((arg + 6), "///"))) ||
2309 		((!StrNCmp((arg + 6), "//", 2)) &&
2310 		 (!(cp = strchr((arg + 8), '/')) || *(cp + 1) == '\0'))) {
2311 		p1 = "*";
2312 		group_wanted = FALSE;
2313 		list_wanted = TRUE;
2314 	    } else if (*(arg + 6) != '/') {
2315 		p1 = (arg + 6);
2316 	    } else if (*(arg + 6) == '/' && *(arg + 7) != '/') {
2317 		p1 = (arg + 7);
2318 	    } else {
2319 		p1 = (cp + 1);
2320 	    }
2321 	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
2322 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
2323 		    NEWS_NETCLOSE(s);
2324 		    s = -1;
2325 		}
2326 		StrAllocCopy(NewsHost, HTNewsHost);
2327 	    } else {
2328 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
2329 		    NEWS_NETCLOSE(s);
2330 		    s = -1;
2331 		}
2332 		StrAllocCopy(NewsHost, cp);
2333 	    }
2334 	    FREE(cp);
2335 	    sprintf(command, "%s//%.250s/", STR_SNEWS_URL, NewsHost);
2336 	    StrAllocCopy(NewsHREF, command);
2337 #else
2338 	    HTAlert(gettext("This client does not contain support for SNEWS URLs."));
2339 	    return HT_NOT_LOADED;
2340 #endif /* USE_SSL */
2341 	} else if (!strncasecomp(arg, "news:/", 6)) {
2342 	    if (((*(arg + 6) == '\0') ||
2343 		 !strcmp((arg + 6), "/") ||
2344 		 !strcmp((arg + 6), "//")) ||
2345 		((*(arg + 6) == '/') &&
2346 		 (!(cp = strchr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
2347 		p1 = "*";
2348 		group_wanted = FALSE;
2349 		list_wanted = TRUE;
2350 	    } else if (*(arg + 6) != '/') {
2351 		p1 = (arg + 6);
2352 	    } else {
2353 		p1 = (cp + 1);
2354 	    }
2355 	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
2356 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
2357 		    NEWS_NETCLOSE(s);
2358 		    s = -1;
2359 		}
2360 		StrAllocCopy(NewsHost, HTNewsHost);
2361 	    } else {
2362 		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
2363 		    NEWS_NETCLOSE(s);
2364 		    s = -1;
2365 		}
2366 		StrAllocCopy(NewsHost, cp);
2367 	    }
2368 	    FREE(cp);
2369 	    SnipIn(command, "news://%.*s/", 9, NewsHost);
2370 	    StrAllocCopy(NewsHREF, command);
2371 	} else {
2372 	    p1 = (arg + 5);	/* Skip "news:" prefix */
2373 	    if (*p1 == '\0') {
2374 		p1 = "*";
2375 		group_wanted = FALSE;
2376 		list_wanted = TRUE;
2377 	    }
2378 	    if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
2379 		NEWS_NETCLOSE(s);
2380 		s = -1;
2381 	    }
2382 	    StrAllocCopy(NewsHost, HTNewsHost);
2383 	    StrAllocCopy(NewsHREF, STR_NEWS_URL);
2384 	}
2385 
2386 	/*
2387 	 * Set up any proxy for snews URLs that returns NNTP responses for Lynx
2388 	 * to convert to HTML, instead of doing the conversion itself, and for
2389 	 * handling posts or followups.  - TZ & FM
2390 	 */
2391 	if (!strncasecomp(p1, STR_SNEWS_URL, 6) ||
2392 	    !strncasecomp(p1, "snewspost:", 10) ||
2393 	    !strncasecomp(p1, "snewsreply:", 11)) {
2394 	    StrAllocCopy(ProxyHost, NewsHost);
2395 	    if ((cp = HTParse(p1, "", PARSE_HOST)) != NULL && *cp != '\0') {
2396 		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, cp);
2397 		StrAllocCopy(NewsHost, cp);
2398 	    } else {
2399 		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, NewsHost);
2400 	    }
2401 	    command[sizeof(command) - 2] = '\0';
2402 	    FREE(cp);
2403 	    sprintf(proxycmd, "GET %.*s%c%c%c%c",
2404 		    (int) sizeof(proxycmd) - 9, command,
2405 		    CR, LF, CR, LF);
2406 	    CTRACE((tfp, "HTNews: Proxy command is '%.*s'\n",
2407 		    (int) (strlen(proxycmd) - 4), proxycmd));
2408 	    strcat(command, "/");
2409 	    StrAllocCopy(ProxyHREF, NewsHREF);
2410 	    StrAllocCopy(NewsHREF, command);
2411 	    if (spost_wanted || sreply_wanted) {
2412 		/*
2413 		 * Reset p1 so that it points to the newsgroup(s).
2414 		 */
2415 		if ((p1 = strrchr(arg, '/')) != NULL) {
2416 		    p1++;
2417 		} else {
2418 		    p1 = (strrchr(arg, ':') + 1);
2419 		}
2420 	    } else {
2421 		/*
2422 		 * Reset p1 so that it points to the newsgroup (or a wildcard),
2423 		 * or the article.
2424 		 */
2425 		if (!(cp = strrchr((p1 + 6), '/')) || *(cp + 1) == '\0') {
2426 		    p1 = "*";
2427 		    group_wanted = FALSE;
2428 		    list_wanted = TRUE;
2429 		} else {
2430 		    p1 = (cp + 1);
2431 		}
2432 	    }
2433 	}
2434 
2435 	/*
2436 	 * Set up command for a post, listing, or article request.  - FM
2437 	 */
2438 	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
2439 	    strcpy(command, "POST");
2440 	} else if (list_wanted) {
2441 	    if (strlen(p1) > 249) {
2442 		FREE(ProxyHost);
2443 		FREE(ProxyHREF);
2444 		HTAlert(URL_TOO_LONG);
2445 		return -400;
2446 	    }
2447 	    SnipIn(command, "XGTITLE %.*s", 11, p1);
2448 	} else if (group_wanted) {
2449 	    char *slash = strchr(p1, '/');
2450 
2451 	    first = 0;
2452 	    last = 0;
2453 	    if (slash) {
2454 		*slash = '\0';
2455 		if (strlen(p1) >= sizeof(groupName)) {
2456 		    FREE(ProxyHost);
2457 		    FREE(ProxyHREF);
2458 		    HTAlert(URL_TOO_LONG);
2459 		    return -400;
2460 		}
2461 		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
2462 		*slash = '/';
2463 		(void) sscanf(slash + 1, "%d-%d", &first, &last);
2464 		if ((first > 0) && (isdigit(UCH(*(slash + 1)))) &&
2465 		    (strchr(slash + 1, '-') == NULL || first == last)) {
2466 		    /*
2467 		     * We got a number greater than 0, which will be loaded as
2468 		     * first, and either no range or the range computes to
2469 		     * zero, so make last negative, as a flag to select the
2470 		     * group and then fetch an article by number (first)
2471 		     * instead of by messageID.  - FM
2472 		     */
2473 		    last = -1;
2474 		}
2475 	    } else {
2476 		if (strlen(p1) >= sizeof(groupName)) {
2477 		    FREE(ProxyHost);
2478 		    FREE(ProxyHREF);
2479 		    HTAlert(URL_TOO_LONG);
2480 		    return -400;
2481 		}
2482 		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
2483 	    }
2484 	    SnipIn(command, "GROUP %.*s", 9, groupName);
2485 	} else {
2486 	    size_t add_open = (size_t) (strchr(p1, '<') == 0);
2487 	    size_t add_close = (size_t) (strchr(p1, '>') == 0);
2488 
2489 	    if (strlen(p1) + add_open + add_close >= 252) {
2490 		FREE(ProxyHost);
2491 		FREE(ProxyHREF);
2492 		HTAlert(URL_TOO_LONG);
2493 		return -400;
2494 	    }
2495 	    sprintf(command, "ARTICLE %s%.*s%s",
2496 		    add_open ? "<" : "",
2497 		    (int) (sizeof(command) - (11 + add_open + add_close)),
2498 		    p1,
2499 		    add_close ? ">" : "");
2500 	}
2501 
2502 	{
2503 	    char *p = command + strlen(command);
2504 
2505 	    /*
2506 	     * Terminate command with CRLF, as in RFC 977.
2507 	     */
2508 	    *p++ = CR;		/* Macros to be correct on Mac */
2509 	    *p++ = LF;
2510 	    *p = 0;
2511 	}
2512 	StrAllocCopy(ListArg, p1);
2513     }				/* scope of p1 */
2514 
2515     if (!*arg) {
2516 	FREE(NewsHREF);
2517 	FREE(ProxyHost);
2518 	FREE(ProxyHREF);
2519 	FREE(ListArg);
2520 	return NO;		/* Ignore if no name */
2521     }
2522 
2523     if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted ||
2524 	  (group_wanted && last != -1) || list_wanted)) {
2525 	head_wanted = anAnchor->isHEAD;
2526 	if (head_wanted && !StrNCmp(command, "ARTICLE ", 8)) {
2527 	    /* overwrite "ARTICLE" - hack... */
2528 	    strcpy(command, "HEAD ");
2529 	    for (cp = command + 5;; cp++)
2530 		if ((*cp = *(cp + 3)) == '\0')
2531 		    break;
2532 	}
2533 	rawtext = (BOOL) (head_wanted || keep_mime_headers);
2534     }
2535     if (rawtext) {
2536 	rawtarget = HTStreamStack(WWW_PLAINTEXT,
2537 				  format_out,
2538 				  stream, anAnchor);
2539 	if (!rawtarget) {
2540 	    FREE(NewsHost);
2541 	    FREE(NewsHREF);
2542 	    FREE(ProxyHost);
2543 	    FREE(ProxyHREF);
2544 	    FREE(ListArg);
2545 	    HTAlert(gettext("No target for raw text!"));
2546 	    return (HT_NOT_LOADED);
2547 	}			/* Copy routine entry points */
2548 	rawtargetClass = *rawtarget->isa;
2549     } else
2550 	/*
2551 	 * Make a hypertext object with an anchor list.
2552 	 */
2553     if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted)) {
2554 	target = HTML_new(anAnchor, format_out, stream);
2555 	targetClass = *target->isa;	/* Copy routine entry points */
2556     }
2557 
2558     /*
2559      * Now, let's get a stream setup up from the NewsHost.
2560      */
2561     for (retries = 0; retries < 2; retries++) {
2562 	if (s < 0) {
2563 	    /* CONNECTING to news host */
2564 	    char url[260];
2565 
2566 	    if (!strcmp(NewsHREF, STR_NEWS_URL)) {
2567 		SnipIn(url, "lose://%.*s/", 9, NewsHost);
2568 	    } else if (ProxyHREF) {
2569 		SnipIn(url, "%.*s", 1, ProxyHREF);
2570 	    } else {
2571 		SnipIn(url, "%.*s", 1, NewsHREF);
2572 	    }
2573 	    CTRACE((tfp, "News: doing HTDoConnect on '%s'\n", url));
2574 
2575 	    _HTProgress(gettext("Connecting to NewsHost ..."));
2576 
2577 #ifdef USE_SSL
2578 	    if (!using_proxy &&
2579 		(!StrNCmp(arg, STR_SNEWS_URL, 6) ||
2580 		 !StrNCmp(arg, "snewspost:", 10) ||
2581 		 !StrNCmp(arg, "snewsreply:", 11)))
2582 		status = HTDoConnect(url, "NNTPS", SNEWS_PORT, &s);
2583 	    else
2584 		status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
2585 #else
2586 	    status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
2587 #endif /* USE_SSL */
2588 
2589 	    if (status == HT_INTERRUPTED) {
2590 		/*
2591 		 * Interrupt cleanly.
2592 		 */
2593 		CTRACE((tfp,
2594 			"HTNews: Interrupted on connect; recovering cleanly.\n"));
2595 		_HTProgress(CONNECTION_INTERRUPTED);
2596 		if (!(post_wanted || reply_wanted ||
2597 		      spost_wanted || sreply_wanted)) {
2598 		    ABORT_TARGET;
2599 		}
2600 		FREE(NewsHost);
2601 		FREE(NewsHREF);
2602 		FREE(ProxyHost);
2603 		FREE(ProxyHREF);
2604 		FREE(ListArg);
2605 #ifdef USE_SSL
2606 		if (Handle) {
2607 		    SSL_free(Handle);
2608 		    Handle = NULL;
2609 		}
2610 #endif /* USE_SSL */
2611 		if (postfile) {
2612 		    HTSYS_remove(postfile);
2613 		    FREE(postfile);
2614 		}
2615 		return HT_NOT_LOADED;
2616 	    }
2617 	    if (status < 0) {
2618 		NEWS_NETCLOSE(s);
2619 		s = -1;
2620 		CTRACE((tfp, "HTNews: Unable to connect to news host.\n"));
2621 		if (retries < 1)
2622 		    continue;
2623 		if (!(post_wanted || reply_wanted ||
2624 		      spost_wanted || sreply_wanted)) {
2625 		    ABORT_TARGET;
2626 		}
2627 		HTSprintf0(&dbuf, gettext("Could not access %s."), NewsHost);
2628 		FREE(NewsHost);
2629 		FREE(NewsHREF);
2630 		FREE(ProxyHost);
2631 		FREE(ProxyHREF);
2632 		FREE(ListArg);
2633 		if (postfile) {
2634 		    HTSYS_remove(postfile);
2635 		    FREE(postfile);
2636 		}
2637 		return HTLoadError(stream, 500, dbuf);
2638 	    } else {
2639 		CTRACE((tfp, "HTNews: Connected to news host %s.\n",
2640 			NewsHost));
2641 #ifdef USE_SSL
2642 		/*
2643 		 * If this is an snews url, then do the SSL stuff here
2644 		 */
2645 		if (!using_proxy &&
2646 		    (!StrNCmp(url, "snews", 5) ||
2647 		     !StrNCmp(url, "snewspost:", 10) ||
2648 		     !StrNCmp(url, "snewsreply:", 11))) {
2649 		    Handle = HTGetSSLHandle();
2650 		    SSL_set_fd(Handle, s);
2651 		    HTSSLInitPRNG();
2652 		    status = SSL_connect(Handle);
2653 
2654 		    if (status <= 0) {
2655 			unsigned long SSLerror;
2656 
2657 			CTRACE((tfp,
2658 				"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
2659 				url, status));
2660 			SSL_load_error_strings();
2661 			while ((SSLerror = ERR_get_error()) != 0) {
2662 			    CTRACE((tfp, "HTNews: SSL: %s\n",
2663 				    ERR_error_string(SSLerror, NULL)));
2664 			}
2665 			HTAlert("Unable to make secure connection to remote host.");
2666 			NEWS_NETCLOSE(s);
2667 			s = -1;
2668 			if (!(post_wanted || reply_wanted ||
2669 			      spost_wanted || sreply_wanted))
2670 			    (*targetClass._abort) (target, NULL);
2671 			FREE(NewsHost);
2672 			FREE(NewsHREF);
2673 			FREE(ProxyHost);
2674 			FREE(ProxyHREF);
2675 			FREE(ListArg);
2676 			if (postfile) {
2677 #ifdef VMS
2678 			    while (remove(postfile) == 0) ;	/* loop through all versions */
2679 #else
2680 			    remove(postfile);
2681 #endif /* VMS */
2682 			    FREE(postfile);
2683 			}
2684 			return HT_NOT_LOADED;
2685 		    }
2686 		    sprintf(SSLprogress,
2687 			    "Secure %d-bit %s (%s) NNTP connection",
2688 			    SSL_get_cipher_bits(Handle, NULL),
2689 			    SSL_get_cipher_version(Handle),
2690 			    SSL_get_cipher(Handle));
2691 		    _HTProgress(SSLprogress);
2692 		}
2693 #endif /* USE_SSL */
2694 		HTInitInput(s);	/* set up buffering */
2695 		if (proxycmd[0]) {
2696 		    status = (int) NEWS_NETWRITE(s, proxycmd, (int) strlen(proxycmd));
2697 		    CTRACE((tfp,
2698 			    "HTNews: Proxy command returned status '%d'.\n",
2699 			    status));
2700 		}
2701 		if (((status = response(NULL)) / 100) != 2) {
2702 		    NEWS_NETCLOSE(s);
2703 		    s = -1;
2704 		    if (status == HT_INTERRUPTED) {
2705 			_HTProgress(CONNECTION_INTERRUPTED);
2706 			if (!(post_wanted || reply_wanted ||
2707 			      spost_wanted || sreply_wanted)) {
2708 			    ABORT_TARGET;
2709 			}
2710 			FREE(NewsHost);
2711 			FREE(NewsHREF);
2712 			FREE(ProxyHost);
2713 			FREE(ProxyHREF);
2714 			FREE(ListArg);
2715 			if (postfile) {
2716 			    HTSYS_remove(postfile);
2717 			    FREE(postfile);
2718 			}
2719 			return (HT_NOT_LOADED);
2720 		    }
2721 		    if (retries < 1)
2722 			continue;
2723 		    FREE(ProxyHost);
2724 		    FREE(ProxyHREF);
2725 		    FREE(ListArg);
2726 		    FREE(postfile);
2727 		    if (!(post_wanted || reply_wanted ||
2728 			  spost_wanted || sreply_wanted)) {
2729 			ABORT_TARGET;
2730 		    }
2731 		    if (response_text[0]) {
2732 			HTSprintf0(&dbuf,
2733 				   gettext("Can't read news info.  News host %.20s responded: %.200s"),
2734 				   NewsHost, response_text);
2735 		    } else {
2736 			HTSprintf0(&dbuf,
2737 				   gettext("Can't read news info, empty response from host %s"),
2738 				   NewsHost);
2739 		    }
2740 		    return HTLoadError(stream, 500, dbuf);
2741 		}
2742 		if (status == 200) {
2743 		    HTCanPost = TRUE;
2744 		} else {
2745 		    HTCanPost = FALSE;
2746 		    if (post_wanted || reply_wanted ||
2747 			spost_wanted || sreply_wanted) {
2748 			HTAlert(CANNOT_POST);
2749 			FREE(NewsHREF);
2750 			if (ProxyHREF) {
2751 			    StrAllocCopy(NewsHost, ProxyHost);
2752 			    FREE(ProxyHost);
2753 			    FREE(ProxyHREF);
2754 			}
2755 			FREE(ListArg);
2756 			if (postfile) {
2757 			    HTSYS_remove(postfile);
2758 			    FREE(postfile);
2759 			}
2760 			return (HT_NOT_LOADED);
2761 		    }
2762 		}
2763 	    }
2764 	}
2765 	/* If needed opening */
2766 	if (post_wanted || reply_wanted ||
2767 	    spost_wanted || sreply_wanted) {
2768 	    if (!HTCanPost) {
2769 		HTAlert(CANNOT_POST);
2770 		FREE(NewsHREF);
2771 		if (ProxyHREF) {
2772 		    StrAllocCopy(NewsHost, ProxyHost);
2773 		    FREE(ProxyHost);
2774 		    FREE(ProxyHREF);
2775 		}
2776 		FREE(ListArg);
2777 		if (postfile) {
2778 		    HTSYS_remove(postfile);
2779 		    FREE(postfile);
2780 		}
2781 		return (HT_NOT_LOADED);
2782 	    }
2783 	    if (postfile == NULL) {
2784 		postfile = LYNewsPost(ListArg,
2785 				      (reply_wanted || sreply_wanted));
2786 	    }
2787 	    if (postfile == NULL) {
2788 		HTProgress(CANCELLED);
2789 		FREE(NewsHREF);
2790 		if (ProxyHREF) {
2791 		    StrAllocCopy(NewsHost, ProxyHost);
2792 		    FREE(ProxyHost);
2793 		    FREE(ProxyHREF);
2794 		}
2795 		FREE(ListArg);
2796 		return (HT_NOT_LOADED);
2797 	    }
2798 	} else {
2799 	    /*
2800 	     * Ensure reader mode, but don't bother checking the status for
2801 	     * anything but HT_INTERRUPTED or a 480 Authorization request,
2802 	     * because if the reader mode command is not needed, the server
2803 	     * probably returned a 500, which is irrelevant at this point.  -
2804 	     * FM
2805 	     */
2806 	    char buffer[20];
2807 
2808 	    sprintf(buffer, "mode reader%c%c", CR, LF);
2809 	    if ((status = response(buffer)) == HT_INTERRUPTED) {
2810 		_HTProgress(CONNECTION_INTERRUPTED);
2811 		break;
2812 	    }
2813 	    if (status == 480) {
2814 		NNTPAuthResult auth_result = HTHandleAuthInfo(NewsHost);
2815 
2816 		if (auth_result == NNTPAUTH_CLOSE) {
2817 		    if (s != -1 && !(ProxyHost || ProxyHREF)) {
2818 			NEWS_NETCLOSE(s);
2819 			s = -1;
2820 		    }
2821 		}
2822 		if (auth_result != NNTPAUTH_OK) {
2823 		    break;
2824 		}
2825 		if (response(buffer) == HT_INTERRUPTED) {
2826 		    _HTProgress(CONNECTION_INTERRUPTED);
2827 		    break;
2828 		}
2829 	    }
2830 	}
2831 
2832       Send_NNTP_command:
2833 #ifdef NEWS_DEB
2834 	if (postfile)
2835 	    printf("postfile = %s, command = %s", postfile, command);
2836 	else
2837 	    printf("command = %s", command);
2838 #endif
2839 	if ((status = response(command)) == HT_INTERRUPTED) {
2840 	    _HTProgress(CONNECTION_INTERRUPTED);
2841 	    break;
2842 	}
2843 	if (status < 0) {
2844 	    if (retries < 1) {
2845 		continue;
2846 	    } else {
2847 		break;
2848 	    }
2849 	}
2850 	/*
2851 	 * For some well known error responses which are expected to occur in
2852 	 * normal use, break from the loop without retrying and without closing
2853 	 * the connection.  It is unlikely that these are leftovers from a
2854 	 * timed-out connection (but we do some checks to see whether the
2855 	 * response corresponds to the last command), or that they will give
2856 	 * anything else when automatically retried.  - kw
2857 	 */
2858 	if (status == 411 && group_wanted &&
2859 	    !StrNCmp(command, "GROUP ", 6) &&
2860 	    !strncasecomp(response_text + 3, " No such group ", 15) &&
2861 	    !strcmp(response_text + 18, groupName)) {
2862 
2863 	    HTAlert(response_text);
2864 	    break;
2865 	} else if (status == 430 && !group_wanted && !list_wanted &&
2866 		   !StrNCmp(command, "ARTICLE <", 9) &&
2867 		   !strcasecomp(response_text + 3, " No such article")) {
2868 
2869 	    HTAlert(response_text);
2870 	    break;
2871 	}
2872 	if ((status / 100) != 2 &&
2873 	    status != 340 &&
2874 	    status != 480) {
2875 	    if (retries) {
2876 		if (list_wanted && !StrNCmp(command, "XGTITLE", 7)) {
2877 		    sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
2878 		    goto Send_NNTP_command;
2879 		}
2880 		HTAlert(response_text);
2881 	    } else {
2882 		_HTProgress(response_text);
2883 	    }
2884 	    NEWS_NETCLOSE(s);
2885 	    s = -1;
2886 	    /*
2887 	     * Message might be a leftover "Timeout-disconnected", so try again
2888 	     * if the retries maximum has not been reached.
2889 	     */
2890 	    continue;
2891 	}
2892 
2893 	/*
2894 	 * Post or load a group, article, etc
2895 	 */
2896 	if (status == 480) {
2897 	    NNTPAuthResult auth_result;
2898 
2899 	    /*
2900 	     * Some servers return 480 for a failed XGTITLE.  - FM
2901 	     */
2902 	    if (list_wanted && !StrNCmp(command, "XGTITLE", 7) &&
2903 		strstr(response_text, "uthenticat") == NULL &&
2904 		strstr(response_text, "uthor") == NULL) {
2905 		sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
2906 		goto Send_NNTP_command;
2907 	    }
2908 	    /*
2909 	     * Handle Authorization.  - FM
2910 	     */
2911 	    if ((auth_result = HTHandleAuthInfo(NewsHost)) == NNTPAUTH_OK) {
2912 		goto Send_NNTP_command;
2913 	    } else if (auth_result == NNTPAUTH_CLOSE) {
2914 		if (s != -1 && !(ProxyHost || ProxyHREF)) {
2915 		    NEWS_NETCLOSE(s);
2916 		    s = -1;
2917 		}
2918 		if (retries < 1)
2919 		    continue;
2920 	    }
2921 	    status = HT_NOT_LOADED;
2922 	} else if (post_wanted || reply_wanted ||
2923 		   spost_wanted || sreply_wanted) {
2924 	    /*
2925 	     * Handle posting of an article.  - FM
2926 	     */
2927 	    if (status != 340) {
2928 		HTAlert(CANNOT_POST);
2929 		if (postfile) {
2930 		    HTSYS_remove(postfile);
2931 		}
2932 	    } else {
2933 		post_article(postfile);
2934 	    }
2935 	    FREE(postfile);
2936 	    status = HT_NOT_LOADED;
2937 	} else if (list_wanted) {
2938 	    /*
2939 	     * List available newsgroups.  - FM
2940 	     */
2941 	    _HTProgress(gettext("Reading list of available newsgroups."));
2942 	    status = read_list(ListArg);
2943 	} else if (group_wanted) {
2944 	    /*
2945 	     * List articles in a news group.  - FM
2946 	     */
2947 	    if (last < 0) {
2948 		/*
2949 		 * We got one article number rather than a range following the
2950 		 * slash which followed the group name, or the range was zero,
2951 		 * so now that we have selected that group, load ARTICLE and
2952 		 * the the number (first) as the command and go back to send it
2953 		 * and check the response.  - FM
2954 		 */
2955 		sprintf(command, "%s %d%c%c",
2956 			head_wanted ? "HEAD" : "ARTICLE",
2957 			first, CR, LF);
2958 		group_wanted = FALSE;
2959 		retries = 2;
2960 		goto Send_NNTP_command;
2961 	    }
2962 	    _HTProgress(gettext("Reading list of articles in newsgroup."));
2963 	    status = read_group(groupName, first, last);
2964 	} else {
2965 	    /*
2966 	     * Get an article from a news group.  - FM
2967 	     */
2968 	    _HTProgress(gettext("Reading news article."));
2969 	    status = read_article(anAnchor);
2970 	}
2971 	if (status == HT_INTERRUPTED) {
2972 	    _HTProgress(CONNECTION_INTERRUPTED);
2973 	    status = HT_LOADED;
2974 	}
2975 	if (!(post_wanted || reply_wanted ||
2976 	      spost_wanted || sreply_wanted)) {
2977 	    if (status == HT_NOT_LOADED) {
2978 		ABORT_TARGET;
2979 	    } else {
2980 		FREE_TARGET;
2981 	    }
2982 	}
2983 	FREE(NewsHREF);
2984 	if (ProxyHREF) {
2985 	    StrAllocCopy(NewsHost, ProxyHost);
2986 	    FREE(ProxyHost);
2987 	    FREE(ProxyHREF);
2988 	}
2989 	FREE(ListArg);
2990 	if (postfile) {
2991 	    HTSYS_remove(postfile);
2992 	    FREE(postfile);
2993 	}
2994 	return status;
2995     }				/* Retry loop */
2996 
2997 #if 0
2998     HTAlert(gettext("Sorry, could not load requested news."));
2999     NXRunAlertPanel(NULL, "Sorry, could not load `%s'.", NULL, NULL, NULL, arg);
3000     /* No -- message earlier wil have covered it */
3001 #endif
3002 
3003     if (!(post_wanted || reply_wanted ||
3004 	  spost_wanted || sreply_wanted)) {
3005 	ABORT_TARGET;
3006     }
3007     FREE(NewsHREF);
3008     if (ProxyHREF) {
3009 	StrAllocCopy(NewsHost, ProxyHost);
3010 	FREE(ProxyHost);
3011 	FREE(ProxyHREF);
3012     }
3013     FREE(ListArg);
3014     if (postfile) {
3015 	HTSYS_remove(postfile);
3016 	FREE(postfile);
3017     }
3018     return HT_NOT_LOADED;
3019 }
3020 
3021 /*
3022  *  This function clears all authorization information by
3023  *  invoking the free_NNTP_AuthInfo() function, which normally
3024  *  is invoked at exit.  It allows a browser command to do
3025  *  this at any time, for example, if the user is leaving
3026  *  the terminal for a period of time, but does not want
3027  *  to end the current session.  - FM
3028  */
HTClearNNTPAuthInfo(void)3029 void HTClearNNTPAuthInfo(void)
3030 {
3031     /*
3032      * Need code to check cached documents and do something to ensure that any
3033      * protected documents no longer can be accessed without a new retrieval.
3034      * - FM
3035      */
3036 
3037     /*
3038      * Now free all of the authorization info.  - FM
3039      */
3040     free_NNTP_AuthInfo();
3041 }
3042 
3043 #ifdef USE_SSL
HTNewsGetCharacter(void)3044 static int HTNewsGetCharacter(void)
3045 {
3046     if (!Handle)
3047 	return HTGetCharacter();
3048     else
3049 	return HTGetSSLCharacter((void *) Handle);
3050 }
3051 
HTNewsProxyConnect(int sock,const char * url,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)3052 int HTNewsProxyConnect(int sock,
3053 		       const char *url,
3054 		       HTParentAnchor *anAnchor,
3055 		       HTFormat format_out,
3056 		       HTStream *sink)
3057 {
3058     int status;
3059     const char *arg = url;
3060     char SSLprogress[256];
3061 
3062     s = channel_s = sock;
3063     Handle = HTGetSSLHandle();
3064     SSL_set_fd(Handle, s);
3065     HTSSLInitPRNG();
3066     status = SSL_connect(Handle);
3067 
3068     if (status <= 0) {
3069 	unsigned long SSLerror;
3070 
3071 	channel_s = -1;
3072 	CTRACE((tfp,
3073 		"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
3074 		url, status));
3075 	SSL_load_error_strings();
3076 	while ((SSLerror = ERR_get_error()) != 0) {
3077 	    CTRACE((tfp, "HTNews: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
3078 	}
3079 	HTAlert("Unable to make secure connection to remote host.");
3080 	NEWS_NETCLOSE(s);
3081 	s = -1;
3082 	return HT_NOT_LOADED;
3083     }
3084     sprintf(SSLprogress, "Secure %d-bit %s (%s) NNTP connection",
3085 	    SSL_get_cipher_bits(Handle, NULL),
3086 	    SSL_get_cipher_version(Handle),
3087 	    SSL_get_cipher(Handle));
3088     _HTProgress(SSLprogress);
3089     status = HTLoadNews(arg, anAnchor, format_out, sink);
3090     channel_s = -1;
3091     return status;
3092 }
3093 #endif /* USE_SSL */
3094 
3095 #ifdef GLOBALDEF_IS_MACRO
3096 #define _HTNEWS_C_1_INIT { "news", HTLoadNews, NULL }
3097 GLOBALDEF(HTProtocol, HTNews, _HTNEWS_C_1_INIT);
3098 #define _HTNEWS_C_2_INIT { "nntp", HTLoadNews, NULL }
3099 GLOBALDEF(HTProtocol, HTNNTP, _HTNEWS_C_2_INIT);
3100 #define _HTNEWS_C_3_INIT { "newspost", HTLoadNews, NULL }
3101 GLOBALDEF(HTProtocol, HTNewsPost, _HTNEWS_C_3_INIT);
3102 #define _HTNEWS_C_4_INIT { "newsreply", HTLoadNews, NULL }
3103 GLOBALDEF(HTProtocol, HTNewsReply, _HTNEWS_C_4_INIT);
3104 #define _HTNEWS_C_5_INIT { "snews", HTLoadNews, NULL }
3105 GLOBALDEF(HTProtocol, HTSNews, _HTNEWS_C_5_INIT);
3106 #define _HTNEWS_C_6_INIT { "snewspost", HTLoadNews, NULL }
3107 GLOBALDEF(HTProtocol, HTSNewsPost, _HTNEWS_C_6_INIT);
3108 #define _HTNEWS_C_7_INIT { "snewsreply", HTLoadNews, NULL }
3109 GLOBALDEF(HTProtocol, HTSNewsReply, _HTNEWS_C_7_INIT);
3110 #else
3111 GLOBALDEF HTProtocol HTNews =
3112 {"news", HTLoadNews, NULL};
3113 GLOBALDEF HTProtocol HTNNTP =
3114 {"nntp", HTLoadNews, NULL};
3115 GLOBALDEF HTProtocol HTNewsPost =
3116 {"newspost", HTLoadNews, NULL};
3117 GLOBALDEF HTProtocol HTNewsReply =
3118 {"newsreply", HTLoadNews, NULL};
3119 GLOBALDEF HTProtocol HTSNews =
3120 {"snews", HTLoadNews, NULL};
3121 GLOBALDEF HTProtocol HTSNewsPost =
3122 {"snewspost", HTLoadNews, NULL};
3123 GLOBALDEF HTProtocol HTSNewsReply =
3124 {"snewsreply", HTLoadNews, NULL};
3125 #endif /* GLOBALDEF_IS_MACRO */
3126 
3127 #endif /* not DISABLE_NEWS */
3128