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