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