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