about summary refs log blame commit diff stats
path: root/WWW/Library/Implementation/HTNews.c
blob: 3cd1b791ad17231a97538a0e23ed698175661044 (plain) (tree)
1
2
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
  
                                                         











































                                                                              







                                                            



                                         


                                          
                             








                                       









































































































































































































                                                                              
                                                    






















































































































































































































































                                                                                          
                                                       



































































































































































                                                                                
                                                                     




























































































































































































                                                                                

                              











































                                                                            
                                              




































































































































































































                                                                               
                                                                               

                                                       
                                                                            


                                                               
                                                                      


                                                       
                                                                            


                                                           
                                                                               


                                                             
                                                                                  

                                                             
                                                                                  

                                                              
                                                                                  
































                                                                             
                                                         






















































































                                                  

                                               





                                         

                                               























































                                                                           



                                                           














                                                            
                                                                      












































































































































                                                                                 
                                                                             





































                                                                         
                                                                     


















































































































                                                                                          

                                                    

































































































































































































































                                                                                                           
                                                        




























































































































                                                                                       
                                                                            










                                                                              
                                                                             










































































































































































































                                                                                    

                                                            


                                                                 

                                                           


































































                                                                              
                                                                          
































                                                                              
                                                                          


































                                                                                     
                                                                          





































































































                                                                               
                                          














                                                                  
                                                                        



















                                                                              

                                                               































































































































































































































































































































































































































































































































































































































































                                                                                                                                    
/*
 * $LynxId: HTNews.c,v 1.74 2019/08/20 09:03:18 tom Exp $
 *
 *			NEWS ACCESS				HTNews.c
 *			===========
 *
 * History:
 *	26 Sep 90	Written TBL
 *	29 Nov 91	Downgraded to C, for portable implementation.
 */

#include <HTUtils.h>		/* Coding convention macros */

#ifndef DISABLE_NEWS

/* Implements:
*/
#include <HTNews.h>

#include <HTCJK.h>
#include <HTMIME.h>
#include <HTFont.h>
#include <HTFormat.h>
#include <HTTCP.h>
#include <LYUtils.h>
#include <LYStrings.h>

#define NEWS_PORT 119		/* See rfc977 */
#define SNEWS_PORT 563		/* See Lou Montulli */
#define APPEND			/* Use append methods */
int HTNewsChunkSize = 30;	/* Number of articles for quick display */
int HTNewsMaxChunk = 40;	/* Largest number of articles in one window */

#ifndef DEFAULT_NEWS_HOST
#define DEFAULT_NEWS_HOST "news"
#endif /* DEFAULT_NEWS_HOST */

#ifndef NEWS_SERVER_FILE
#define NEWS_SERVER_FILE "/usr/local/lib/rn/server"
#endif /* NEWS_SERVER_FILE */

#ifndef NEWS_AUTH_FILE
#define NEWS_AUTH_FILE ".newsauth"
#endif /* NEWS_AUTH_FILE */

#ifdef USE_SSL

#if defined(LIBRESSL_VERSION_NUMBER)
/* OpenSSL and LibreSSL version numbers do not correspond */
#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
#undef  SSL_load_error_strings
#define SSL_load_error_strings()	/* nothing */
#endif

static SSL *Handle = NULL;
static int channel_s = 1;

#define NEWS_NETWRITE(sock, buff, size) \
	((Handle != NULL) \
	 ? SSL_write(Handle, buff, size) \
	 : NETWRITE(sock, buff, size))
#define NEWS_NETCLOSE(sock) \
	{ \
	    if ((int)(sock) >= 0) { \
	        (void)NETCLOSE(sock); \
	    } \
	    if (Handle != NULL) { \
	        SSL_free(Handle); \
	        Handle = NULL; \
	    } \
	}
static int HTNewsGetCharacter(void);

#define NEXT_CHAR HTNewsGetCharacter()
#else
#define NEWS_NETWRITE  NETWRITE
#define NEWS_NETCLOSE  NETCLOSE
#define NEXT_CHAR HTGetCharacter()
#endif /* USE_SSL */

#include <HTML.h>
#include <HTAccess.h>
#include <HTParse.h>
#include <HTFormat.h>
#include <HTAlert.h>

#include <LYNews.h>
#include <LYGlobalDefs.h>
#include <LYLeaks.h>

#define SnipIn(d,fmt,len,s)      sprintf(d, fmt,      (int)sizeof(d)-len, s)
#define SnipIn2(d,fmt,tag,len,s) sprintf(d, fmt, tag, (int)sizeof(d)-len, s)

struct _HTStructured {
    const HTStructuredClass *isa;
    /* ... */
};

#define LINE_LENGTH 512		/* Maximum length of line of ARTICLE etc */
#define GROUP_NAME_LENGTH	256	/* Maximum length of group name */

/*
 *  Module-wide variables.
 */
char *HTNewsHost = NULL;	/* Default host */
static char *NewsHost = NULL;	/* Current host */
static char *NewsHREF = NULL;	/* Current HREF prefix */
static int s;			/* Socket for NewsHost */
static int HTCanPost = FALSE;	/* Current POST permission */
static char response_text[LINE_LENGTH + 1];	/* Last response */

static HTStructured *target;	/* The output sink */
static HTStructuredClass targetClass;	/* Copy of fn addresses */
static HTStream *rawtarget = NULL;	/* The output sink for rawtext */
static HTStreamClass rawtargetClass;	/* Copy of fn addresses */
static int diagnostic;		/* level: 0=none 2=source */
static BOOL rawtext = NO;	/* Flag: HEAD or -mime_headers */
static HTList *NNTP_AuthInfo = NULL;	/* AUTHINFO database */
static char *name = NULL;
static char *address = NULL;
static char *dbuf = NULL;	/* dynamic buffer for long messages etc. */

#define PUTC(c) (*targetClass.put_character)(target, c)
#define PUTS(s) (*targetClass.put_string)(target, s)
#define RAW_PUTS(s) (*rawtargetClass.put_string)(rawtarget, s)
#define START(e) (*targetClass.start_element)(target, e, 0, 0, -1, 0)
#define END(e) (*targetClass.end_element)(target, e, 0)
#define MAYBE_END(e) if (HTML_dtd.tags[e].contents != SGML_EMPTY) \
			(*targetClass.end_element)(target, e, 0)
#define FREE_TARGET if (rawtext) (*rawtargetClass._free)(rawtarget); \
			else (*targetClass._free)(target)
#define ABORT_TARGET if (rawtext) (*rawtargetClass._abort)(rawtarget, NULL); \
			else (*targetClass._abort)(target, NULL)

typedef struct _NNTPAuth {
    char *host;
    char *user;
    char *pass;
} NNTPAuth;

#ifdef LY_FIND_LEAKS
static void free_news_globals(void)
{
    if (s >= 0) {
	NEWS_NETCLOSE(s);
	s = -1;
    }
    FREE(HTNewsHost);
    FREE(NewsHost);
    FREE(NewsHREF);
    FREE(name);
    FREE(address);
    FREE(dbuf);
}
#endif /* LY_FIND_LEAKS */

static void free_NNTP_AuthInfo(void)
{
    HTList *cur = NNTP_AuthInfo;
    NNTPAuth *auth = NULL;

    if (!cur)
	return;

    while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
	FREE(auth->host);
	FREE(auth->user);
	FREE(auth->pass);
	FREE(auth);
    }
    HTList_delete(NNTP_AuthInfo);
    NNTP_AuthInfo = NULL;
    return;
}

/*
 * Initialize the authentication list by loading the user's $HOME/.newsauth
 * file.  That file is part of tin's configuration and is used by a few other
 * programs.
 */
static void load_NNTP_AuthInfo(void)
{
    FILE *fp;
    char fname[LY_MAXPATH];
    char buffer[LINE_LENGTH + 1];

    LYAddPathToHome(fname, sizeof(fname), NEWS_AUTH_FILE);

    if ((fp = fopen(fname, "r")) != 0) {
	while (fgets(buffer, (int) sizeof(buffer), fp) != 0) {
	    char the_host[LINE_LENGTH + 1];
	    char the_pass[LINE_LENGTH + 1];
	    char the_user[LINE_LENGTH + 1];

	    if (sscanf(buffer, "%s%s%s", the_host, the_pass, the_user) == 3
		&& strlen(the_host) != 0
		&& strlen(the_pass) != 0
		&& strlen(the_user) != 0) {
		NNTPAuth *auth = typecalloc(NNTPAuth);

		if (auth == NULL)
		    break;
		StrAllocCopy(auth->host, the_host);
		StrAllocCopy(auth->pass, the_pass);
		StrAllocCopy(auth->user, the_user);

		HTList_appendObject(NNTP_AuthInfo, auth);
	    }
	}
	fclose(fp);
    }
}

const char *HTGetNewsHost(void)
{
    return HTNewsHost;
}

void HTSetNewsHost(const char *value)
{
    StrAllocCopy(HTNewsHost, value);
}

/*	Initialisation for this module
 *	------------------------------
 *
 *	Except on the NeXT, we pick up the NewsHost name from
 *
 *	1.	Environment variable NNTPSERVER
 *	2.	File NEWS_SERVER_FILE
 *	3.	Compilation time macro DEFAULT_NEWS_HOST
 *	4.	Default to "news"
 *
 *	On the NeXT, we pick up the NewsHost name from, in order:
 *
 *	1.	WorldWideWeb default "NewsHost"
 *	2.	Global default "NewsHost"
 *	3.	News default "NewsHost"
 *	4.	Compilation time macro DEFAULT_NEWS_HOST
 *	5.	Default to "news"
 */
static BOOL initialized = NO;
static BOOL initialize(void)
{
#ifdef NeXTStep
    char *cp = NULL;
#endif

    /*
     * Get name of Host.
     */
#ifdef NeXTStep
    if ((cp = NXGetDefaultValue("WorldWideWeb", "NewsHost")) == 0) {
	if ((cp = NXGetDefaultValue("News", "NewsHost")) == 0) {
	    StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
	}
    }
    if (cp) {
	StrAllocCopy(HTNewsHost, cp);
	cp = NULL;
    }
#else
    if (LYGetEnv("NNTPSERVER")) {
	StrAllocCopy(HTNewsHost, LYGetEnv("NNTPSERVER"));
	CTRACE((tfp, "HTNews: NNTPSERVER defined as `%s'\n",
		HTNewsHost));
    } else {
	FILE *fp = fopen(NEWS_SERVER_FILE, TXT_R);

	if (fp) {
	    char server_name[MAXHOSTNAMELEN + 1];

	    if (fgets(server_name, (int) sizeof server_name, fp) != NULL) {
		char *p = StrChr(server_name, '\n');

		if (p != NULL)
		    *p = '\0';
		StrAllocCopy(HTNewsHost, server_name);
		CTRACE((tfp, "HTNews: File %s defines news host as `%s'\n",
			NEWS_SERVER_FILE, HTNewsHost));
	    }
	    fclose(fp);
	}
    }
    if (!HTNewsHost)
	StrAllocCopy(HTNewsHost, DEFAULT_NEWS_HOST);
#endif /* NeXTStep */

    s = -1;			/* Disconnected */
#ifdef LY_FIND_LEAKS
    atexit(free_news_globals);
#endif
    return YES;
}

/*	Send NNTP Command line to remote host & Check Response
 *	------------------------------------------------------
 *
 * On entry,
 *	command points to the command to be sent, including CRLF, or is null
 *		pointer if no command to be sent.
 * On exit,
 *	Negative status indicates transmission error, socket closed.
 *	Positive status is an NNTP status.
 */
static int response(char *command)
{
    int result;
    char *p = response_text;
    int ich;

    if (command) {
	int status;
	int length = (int) strlen(command);

	CTRACE((tfp, "NNTP command to be sent: %s", command));
#ifdef NOT_ASCII
	{
	    const char *p2;
	    char *q;
	    char ascii[LINE_LENGTH + 1];

	    for (p2 = command, q = ascii; *p2; p2++, q++) {
		*q = TOASCII(*p2);
	    }
	    status = NEWS_NETWRITE(s, ascii, length);
	}
#else
	status = (int) NEWS_NETWRITE(s, (char *) command, length);
#endif /* NOT_ASCII */
	if (status < 0) {
	    CTRACE((tfp, "HTNews: Unable to send command. Disconnecting.\n"));
	    NEWS_NETCLOSE(s);
	    s = -1;
	    return status;
	}			/* if bad status */
    }
    /* if command to be sent */
    for (;;) {
	ich = NEXT_CHAR;
	if (((*p++ = (char) ich) == LF) ||
	    (p == &response_text[LINE_LENGTH])) {
	    *--p = '\0';	/* Terminate the string */
	    CTRACE((tfp, "NNTP Response: %s\n", response_text));
	    sscanf(response_text, "%d", &result);
	    return result;
	}
	/* if end of line */
	if (ich == EOF) {
	    *(p - 1) = '\0';
	    if (interrupted_in_htgetcharacter) {
		CTRACE((tfp,
			"HTNews: Interrupted on read, closing socket %d\n",
			s));
	    } else {
		CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n",
			s));
	    }
	    NEWS_NETCLOSE(s);	/* End of file, close socket */
	    s = -1;
	    if (interrupted_in_htgetcharacter) {
		interrupted_in_htgetcharacter = 0;
		return (HT_INTERRUPTED);
	    }
	    return ((int) EOF);	/* End of file on response */
	}
    }				/* Loop over characters */
}

/*	Case insensitive string comparisons
 *	-----------------------------------
 *
 * On entry,
 *	template must be already in upper case.
 *	unknown may be in upper or lower or mixed case to match.
 */
static BOOL match(const char *unknown, const char *ctemplate)
{
    const char *u = unknown;
    const char *t = ctemplate;

    for (; *u && *t && (TOUPPER(*u) == *t); u++, t++) ;		/* Find mismatch or end */
    return (BOOL) (*t == 0);	/* OK if end of template */
}

typedef enum {
    NNTPAUTH_ERROR = 0,		/* general failure */
    NNTPAUTH_OK = 281,		/* authenticated successfully */
    NNTPAUTH_CLOSE = 502	/* server probably closed connection */
} NNTPAuthResult;

/*
 *  This function handles nntp authentication. - FM
 */
static NNTPAuthResult HTHandleAuthInfo(char *host)
{
    HTList *cur = NULL;
    NNTPAuth *auth = NULL;
    char *UserName = NULL;
    char *PassWord = NULL;
    char *msg = NULL;
    char buffer[512];
    int status, tries;

    /*
     * Make sure we have a host.  - FM
     */
    if (isEmpty(host))
	return NNTPAUTH_ERROR;

    /*
     * Check for an existing authorization entry.  - FM
     */
    if (NNTP_AuthInfo == NULL) {
	NNTP_AuthInfo = HTList_new();
	load_NNTP_AuthInfo();
#ifdef LY_FIND_LEAKS
	atexit(free_NNTP_AuthInfo);
#endif
    }

    cur = NNTP_AuthInfo;
    while (NULL != (auth = (NNTPAuth *) HTList_nextObject(cur))) {
	if (!strcmp(auth->host, host)) {
	    UserName = auth->user;
	    PassWord = auth->pass;
	    break;
	}
    }

    /*
     * Handle the username.  - FM
     */
    buffer[sizeof(buffer) - 1] = '\0';
    tries = 3;

    while (tries) {
	if (UserName == NULL) {
	    HTSprintf0(&msg, gettext("Username for news host '%s':"), host);
	    UserName = HTPrompt(msg, NULL);
	    FREE(msg);
	    if (!(UserName && *UserName)) {
		FREE(UserName);
		return NNTPAUTH_ERROR;
	    }
	}
	sprintf(buffer, "AUTHINFO USER %.*s%c%c",
		(int) sizeof(buffer) - 17, UserName, CR, LF);
	if ((status = response(buffer)) < 0) {
	    if (status == HT_INTERRUPTED)
		_HTProgress(CONNECTION_INTERRUPTED);
	    else
		HTAlert(FAILED_CONNECTION_CLOSED);
	    if (auth) {
		if (auth->user != UserName) {
		    FREE(auth->user);
		    auth->user = UserName;
		}
	    } else {
		FREE(UserName);
	    }
	    return NNTPAUTH_CLOSE;
	}
	if (status == 281) {
	    /*
	     * Username is accepted and no password is required.  - FM
	     */
	    if (auth) {
		if (auth->user != UserName) {
		    FREE(auth->user);
		    auth->user = UserName;
		}
	    } else {
		/*
		 * Store the accepted username and no password.  - FM
		 */
		if ((auth = typecalloc(NNTPAuth)) != NULL) {
		    StrAllocCopy(auth->host, host);
		    auth->user = UserName;
		    HTList_appendObject(NNTP_AuthInfo, auth);
		}
	    }
	    return NNTPAUTH_OK;
	}
	if (status != 381) {
	    /*
	     * Not success, nor a request for the password, so it must be an
	     * error.  - FM
	     */
	    HTAlert(response_text);
	    tries--;
	    if ((tries > 0) && HTConfirm(gettext("Change username?"))) {
		if (!auth || auth->user != UserName) {
		    FREE(UserName);
		}
		if ((UserName = HTPrompt(gettext("Username:"), UserName))
		    != NULL &&
		    *UserName) {
		    continue;
		}
	    }
	    if (auth) {
		if (auth->user != UserName) {
		    FREE(auth->user);
		}
		FREE(auth->pass);
	    }
	    FREE(UserName);
	    return NNTPAUTH_ERROR;
	}
	break;
    }

    if (status == 381) {
	/*
	 * Handle the password.  - FM
	 */
	tries = 3;
	while (tries) {
	    if (PassWord == NULL) {
		HTSprintf0(&msg, gettext("Password for news host '%s':"), host);
		PassWord = HTPromptPassword(msg, NULL);
		FREE(msg);
		if (!(PassWord && *PassWord)) {
		    FREE(PassWord);
		    return NNTPAUTH_ERROR;
		}
	    }
	    sprintf(buffer, "AUTHINFO PASS %.*s%c%c",
		    (int) sizeof(buffer) - 17, PassWord, CR, LF);
	    if ((status = response(buffer)) < 0) {
		if (status == HT_INTERRUPTED) {
		    _HTProgress(CONNECTION_INTERRUPTED);
		} else {
		    HTAlert(FAILED_CONNECTION_CLOSED);
		}
		if (auth) {
		    if (auth->user != UserName) {
			FREE(auth->user);
			auth->user = UserName;
		    }
		    if (auth->pass != PassWord) {
			FREE(auth->pass);
			auth->pass = PassWord;
		    }
		} else {
		    FREE(UserName);
		    FREE(PassWord);
		}
		return NNTPAUTH_CLOSE;
	    }
	    if (status == 502) {
		/*
		 * That's what INN's nnrpd returns.  It closes the connection
		 * after this.  - kw
		 */
		HTAlert(response_text);
		if (auth) {
		    if (auth->user == UserName)
			UserName = NULL;
		    FREE(auth->user);
		    if (auth->pass == PassWord)
			PassWord = NULL;
		    FREE(auth->pass);
		}
		FREE(UserName);
		FREE(PassWord);
		return NNTPAUTH_CLOSE;
	    }
	    if (status == 281) {
		/*
		 * Password also is accepted, and everything has been stored. 
		 * - FM
		 */
		if (auth) {
		    if (auth->user != UserName) {
			FREE(auth->user);
			auth->user = UserName;
		    }
		    if (auth->pass != PassWord) {
			FREE(auth->pass);
			auth->pass = PassWord;
		    }
		} else {
		    if ((auth = typecalloc(NNTPAuth)) != NULL) {
			StrAllocCopy(auth->host, host);
			auth->user = UserName;
			auth->pass = PassWord;
			HTList_appendObject(NNTP_AuthInfo, auth);
		    }
		}
		return NNTPAUTH_OK;
	    }
	    /*
	     * Not success, so it must be an error.  - FM
	     */
	    HTAlert(response_text);
	    if (!auth || auth->pass != PassWord) {
		FREE(PassWord);
	    } else {
		PassWord = NULL;
	    }
	    tries--;
	    if ((tries > 0) && HTConfirm(gettext("Change password?"))) {
		continue;
	    }
	    if (auth) {
		if (auth->user == UserName)
		    UserName = NULL;
		FREE(auth->user);
		FREE(auth->pass);
	    }
	    FREE(UserName);
	    break;
	}
    }

    return NNTPAUTH_ERROR;
}

/*	Find Author's name in mail address
 *	----------------------------------
 *
 * On exit,
 *	Returns allocated string which cannot be freed by the
 *	calling function, and is reallocated on subsequent calls
 *	to this function.
 *
 * For example, returns "Tim Berners-Lee" if given any of
 *	" Tim Berners-Lee <tim@online.cern.ch> "
 *  or	" tim@online.cern.ch ( Tim Berners-Lee ) "
 */
static char *author_name(char *email)
{
    char *p, *e;

    StrAllocCopy(name, email);
    CTRACE((tfp, "Trying to find name in: %s\n", name));

    if ((p = strrchr(name, '(')) && (e = strrchr(name, ')'))) {
	if (e > p) {
	    *e = '\0';		/* Chop off everything after the ')'  */
	    return HTStrip(p + 1);	/* Remove leading and trailing spaces */
	}
    }

    if ((p = strrchr(name, '<')) && (e = strrchr(name, '>'))) {
	if (e++ > p) {
	    while ((*p++ = *e++) != 0)	/* Remove <...> */
		;
	    return HTStrip(name);	/* Remove leading and trailing spaces */
	}
    }

    return HTStrip(name);	/* Default to the whole thing */
}

/*	Find Author's mail address
 *	--------------------------
 *
 * On exit,
 *	Returns allocated string which cannot be freed by the
 *	calling function, and is reallocated on subsequent calls
 *	to this function.
 *
 * For example, returns "montulli@spaced.out.galaxy.net" if given any of
 *	" Lou Montulli <montulli@spaced.out.galaxy.net> "
 *  or	" montulli@spaced.out.galaxy.net ( Lou "The Stud" Montulli ) "
 */
static char *author_address(char *email)
{
    char *p, *at, *e;

    StrAllocCopy(address, email);
    CTRACE((tfp, "Trying to find address in: %s\n", address));

    if ((p = strrchr(address, '<'))) {
	if ((e = strrchr(p, '>')) && (at = strrchr(p, '@'))) {
	    if (at < e) {
		*e = '\0';	/* Remove > */
		return HTStrip(p + 1);	/* Remove leading and trailing spaces */
	    }
	}
    }

    if ((p = strrchr(address, '(')) &&
	(e = strrchr(address, ')')) && (at = StrChr(address, '@'))) {
	if (e > p && at < e) {
	    *p = '\0';		/* Chop off everything after the ')'  */
	    return HTStrip(address);	/* Remove leading and trailing spaces */
	}
    }

    if ((at = strrchr(address, '@')) && at > address) {
	p = (at - 1);
	e = (at + 1);
	while (p > address && !isspace(UCH(*p)))
	    p--;
	while (*e && !isspace(UCH(*e)))
	    e++;
	*e = 0;
	return HTStrip(p);
    }

    /*
     * Default to the first word.
     */
    p = address;
    while (isspace(UCH(*p)))
	p++;			/* find first non-space */
    e = p;
    while (!isspace(UCH(*e)) && *e != '\0')
	e++;			/* find next space or end */
    *e = '\0';			/* terminate space */

    return (p);
}

/*	Start anchor element
 *	--------------------
 */
static void start_anchor(const char *href)
{
    BOOL present[HTML_A_ATTRIBUTES];
    const char *value[HTML_A_ATTRIBUTES];
    int i;

    for (i = 0; i < HTML_A_ATTRIBUTES; i++)
	present[i] = (BOOL) (i == HTML_A_HREF);
    value[HTML_A_HREF] = href;
    (*targetClass.start_element) (target, HTML_A, present, value, -1, 0);
}

/*	Start link element
 *	------------------
 */
static void start_link(const char *href, const char *rev)
{
    BOOL present[HTML_LINK_ATTRIBUTES];
    const char *value[HTML_LINK_ATTRIBUTES];
    int i;

    for (i = 0; i < HTML_LINK_ATTRIBUTES; i++)
	present[i] = (BOOL) (i == HTML_LINK_HREF || i == HTML_LINK_REV);
    value[HTML_LINK_HREF] = href;
    value[HTML_LINK_REV] = rev;
    (*targetClass.start_element) (target, HTML_LINK, present, value, -1, 0);
}

/*	Start list element
 *	------------------
 */
static void start_list(int seqnum)
{
    BOOL present[HTML_OL_ATTRIBUTES];
    const char *value[HTML_OL_ATTRIBUTES];
    char SeqNum[20];
    int i;

    for (i = 0; i < HTML_OL_ATTRIBUTES; i++)
	present[i] = (BOOL) (i == HTML_OL_SEQNUM || i == HTML_OL_START);
    sprintf(SeqNum, "%d", seqnum);
    value[HTML_OL_SEQNUM] = SeqNum;
    value[HTML_OL_START] = SeqNum;
    (*targetClass.start_element) (target, HTML_OL, present, value, -1, 0);
}

/*	Paste in an Anchor
 *	------------------
 *
 *
 * On entry,
 *	HT	has a selection of zero length at the end.
 *	text	points to the text to be put into the file, 0 terminated.
 *	addr	points to the hypertext reference address,
 *		terminated by white space, comma, NULL or '>'
 */
static void write_anchor(const char *text, const char *addr)
{
    char href[LINE_LENGTH + 1];
    const char *p;
    char *q;

    for (p = addr; *p && (*p != '>') && !WHITE(*p) && (*p != ','); p++) {
	;
    }
    if (strlen(NewsHREF) + (size_t) (p - addr) + 1 < sizeof(href)) {
	q = href;
	strcpy(q, NewsHREF);
	/* Make complete hypertext reference */
	StrNCat(q, addr, (size_t) (p - addr));
    } else {
	q = NULL;
	HTSprintf0(&q, "%s%.*s", NewsHREF, (int) (p - addr), addr);
    }

    start_anchor(q);
    PUTS(text);
    END(HTML_A);

    if (q != href)
	FREE(q);
}

/*	Write list of anchors
 *	---------------------
 *
 *	We take a pointer to a list of objects, and write out each,
 *	generating an anchor for each.
 *
 * On entry,
 *	HT	has a selection of zero length at the end.
 *	text	points to a comma or space separated list of addresses.
 * On exit,
 *	*text	is NOT any more chopped up into substrings.
 */
static void write_anchors(char *text)
{
    char *start = text;
    char *end;
    char c;

    for (;;) {
	for (; *start && (WHITE(*start)); start++) ;	/* Find start */
	if (!*start)
	    return;		/* (Done) */
	for (end = start;
	     *end && (*end != ' ') && (*end != ','); end++) ;	/* Find end */
	if (*end)
	    end++;		/* Include comma or space but not NULL */
	c = *end;
	*end = '\0';
	if (*start == '<')
	    write_anchor(start, start + 1);
	else
	    write_anchor(start, start);
	START(HTML_BR);
	*end = c;
	start = end;		/* Point to next one */
    }
}

/*	Abort the connection					abort_socket
 *	--------------------
 */
static void abort_socket(void)
{
    CTRACE((tfp, "HTNews: EOF on read, closing socket %d\n", s));
    NEWS_NETCLOSE(s);		/* End of file, close socket */
    if (rawtext) {
	RAW_PUTS("Network Error: connection lost\n");
    } else {
	PUTS("Network Error: connection lost");
	PUTC('\n');
    }
    s = -1;			/* End of file on response */
}

/*
 *  Determine if a line is a valid header line.			valid_header
 *  -------------------------------------------
 */
static BOOLEAN valid_header(char *line)
{
    char *colon, *space;

    /*
     * Blank or tab in first position implies this is a continuation header.
     */
    if (line[0] == ' ' || line[0] == '\t')
	return (TRUE);

    /*
     * Just check for initial letter, colon, and space to make sure we discard
     * only invalid headers.
     */
    colon = StrChr(line, ':');
    space = StrChr(line, ' ');
    if (isalpha(UCH(line[0])) && colon && space == colon + 1)
	return (TRUE);

    /*
     * Anything else is a bad header -- it should be ignored.
     */
    return (FALSE);
}

/*	post in an Article					post_article
 *	------------------
 *			(added by FM, modeled on Lynx's previous mini inews)
 *
 *	Note the termination condition of a single dot on a line by itself.
 *
 *  On entry,
 *	s		Global socket number is OK
 *	postfile	file with header and article to post.
 */
static void post_article(char *postfile)
{
    char line[512];
    char buf[512];
    char crlf[3];
    char *cp;
    int status;
    FILE *fd;
    int in_header = 1, seen_header = 0, seen_fromline = 0;
    int blen = 0, llen = 0;

    /*
     * Open the temporary file with the nntp headers and message body.  - FM
     */
    if ((fd = fopen(NonNull(postfile), TXT_R)) == NULL) {
	HTAlert(FAILED_CANNOT_OPEN_POST);
	return;
    }

    /*
     * Read the temporary file and post in maximum 512 byte chunks.  - FM
     */
    buf[0] = '\0';
    sprintf(crlf, "%c%c", CR, LF);
    while (fgets(line, (int) sizeof(line) - 2, fd) != NULL) {
	if ((cp = StrChr(line, '\n')) != NULL)
	    *cp = '\0';
	if (line[0] == '.') {
	    /*
	     * A single '.' means end of transmission for nntp.  Lead dots on
	     * lines normally are trimmed and the EOF is not registered if the
	     * dot was not followed by CRLF.  We prepend an extra dot for any
	     * line beginning with one, to retain the one intended, as well as
	     * avoid a false EOF signal.  We know we have room for it in the
	     * buffer, because we normally send when it would exceed 510.  - FM
	     */
	    strcat(buf, ".");
	    blen++;
	}
	llen = (int) strlen(line);
	if (in_header && !strncasecomp(line, "From:", 5)) {
	    seen_header = 1;
	    seen_fromline = 1;
	}
	if (in_header && line[0] == '\0') {
	    if (seen_header) {
		in_header = 0;
		if (!seen_fromline) {
		    if (blen >= (int) sizeof(buf) - 35) {
			IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
			buf[blen = 0] = 0;
		    }
		    strcat(buf, "From: anonymous@nowhere.you.know");
		    strcat(buf, crlf);
		    blen += 34;
		}
	    } else {
		continue;
	    }
	} else if (in_header) {
	    if (valid_header(line)) {
		seen_header = 1;
	    } else {
		continue;
	    }
	}
	strcat(line, crlf);
	llen += 2;
	if ((blen + llen) >= (int) sizeof(buf) - 1) {
	    IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
	    buf[blen = 0] = 0;
	}
	strcat(buf, line);
	blen += llen;
    }
    fclose(fd);
    HTSYS_remove(postfile);

    /*
     * Send the nntp EOF and get the server's response.  - FM
     */
    if (blen >= (int) sizeof(buf) - 4) {
	IGNORE_RC(NEWS_NETWRITE(s, buf, blen));
	buf[blen = 0] = 0;
    }
    strcat(buf, ".");
    strcat(buf, crlf);
    blen += 3;
    IGNORE_RC(NEWS_NETWRITE(s, buf, blen));

    status = response(NULL);
    if (status == 240) {
	/*
	 * Successful post.  - FM
	 */
	HTProgress(response_text);
    } else {
	/*
	 * Shucks, something went wrong.  - FM
	 */
	HTAlert(response_text);
    }
}

#ifdef NEWS_DEBUG
/* for DEBUG 1997/11/07 (Fri) 17:20:16 */
void debug_print(unsigned char *p)
{
    while (*p) {
	if (*p == '\0')
	    break;
	if (*p == 0x1b)
	    printf("[ESC]");
	else if (*p == '\n')
	    printf("[NL]");
	else if (*p < ' ' || *p >= 0x80)
	    printf("(%02x)", *p);
	else
	    putchar(*p);
	p++;
    }
    printf("]\n");
}
#endif

static char *decode_mime(char **str)
{
    static char empty[] = "";

#ifdef SH_EX
    if (HTCJK != JAPANESE)
	return *str;
#endif
    HTmmdecode(str, *str);
    return HTrjis(str, *str) ? *str : empty;
}

/*	Read in an Article					read_article
 *	------------------
 *
 *	Note the termination condition of a single dot on a line by itself.
 *	RFC 977 specifies that the line "folding" of RFC850 is not used, so we
 *	do not handle it here.
 *
 * On entry,
 *	s	Global socket number is OK
 *	HT	Global hypertext object is ready for appending text
 */
static int read_article(HTParentAnchor *thisanchor)
{
    char line[LINE_LENGTH + 1];
    char *full_line = NULL;
    char *subject = NULL;	/* Subject string           */
    char *from = NULL;		/* From string              */
    char *replyto = NULL;	/* Reply-to string          */
    char *date = NULL;		/* Date string              */
    char *organization = NULL;	/* Organization string      */
    char *references = NULL;	/* Hrefs for other articles */
    char *newsgroups = NULL;	/* Newsgroups list          */
    char *followupto = NULL;	/* Followup list            */
    char *href = NULL;
    char *p = line;
    char *cp;
    const char *ccp;
    BOOL done = NO;

    /*
     * Read in the HEADer of the article.
     *
     * The header fields are either ignored, or formatted and put into the
     * text.
     */
    if (!diagnostic && !rawtext) {
	while (!done) {
	    int ich = NEXT_CHAR;

	    *p++ = (char) ich;
	    if (ich == EOF) {
		if (interrupted_in_htgetcharacter) {
		    interrupted_in_htgetcharacter = 0;
		    CTRACE((tfp,
			    "HTNews: Interrupted on read, closing socket %d\n",
			    s));
		    NEWS_NETCLOSE(s);
		    s = -1;
		    return (HT_INTERRUPTED);
		}
		abort_socket();	/* End of file, close socket */
		return (HT_LOADED);	/* End of file on response */
	    }
	    if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
		*--p = '\0';	/* Terminate the string */
		CTRACE((tfp, "H %s\n", line));

		if (line[0] == '\t' || line[0] == ' ') {
		    int i = 0;

		    while (line[i]) {
			if (line[i] == '\t')
			    line[i] = ' ';
			i++;
		    }
		    if (full_line == NULL) {
			StrAllocCopy(full_line, line);
		    } else {
			StrAllocCat(full_line, line);
		    }
		} else {
		    StrAllocCopy(full_line, line);
		}

		if (full_line[0] == '.') {
		    /*
		     * End of article?
		     */
		    if (UCH(full_line[1]) < ' ') {
			done = YES;
			break;
		    }
		} else if (UCH(full_line[0]) < ' ') {
		    break;	/* End of Header? */

		} else if (match(full_line, "SUBJECT:")) {
		    StrAllocCopy(subject, HTStrip(StrChr(full_line, ':') + 1));
		    decode_mime(&subject);
		} else if (match(full_line, "DATE:")) {
		    StrAllocCopy(date, HTStrip(StrChr(full_line, ':') + 1));

		} else if (match(full_line, "ORGANIZATION:")) {
		    StrAllocCopy(organization,
				 HTStrip(StrChr(full_line, ':') + 1));
		    decode_mime(&organization);

		} else if (match(full_line, "FROM:")) {
		    StrAllocCopy(from, HTStrip(StrChr(full_line, ':') + 1));
		    decode_mime(&from);

		} else if (match(full_line, "REPLY-TO:")) {
		    StrAllocCopy(replyto, HTStrip(StrChr(full_line, ':') + 1));
		    decode_mime(&replyto);

		} else if (match(full_line, "NEWSGROUPS:")) {
		    StrAllocCopy(newsgroups, HTStrip(StrChr(full_line, ':') + 1));

		} else if (match(full_line, "REFERENCES:")) {
		    StrAllocCopy(references, HTStrip(StrChr(full_line, ':') + 1));

		} else if (match(full_line, "FOLLOWUP-TO:")) {
		    StrAllocCopy(followupto, HTStrip(StrChr(full_line, ':') + 1));

		} else if (match(full_line, "MESSAGE-ID:")) {
		    char *msgid = HTStrip(full_line + 11);

		    if (msgid[0] == '<' && msgid[strlen(msgid) - 1] == '>') {
			msgid[strlen(msgid) - 1] = '\0';	/* Chop > */
			msgid++;	/* Chop < */
			HTAnchor_setMessageID(thisanchor, msgid);
		    }

		}		/* end if match */
		p = line;	/* Restart at beginning */
	    }			/* if end of line */
	}			/* Loop over characters */
	FREE(full_line);

	START(HTML_HEAD);
	PUTC('\n');
	START(HTML_TITLE);
	if (subject && *subject != '\0')
	    PUTS(subject);
	else
	    PUTS("No Subject");
	END(HTML_TITLE);
	PUTC('\n');
	/*
	 * Put in the owner as a link rel.
	 */
	if (from || replyto) {
	    char *temp = NULL;

	    StrAllocCopy(temp, author_address(replyto ? replyto : from));
	    StrAllocCopy(href, STR_MAILTO_URL);
	    if (StrChr(temp, '%') || StrChr(temp, '?')) {
		cp = HTEscape(temp, URL_XPALPHAS);
		StrAllocCat(href, cp);
		FREE(cp);
	    } else {
		StrAllocCat(href, temp);
	    }
	    start_link(href, "made");
	    PUTC('\n');
	    FREE(temp);
	}
	END(HTML_HEAD);
	PUTC('\n');

	START(HTML_H1);
	if (subject && *subject != '\0')
	    PUTS(subject);
	else
	    PUTS("No Subject");
	END(HTML_H1);
	PUTC('\n');

	if (subject)
	    FREE(subject);

	START(HTML_DLC);
	PUTC('\n');

	if (from || replyto) {
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("From:");
	    END(HTML_B);
	    PUTC(' ');
	    if (from)
		PUTS(from);
	    else
		PUTS(replyto);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');

	    if (!replyto)
		StrAllocCopy(replyto, from);
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Reply to:");
	    END(HTML_B);
	    PUTC(' ');
	    start_anchor(href);
	    if (*replyto != '<')
		PUTS(author_name(replyto));
	    else
		PUTS(author_address(replyto));
	    END(HTML_A);
	    START(HTML_BR);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');

	    FREE(from);
	    FREE(replyto);
	}

	if (date) {
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Date:");
	    END(HTML_B);
	    PUTC(' ');
	    PUTS(date);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');
	    FREE(date);
	}

	if (organization) {
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Organization:");
	    END(HTML_B);
	    PUTC(' ');
	    PUTS(organization);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');
	    FREE(organization);
	}

	/* sanitize some headers - kw */
	if (newsgroups &&
	    ((cp = StrChr(newsgroups, '/')) ||
	     (cp = StrChr(newsgroups, '(')))) {
	    *cp = '\0';
	}
	if (newsgroups && !*newsgroups) {
	    FREE(newsgroups);
	}
	if (followupto &&
	    ((cp = StrChr(followupto, '/')) ||
	     (cp = StrChr(followupto, '(')))) {
	    *cp = '\0';
	}
	if (followupto && !*followupto) {
	    FREE(followupto);
	}

	if (newsgroups && HTCanPost) {
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Newsgroups:");
	    END(HTML_B);
	    PUTC('\n');
	    MAYBE_END(HTML_DT);
	    START(HTML_DD);
	    write_anchors(newsgroups);
	    MAYBE_END(HTML_DD);
	    PUTC('\n');
	}

	if (followupto && !strcasecomp(followupto, "poster")) {
	    /*
	     * "Followup-To:  poster" has special meaning.  Don't use it to
	     * construct a newsreply link.  -kw
	     */
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Followup to:");
	    END(HTML_B);
	    PUTC(' ');
	    if (href) {
		start_anchor(href);
		PUTS("poster");
		END(HTML_A);
	    } else {
		PUTS("poster");
	    }
	    MAYBE_END(HTML_DT);
	    PUTC('\n');
	    FREE(followupto);
	}

	if (newsgroups && HTCanPost) {
	    /*
	     * We have permission to POST to this host, so add a link for
	     * posting followups for this article.  - FM
	     */
	    if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
		StrAllocCopy(href, "snewsreply://");
	    else
		StrAllocCopy(href, "newsreply://");
	    StrAllocCat(href, NewsHost);
	    StrAllocCat(href, "/");
	    StrAllocCat(href, (followupto ? followupto : newsgroups));
	    if (*href == 'n' &&
		(ccp = HTAnchor_messageID(thisanchor)) && *ccp) {
		StrAllocCat(href, ";ref=");
		if (StrChr(ccp, '<') || StrChr(ccp, '&') ||
		    StrChr(ccp, ' ') || StrChr(ccp, ':') ||
		    StrChr(ccp, '/') || StrChr(ccp, '%') ||
		    StrChr(ccp, ';')) {
		    char *cp1 = HTEscape(ccp, URL_XPALPHAS);

		    StrAllocCat(href, cp1);
		    FREE(cp1);
		} else {
		    StrAllocCat(href, ccp);
		}
	    }

	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("Followup to:");
	    END(HTML_B);
	    PUTC(' ');
	    start_anchor(href);
	    if (StrChr((followupto ? followupto : newsgroups), ',')) {
		PUTS("newsgroups");
	    } else {
		PUTS("newsgroup");
	    }
	    END(HTML_A);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');
	}
	FREE(newsgroups);
	FREE(followupto);

	if (references) {
	    START(HTML_DT);
	    START(HTML_B);
	    PUTS("References:");
	    END(HTML_B);
	    MAYBE_END(HTML_DT);
	    PUTC('\n');
	    START(HTML_DD);
	    write_anchors(references);
	    MAYBE_END(HTML_DD);
	    PUTC('\n');
	    FREE(references);
	}

	END(HTML_DLC);
	PUTC('\n');
	FREE(href);
    }

    if (rawtext) {
	/*
	 * No tags, and never do a PUTC.  - kw
	 */
	;
    } else if (diagnostic) {
	/*
	 * Read in the HEAD and BODY of the Article as XMP formatted text.  -
	 * FM
	 */
	START(HTML_XMP);
	PUTC('\n');
    } else {
	/*
	 * Read in the BODY of the Article as PRE formatted text.  - FM
	 */
	START(HTML_PRE);
	PUTC('\n');
    }

    p = line;
    while (!done) {
	int ich = NEXT_CHAR;

	*p++ = (char) ich;
	if (ich == EOF) {
	    if (interrupted_in_htgetcharacter) {
		interrupted_in_htgetcharacter = 0;
		CTRACE((tfp,
			"HTNews: Interrupted on read, closing socket %d\n",
			s));
		NEWS_NETCLOSE(s);
		s = -1;
		return (HT_INTERRUPTED);
	    }
	    abort_socket();	/* End of file, close socket */
	    return (HT_LOADED);	/* End of file on response */
	}
	if (((char) ich == LF) || (p == &line[LINE_LENGTH])) {
	    *p = '\0';		/* Terminate the string */
	    CTRACE((tfp, "B %s", line));
#ifdef NEWS_DEBUG		/* 1997/11/09 (Sun) 15:56:11 */
	    debug_print(line);	/* @@@ */
#endif
	    if (line[0] == '.') {
		/*
		 * End of article?
		 */
		if (UCH(line[1]) < ' ') {
		    break;
		} else {	/* Line starts with dot */
		    if (rawtext) {
			RAW_PUTS(&line[1]);
		    } else {
			PUTS(&line[1]);		/* Ignore first dot */
		    }
		}
	    } else {
		if (rawtext) {
		    RAW_PUTS(line);
		} else if (diagnostic || !scan_for_buried_news_references) {
		    /*
		     * All lines are passed as unmodified source.  - FM
		     */
		    PUTS(line);
		} else {
		    /*
		     * Normal lines are scanned for buried references to other
		     * articles.  Unfortunately, it could pick up mail
		     * addresses as well!  It also can corrupt uuencoded
		     * messages!  So we don't do this when fetching articles as
		     * WWW_SOURCE or when downloading (diagnostic is TRUE) or
		     * if the client has set scan_for_buried_news_references to
		     * FALSE.  Otherwise, we convert all "<...@...>" strings
		     * preceded by "rticle " to "news:...@..." links, and any
		     * strings that look like URLs to links.  - FM
		     */
		    char *l = line;
		    char *p2;

		    while ((p2 = strstr(l, "rticle <")) != NULL) {
			char *q = strrchr(p2, '>');
			char *at = strrchr(p2, '@');

			if (q && at && at < q) {
			    char c = q[1];

			    q[1] = 0;	/* chop up */
			    p2 += 7;
			    *p2 = 0;
			    while (*l) {
				if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
				    StrNCmp(l, "snews://", 8) &&
				    StrNCmp(l, "nntp://", 7) &&
				    StrNCmp(l, "snewspost:", 10) &&
				    StrNCmp(l, "snewsreply:", 11) &&
				    StrNCmp(l, "newspost:", 9) &&
				    StrNCmp(l, "newsreply:", 10) &&
				    StrNCmp(l, "ftp://", 6) &&
				    StrNCmp(l, "file:/", 6) &&
				    StrNCmp(l, "finger://", 9) &&
				    StrNCmp(l, "http://", 7) &&
				    StrNCmp(l, "https://", 8) &&
				    StrNCmp(l, "wais://", 7) &&
				    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
				    StrNCmp(l, "cso://", 6) &&
				    StrNCmp(l, "gopher://", 9)) {
				    PUTC(*l++);
				} else {
				    StrAllocCopy(href, l);
				    start_anchor(strtok(href, " \r\n\t,>)\""));
				    while (*l && !StrChr(" \r\n\t,>)\"", *l))
					PUTC(*l++);
				    END(HTML_A);
				    FREE(href);
				}
			    }
			    *p2 = '<';	/* again */
			    *q = 0;
			    start_anchor(p2 + 1);
			    *q = '>';	/* again */
			    PUTS(p2);
			    END(HTML_A);
			    q[1] = c;	/* again */
			    l = q + 1;
			} else {
			    break;	/* line has unmatched <> */
			}
		    }
		    while (*l) {	/* Last bit of the line */
			if (StrNCmp(l, STR_NEWS_URL, LEN_NEWS_URL) &&
			    StrNCmp(l, "snews://", 8) &&
			    StrNCmp(l, "nntp://", 7) &&
			    StrNCmp(l, "snewspost:", 10) &&
			    StrNCmp(l, "snewsreply:", 11) &&
			    StrNCmp(l, "newspost:", 9) &&
			    StrNCmp(l, "newsreply:", 10) &&
			    StrNCmp(l, "ftp://", 6) &&
			    StrNCmp(l, "file:/", 6) &&
			    StrNCmp(l, "finger://", 9) &&
			    StrNCmp(l, "http://", 7) &&
			    StrNCmp(l, "https://", 8) &&
			    StrNCmp(l, "wais://", 7) &&
			    StrNCmp(l, STR_MAILTO_URL, LEN_MAILTO_URL) &&
			    StrNCmp(l, "cso://", 6) &&
			    StrNCmp(l, "gopher://", 9))
			    PUTC(*l++);
			else {
			    StrAllocCopy(href, l);
			    start_anchor(strtok(href, " \r\n\t,>)\""));
			    while (*l && !StrChr(" \r\n\t,>)\"", *l))
				PUTC(*l++);
			    END(HTML_A);
			    FREE(href);
			}
		    }
		}		/* if diagnostic or not scan_for_buried_news_references */
	    }			/* if not dot */
	    p = line;		/* Restart at beginning */
	}			/* if end of line */
    }				/* Loop over characters */

    if (rawtext)
	return (HT_LOADED);

    if (diagnostic)
	END(HTML_XMP);
    else
	END(HTML_PRE);
    PUTC('\n');
    return (HT_LOADED);
}

/*	Read in a List of Newsgroups
 *	----------------------------
 *
 *  Note the termination condition of a single dot on a line by itself.
 *  RFC 977 specifies that the line "folding" of RFC850 is not used,
 *  so we do not handle it here.
 */
static int read_list(char *arg)
{
    char line[LINE_LENGTH + 1];
    char *p;
    BOOL done = NO;
    BOOL head = NO;
    BOOL tail = NO;
    BOOL skip_this_line = NO;
    BOOL skip_rest_of_line = NO;
    int listing = 0;
    char *pattern = NULL;
    int len = 0;

    /*
     * Support head or tail matches for groups to list.  - FM
     */
    if (arg && strlen(arg) > 1) {
	if (*arg == '*') {
	    tail = YES;
	    StrAllocCopy(pattern, (arg + 1));
	} else if (arg[strlen(arg) - 1] == '*') {
	    head = YES;
	    StrAllocCopy(pattern, arg);
	    pattern[strlen(pattern) - 1] = '\0';
	}
	if (tail || head) {
	    len = (int) strlen(pattern);
	}

    }

    /*
     * Read the server's reply.
     *
     * The lines are scanned for newsgroup names and descriptions.
     */
    START(HTML_HEAD);
    PUTC('\n');
    START(HTML_TITLE);
    PUTS("Newsgroups");
    END(HTML_TITLE);
    PUTC('\n');
    END(HTML_HEAD);
    PUTC('\n');
    START(HTML_H1);
    PUTS("Newsgroups");
    END(HTML_H1);
    PUTC('\n');
    p = line;
    START(HTML_DLC);
    PUTC('\n');
    while (!done) {
	int ich = NEXT_CHAR;
	char ch = (char) ich;

	if (ich == EOF) {
	    if (interrupted_in_htgetcharacter) {
		interrupted_in_htgetcharacter = 0;
		CTRACE((tfp,
			"HTNews: Interrupted on read, closing socket %d\n",
			s));
		NEWS_NETCLOSE(s);
		s = -1;
		return (HT_INTERRUPTED);
	    }
	    abort_socket();	/* End of file, close socket */
	    FREE(pattern);
	    return (HT_LOADED);	/* End of file on response */
	} else if (skip_this_line) {
	    if (ch == LF) {
		skip_this_line = skip_rest_of_line = NO;
		p = line;
	    }
	    continue;
	} else if (skip_rest_of_line) {
	    if (ch != LF) {
		continue;
	    }
	} else if (p == &line[LINE_LENGTH]) {
	    CTRACE((tfp, "b %.*s%c[...]\n", (LINE_LENGTH), line, ch));
	    *p = '\0';
	    if (ch == LF) {
		;		/* Will be dealt with below */
	    } else if (WHITE(ch)) {
		ch = LF;	/* May treat as line without description */
		skip_this_line = YES;	/* ...and ignore until LF */
	    } else if (StrChr(line, ' ') == NULL &&
		       StrChr(line, '\t') == NULL) {
		/* No separator found */
		CTRACE((tfp, "HTNews..... group name too long, discarding.\n"));
		skip_this_line = YES;	/* ignore whole line */
		continue;
	    } else {
		skip_rest_of_line = YES;	/* skip until ch == LF found */
	    }
	} else {
	    *p++ = ch;
	}
	if (ch == LF) {
	    skip_rest_of_line = NO;	/* done, reset flag */
	    *p = '\0';		/* Terminate the string */
	    CTRACE((tfp, "B %s", line));
	    if (line[0] == '.') {
		/*
		 * End of article?
		 */
		if (UCH(line[1]) < ' ') {
		    break;
		} else {	/* Line starts with dot */
		    START(HTML_DT);
		    PUTS(&line[1]);
		    MAYBE_END(HTML_DT);
		}
	    } else if (line[0] == '#') {	/* Comment? */
		p = line;	/* Restart at beginning */
		continue;
	    } else {
		/*
		 * Normal lines are scanned for references to newsgroups.
		 */
		int i = 0;

		/* find whitespace if it exits */
		for (; line[i] != '\0' && !WHITE(line[i]); i++) ;	/* null body */

		if (line[i] != '\0') {
		    line[i] = '\0';
		    if ((head && strncasecomp(line, pattern, len)) ||
			(tail && (i < len ||
				  strcasecomp((line + (i - len)), pattern)))) {
			p = line;	/* Restart at beginning */
			continue;
		    }
		    START(HTML_DT);
		    write_anchor(line, line);
		    listing++;
		    MAYBE_END(HTML_DT);
		    PUTC('\n');
		    START(HTML_DD);
		    PUTS(&line[i + 1]);		/* put description */
		    MAYBE_END(HTML_DD);
		} else {
		    if ((head && strncasecomp(line, pattern, len)) ||
			(tail && (i < len ||
				  strcasecomp((line + (i - len)), pattern)))) {
			p = line;	/* Restart at beginning */
			continue;
		    }
		    START(HTML_DT);
		    write_anchor(line, line);
		    MAYBE_END(HTML_DT);
		    listing++;
		}
	    }			/* if not dot */
	    p = line;		/* Restart at beginning */
	}			/* if end of line */
    }				/* Loop over characters */
    if (!listing) {
	char *msg = NULL;

	START(HTML_DT);
	HTSprintf0(&msg, gettext("No matches for: %s"), arg);
	PUTS(msg);
	MAYBE_END(HTML_DT);
	FREE(msg);
    }
    END(HTML_DLC);
    PUTC('\n');
    FREE(pattern);
    return (HT_LOADED);
}

/*	Read in a Newsgroup
 *	-------------------
 *
 *  Unfortunately, we have to ask for each article one by one if we
 *  want more than one field.
 *
 */
static int read_group(const char *groupName,
		      int first_required,
		      int last_required)
{
    char line[LINE_LENGTH + 1];
    char *author = NULL;
    char *subject = NULL;
    char *date = NULL;
    int i;
    char *p;
    BOOL done;

    char buffer[LINE_LENGTH + 1];
    char *temp = NULL;
    char *reference = NULL;	/* Href for article */
    int art;			/* Article number WITHIN GROUP */
    int status, count, first, last;	/* Response fields */

    START(HTML_HEAD);
    PUTC('\n');
    START(HTML_TITLE);
    PUTS("Newsgroup ");
    PUTS(groupName);
    END(HTML_TITLE);
    PUTC('\n');
    END(HTML_HEAD);
    PUTC('\n');

    sscanf(response_text, " %d %d %d %d", &status, &count, &first, &last);
    CTRACE((tfp, "Newsgroup status=%d, count=%d, (%d-%d) required:(%d-%d)\n",
	    status, count, first, last, first_required, last_required));
    if (last == 0) {
	PUTS(gettext("\nNo articles in this group.\n"));
	goto add_post;
    }
#define FAST_THRESHOLD 100	/* Above this, read IDs fast */
#define CHOP_THRESHOLD 50	/* Above this, chop off the rest */

    if (first_required < first)
	first_required = first;	/* clip */
    if ((last_required == 0) || (last_required > last))
	last_required = last;

    if (last_required < first_required) {
	PUTS(gettext("\nNo articles in this range.\n"));
	goto add_post;
    }

    if (last_required - first_required + 1 > HTNewsMaxChunk) {	/* Trim this block */
	first_required = last_required - HTNewsChunkSize + 1;
    }
    CTRACE((tfp, "    Chunk will be (%d-%d)\n",
	    first_required, last_required));

    /*
     * Set window title.
     */
    HTSprintf0(&temp, gettext("%s,  Articles %d-%d"),
	       groupName, first_required, last_required);
    START(HTML_H1);
    PUTS(temp);
    FREE(temp);
    END(HTML_H1);
    PUTC('\n');

    /*
     * Link to earlier articles.
     */
    if (first_required > first) {
	int before;		/* Start of one before */

	if (first_required - HTNewsMaxChunk <= first)
	    before = first;
	else
	    before = first_required - HTNewsChunkSize;
	HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
		   before, first_required - 1);
	CTRACE((tfp, "    Block before is %s\n", dbuf));
	PUTC('(');
	start_anchor(dbuf);
	PUTS(gettext("Earlier articles"));
	END(HTML_A);
	PUTS("...)\n");
	START(HTML_P);
	PUTC('\n');
    }

    done = NO;

/*#define USE_XHDR*/
#ifdef USE_XHDR
    if (count > FAST_THRESHOLD) {
	HTSprintf0(&temp,
		   gettext("\nThere are about %d articles currently available in %s, IDs as follows:\n\n"),
		   count, groupName);
	PUTS(temp);
	FREE(temp);
	sprintf(buffer, "XHDR Message-ID %d-%d%c%c", first, last, CR, LF);
	status = response(buffer);
	if (status == 221) {
	    p = line;
	    while (!done) {
		int ich = NEXT_CHAR;

		*p++ = ich;
		if (ich == EOF) {
		    if (interrupted_in_htgetcharacter) {
			interrupted_in_htgetcharacter = 0;
			CTRACE((tfp,
				"HTNews: Interrupted on read, closing socket %d\n",
				s));
			NEWS_NETCLOSE(s);
			s = -1;
			return (HT_INTERRUPTED);
		    }
		    abort_socket();	/* End of file, close socket */
		    return (HT_LOADED);		/* End of file on response */
		}
		if (((char) ich == '\n') || (p == &line[LINE_LENGTH])) {
		    *p = '\0';	/* Terminate the string */
		    CTRACE((tfp, "X %s", line));
		    if (line[0] == '.') {
			/*
			 * End of article?
			 */
			if (UCH(line[1]) < ' ') {
			    done = YES;
			    break;
			} else {	/* Line starts with dot */
			    /* Ignore strange line */
			}
		    } else {
			/*
			 * Normal lines are scanned for references to articles.
			 */
			char *space = StrChr(line, ' ');

			if (space++)
			    write_anchor(space, space);
		    }		/* if not dot */
		    p = line;	/* Restart at beginning */
		}		/* if end of line */
	    }			/* Loop over characters */

	    /* leaving loop with "done" set */
	}			/* Good status */
    }
#endif /* USE_XHDR */

    /*
     * Read newsgroup using individual fields.
     */
    if (!done) {
	START(HTML_B);
	if (first == first_required && last == last_required)
	    PUTS(gettext("All available articles in "));
	else
	    PUTS("Articles in ");
	PUTS(groupName);
	END(HTML_B);
	PUTC('\n');
	if (LYListNewsNumbers)
	    start_list(first_required);
	else
	    START(HTML_UL);
	for (art = first_required; art <= last_required; art++) {
/*#define OVERLAP*/
#ifdef OVERLAP
	    /*
	     * With this code we try to keep the server running flat out by
	     * queuing just one extra command ahead of time.  We assume (1)
	     * that the server won't abort if it gets input during output, and
	     * (2) that TCP buffering is enough for the two commands.  Both
	     * these assumptions seem very reasonable.  However, we HAVE had a
	     * hangup with a loaded server.
	     */
	    if (art == first_required) {
		if (art == last_required) {	/* Only one */
		    sprintf(buffer, "HEAD %d%c%c",
			    art, CR, LF);
		    status = response(buffer);
		} else {	/* First of many */
		    sprintf(buffer, "HEAD %d%c%cHEAD %d%c%c",
			    art, CR, LF, art + 1, CR, LF);
		    status = response(buffer);
		}
	    } else if (art == last_required) {	/* Last of many */
		status = response(NULL);
	    } else {		/* Middle of many */
		sprintf(buffer, "HEAD %d%c%c", art + 1, CR, LF);
		status = response(buffer);
	    }
#else /* Not OVERLAP: */
	    sprintf(buffer, "HEAD %d%c%c", art, CR, LF);
	    status = response(buffer);
#endif /* OVERLAP */
	    /*
	     * Check for a good response (221) for the HEAD request, and if so,
	     * parse it.  Otherwise, indicate the error so that the number of
	     * listings corresponds to what's claimed for the range, and if we
	     * are listing numbers via an ordered list, they stay in synchrony
	     * with the article numbers.  - FM
	     */
	    if (status == 221) {	/* Head follows - parse it: */
		p = line;	/* Write pointer */
		done = NO;
		while (!done) {
		    int ich = NEXT_CHAR;

		    *p++ = (char) ich;
		    if (ich == EOF) {
			if (interrupted_in_htgetcharacter) {
			    interrupted_in_htgetcharacter = 0;
			    CTRACE((tfp,
				    "HTNews: Interrupted on read, closing socket %d\n",
				    s));
			    NEWS_NETCLOSE(s);
			    s = -1;
			    return (HT_INTERRUPTED);
			}
			abort_socket();		/* End of file, close socket */
			return (HT_LOADED);	/* End of file on response */
		    }
		    if (((char) ich == LF) ||
			(p == &line[LINE_LENGTH])) {

			*--p = '\0';	/* Terminate  & chop LF */
			p = line;	/* Restart at beginning */
			CTRACE((tfp, "G %s\n", line));
			switch (line[0]) {

			case '.':
			    /*
			     * End of article?
			     */
			    done = (BOOL) (UCH(line[1]) < ' ');
			    break;

			case 'S':
			case 's':
			    if (match(line, "SUBJECT:")) {
				StrAllocCopy(subject, line + 9);
				decode_mime(&subject);
			    }
			    break;

			case 'M':
			case 'm':
			    if (match(line, "MESSAGE-ID:")) {
				char *addr = HTStrip(line + 11) + 1;	/* Chop < */

				addr[strlen(addr) - 1] = '\0';	/* Chop > */
				StrAllocCopy(reference, addr);
			    }
			    break;

			case 'f':
			case 'F':
			    if (match(line, "FROM:")) {
				char *p2;

				StrAllocCopy(author, StrChr(line, ':') + 1);
				decode_mime(&author);
				p2 = author + strlen(author) - 1;
				if (*p2 == LF)
				    *p2 = '\0';		/* Chop off newline */
			    }
			    break;

			case 'd':
			case 'D':
			    if (LYListNewsDates && match(line, "DATE:")) {
				StrAllocCopy(date,
					     HTStrip(StrChr(line, ':') + 1));
			    }
			    break;

			}	/* end switch on first character */
		    }		/* if end of line */
		}		/* Loop over characters */

		PUTC('\n');
		START(HTML_LI);
		p = decode_mime(&subject);
		HTSprintf0(&temp, "\"%s\"", NonNull(p));
		if (reference) {
		    write_anchor(temp, reference);
		    FREE(reference);
		} else {
		    PUTS(temp);
		}
		FREE(temp);

		if (author != NULL) {
		    PUTS(" - ");
		    if (LYListNewsDates)
			START(HTML_I);
		    PUTS(decode_mime(&author));
		    if (LYListNewsDates)
			END(HTML_I);
		    FREE(author);
		}
		if (date) {
		    if (!diagnostic) {
			for (i = 0; date[i]; i++) {
			    if (date[i] == ' ') {
				date[i] = HT_NON_BREAK_SPACE;
			    }
			}
		    }
		    sprintf(buffer, " [%.*s]", (int) (sizeof(buffer) - 4), date);
		    PUTS(buffer);
		    FREE(date);
		}
		MAYBE_END(HTML_LI);
		/*
		 * Indicate progress!  @@@@@@
		 */
	    } else if (status == HT_INTERRUPTED) {
		interrupted_in_htgetcharacter = 0;
		CTRACE((tfp,
			"HTNews: Interrupted on read, closing socket %d\n",
			s));
		NEWS_NETCLOSE(s);
		s = -1;
		return (HT_INTERRUPTED);
	    } else {
		/*
		 * Use the response text on error.  - FM
		 */
		PUTC('\n');
		START(HTML_LI);
		START(HTML_I);
		if (LYListNewsNumbers)
		    LYStrNCpy(buffer, "Status:", sizeof(buffer) - 1);
		else
		    sprintf(buffer, "Status (ARTICLE %d):", art);
		PUTS(buffer);
		END(HTML_I);
		PUTC(' ');
		PUTS(response_text);
		MAYBE_END(HTML_LI);
	    }			/* Handle response to HEAD request */
	}			/* Loop over article */
	FREE(author);
	FREE(subject);
    }				/* If read headers */
    PUTC('\n');
    if (LYListNewsNumbers)
	END(HTML_OL);
    else
	END(HTML_UL);
    PUTC('\n');

    /*
     * Link to later articles.
     */
    if (last_required < last) {
	int after;		/* End of article after */

	after = last_required + HTNewsChunkSize;
	if (after == last)
	    HTSprintf0(&dbuf, "%s%s", NewsHREF, groupName);	/* original group */
	else
	    HTSprintf0(&dbuf, "%s%s/%d-%d", NewsHREF, groupName,
		       last_required + 1, after);
	CTRACE((tfp, "    Block after is %s\n", dbuf));
	PUTC('(');
	start_anchor(dbuf);
	PUTS(gettext("Later articles"));
	END(HTML_A);
	PUTS("...)\n");
    }

  add_post:
    if (HTCanPost) {
	/*
	 * We have permission to POST to this host, so add a link for posting
	 * messages to this newsgroup.  - FM
	 */
	char *href = NULL;

	START(HTML_HR);
	PUTC('\n');
	if (!strncasecomp(NewsHREF, STR_SNEWS_URL, 6))
	    StrAllocCopy(href, "snewspost://");
	else
	    StrAllocCopy(href, "newspost://");
	StrAllocCat(href, NewsHost);
	StrAllocCat(href, "/");
	StrAllocCat(href, groupName);
	start_anchor(href);
	PUTS(gettext("Post to "));
	PUTS(groupName);
	END(HTML_A);
	FREE(href);
    } else {
	START(HTML_HR);
    }
    PUTC('\n');
    return (HT_LOADED);
}

/*	Load by name.						HTLoadNews
 *	=============
 */
static int HTLoadNews(const char *arg,
		      HTParentAnchor *anAnchor,
		      HTFormat format_out,
		      HTStream *stream)
{
    char command[262];		/* The whole command */
    char proxycmd[260];		/* The proxy command */
    char groupName[GROUP_NAME_LENGTH];	/* Just the group name */
    int status;			/* tcp return */
    int retries;		/* A count of how hard we have tried */
    BOOL normal_url;		/* Flag: "news:" or "nntp:" (physical) URL */
    BOOL group_wanted;		/* Flag: group was asked for, not article */
    BOOL list_wanted;		/* Flag: list was asked for, not article */
    BOOL post_wanted;		/* Flag: new post to group was asked for */
    BOOL reply_wanted;		/* Flag: followup post was asked for */
    BOOL spost_wanted;		/* Flag: new SSL post to group was asked for */
    BOOL sreply_wanted;		/* Flag: followup SSL post was asked for */
    BOOL head_wanted = NO;	/* Flag: want HEAD of single article */
    int first, last;		/* First and last articles asked for */
    char *cp = 0;
    char *ListArg = NULL;
    char *ProxyHost = NULL;
    char *ProxyHREF = NULL;
    char *postfile = NULL;

#ifdef USE_SSL
    char SSLprogress[256];
#endif /* USE_SSL */

    diagnostic = (format_out == WWW_SOURCE ||	/* set global flag */
		  format_out == HTAtom_for("www/download") ||
		  format_out == HTAtom_for("www/dump"));
    rawtext = NO;

    CTRACE((tfp, "HTNews: Looking for %s\n", arg));

    if (!initialized)
	initialized = initialize();
    if (!initialized)
	return -1;		/* FAIL */

    FREE(NewsHREF);
    command[0] = '\0';
    command[sizeof(command) - 1] = '\0';
    proxycmd[0] = '\0';
    proxycmd[sizeof(proxycmd) - 1] = '\0';

    {
	const char *p1;

	/*
	 * We will ask for the document, omitting the host name & anchor.
	 *
	 * Syntax of address is
	 * xxx@yyy                 Article
	 * <xxx@yyy>               Same article
	 * xxxxx                   News group (no "@")
	 * group/n1-n2             Articles n1 to n2 in group
	 */
	normal_url = (BOOL) (!StrNCmp(arg, STR_NEWS_URL, LEN_NEWS_URL) ||
			     !StrNCmp(arg, "nntp:", 5));
	spost_wanted = (BOOL) (!normal_url && strstr(arg, "snewspost:") != NULL);
	sreply_wanted = (BOOL) (!(normal_url || spost_wanted) &&
				strstr(arg, "snewsreply:") != NULL);
	post_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted) &&
			      strstr(arg, "newspost:") != NULL);
	reply_wanted = (BOOL) (!(normal_url || spost_wanted || sreply_wanted ||
				 post_wanted) &&
			       strstr(arg, "newsreply:") != NULL);
	group_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
				  post_wanted || reply_wanted) &&
				StrChr(arg, '@') == NULL) &&
			       (StrChr(arg, '*') == NULL));
	list_wanted = (BOOL) ((!(spost_wanted || sreply_wanted ||
				 post_wanted || reply_wanted ||
				 group_wanted) &&
			       StrChr(arg, '@') == NULL) &&
			      (StrChr(arg, '*') != NULL));

#ifndef USE_SSL
	if (!strncasecomp(arg, "snewspost:", 10) ||
	    !strncasecomp(arg, "snewsreply:", 11)) {
	    HTAlert(FAILED_CANNOT_POST_SSL);
	    return HT_NOT_LOADED;
	}
#endif /* !USE_SSL */
	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
	    /*
	     * Make sure we have a non-zero path for the newsgroup(s).  - FM
	     */
	    if ((p1 = strrchr(arg, '/')) != NULL) {
		p1++;
	    } else if ((p1 = strrchr(arg, ':')) != NULL) {
		p1++;
	    }
	    if (!(p1 && *p1)) {
		HTAlert(WWW_ILLEGAL_URL_MESSAGE);
		return (HT_NO_DATA);
	    }
	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, HTNewsHost);
	    } else {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, cp);
	    }
	    FREE(cp);
	    HTSprintf0(&NewsHREF, "%s://%.*s/",
		       (post_wanted ?
			"newspost" :
			(reply_wanted ?
			 "newreply" :
			 (spost_wanted ?
			  "snewspost" : "snewsreply"))),
		       (int) sizeof(command) - 15, NewsHost);

	    /*
	     * If the SSL daemon is being used as a proxy, reset p1 to the
	     * start of the proxied URL rather than to the start of the
	     * newsgroup(s).  - FM
	     */
	    if (spost_wanted && strncasecomp(arg, "snewspost:", 10))
		p1 = strstr(arg, "snewspost:");
	    if (sreply_wanted && strncasecomp(arg, "snewsreply:", 11))
		p1 = strstr(arg, "snewsreply:");

	    /* p1 = HTParse(arg, "", PARSE_PATH | PARSE_PUNCTUATION); */
	    /*
	     * Don't use HTParse because news:  access doesn't follow
	     * traditional rules.  For instance, if the article reference
	     * contains a '#', the rest of it is lost -- JFG 10/7/92, from a
	     * bug report
	     */
	} else if (isNNTP_URL(arg)) {
	    if (((*(arg + 5) == '\0') ||
		 (!strcmp((arg + 5), "/") ||
		  !strcmp((arg + 5), "//") ||
		  !strcmp((arg + 5), "///"))) ||
		((!StrNCmp((arg + 5), "//", 2)) &&
		 (!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
		p1 = "*";
		group_wanted = FALSE;
		list_wanted = TRUE;
	    } else if (*(arg + 5) != '/') {
		p1 = (arg + 5);
	    } else if (*(arg + 5) == '/' && *(arg + 6) != '/') {
		p1 = (arg + 6);
	    } else {
		p1 = (cp + 1);
	    }
	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, HTNewsHost);
	    } else {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, cp);
	    }
	    FREE(cp);
	    SnipIn2(command, "%s//%.*s/", STR_NNTP_URL, 9, NewsHost);
	    StrAllocCopy(NewsHREF, command);
	} else if (!strncasecomp(arg, STR_SNEWS_URL, 6)) {
#ifdef USE_SSL
	    if (((*(arg + 6) == '\0') ||
		 (!strcmp((arg + 6), "/") ||
		  !strcmp((arg + 6), "//") ||
		  !strcmp((arg + 6), "///"))) ||
		((!StrNCmp((arg + 6), "//", 2)) &&
		 (!(cp = StrChr((arg + 8), '/')) || *(cp + 1) == '\0'))) {
		p1 = "*";
		group_wanted = FALSE;
		list_wanted = TRUE;
	    } else if (*(arg + 6) != '/') {
		p1 = (arg + 6);
	    } else if (*(arg + 6) == '/' && *(arg + 7) != '/') {
		p1 = (arg + 7);
	    } else {
		p1 = (cp + 1);
	    }
	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, HTNewsHost);
	    } else {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, cp);
	    }
	    FREE(cp);
	    sprintf(command, "%s//%.250s/", STR_SNEWS_URL, NewsHost);
	    StrAllocCopy(NewsHREF, command);
#else
	    HTAlert(gettext("This client does not contain support for SNEWS URLs."));
	    return HT_NOT_LOADED;
#endif /* USE_SSL */
	} else if (!strncasecomp(arg, "news:/", 6)) {
	    if (((*(arg + 6) == '\0') ||
		 !strcmp((arg + 6), "/") ||
		 !strcmp((arg + 6), "//")) ||
		((*(arg + 6) == '/') &&
		 (!(cp = StrChr((arg + 7), '/')) || *(cp + 1) == '\0'))) {
		p1 = "*";
		group_wanted = FALSE;
		list_wanted = TRUE;
	    } else if (*(arg + 6) != '/') {
		p1 = (arg + 6);
	    } else {
		p1 = (cp + 1);
	    }
	    if (!(cp = HTParse(arg, "", PARSE_HOST)) || *cp == '\0') {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, HTNewsHost);
	    } else {
		if (s >= 0 && NewsHost && strcasecomp(NewsHost, cp)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		StrAllocCopy(NewsHost, cp);
	    }
	    FREE(cp);
	    SnipIn(command, "news://%.*s/", 9, NewsHost);
	    StrAllocCopy(NewsHREF, command);
	} else {
	    p1 = (arg + 5);	/* Skip "news:" prefix */
	    if (*p1 == '\0') {
		p1 = "*";
		group_wanted = FALSE;
		list_wanted = TRUE;
	    }
	    if (s >= 0 && NewsHost && strcasecomp(NewsHost, HTNewsHost)) {
		NEWS_NETCLOSE(s);
		s = -1;
	    }
	    StrAllocCopy(NewsHost, HTNewsHost);
	    StrAllocCopy(NewsHREF, STR_NEWS_URL);
	}

	/*
	 * Set up any proxy for snews URLs that returns NNTP responses for Lynx
	 * to convert to HTML, instead of doing the conversion itself, and for
	 * handling posts or followups.  - TZ & FM
	 */
	if (!strncasecomp(p1, STR_SNEWS_URL, 6) ||
	    !strncasecomp(p1, "snewspost:", 10) ||
	    !strncasecomp(p1, "snewsreply:", 11)) {
	    StrAllocCopy(ProxyHost, NewsHost);
	    if ((cp = HTParse(p1, "", PARSE_HOST)) != NULL && *cp != '\0') {
		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, cp);
		StrAllocCopy(NewsHost, cp);
	    } else {
		SnipIn2(command, "%s//%.*s", STR_SNEWS_URL, 10, NewsHost);
	    }
	    command[sizeof(command) - 2] = '\0';
	    FREE(cp);
	    sprintf(proxycmd, "GET %.*s%c%c%c%c",
		    (int) sizeof(proxycmd) - 9, command,
		    CR, LF, CR, LF);
	    CTRACE((tfp, "HTNews: Proxy command is '%.*s'\n",
		    (int) (strlen(proxycmd) - 4), proxycmd));
	    strcat(command, "/");
	    StrAllocCopy(ProxyHREF, NewsHREF);
	    StrAllocCopy(NewsHREF, command);
	    if (spost_wanted || sreply_wanted) {
		/*
		 * Reset p1 so that it points to the newsgroup(s).
		 */
		if ((p1 = strrchr(arg, '/')) != NULL) {
		    p1++;
		} else {
		    p1 = (strrchr(arg, ':') + 1);
		}
	    } else {
		/*
		 * Reset p1 so that it points to the newsgroup (or a wildcard),
		 * or the article.
		 */
		if (!(cp = strrchr((p1 + 6), '/')) || *(cp + 1) == '\0') {
		    p1 = "*";
		    group_wanted = FALSE;
		    list_wanted = TRUE;
		} else {
		    p1 = (cp + 1);
		}
	    }
	}

	/*
	 * Set up command for a post, listing, or article request.  - FM
	 */
	if (post_wanted || reply_wanted || spost_wanted || sreply_wanted) {
	    strcpy(command, "POST");
	} else if (list_wanted) {
	    if (strlen(p1) > 249) {
		FREE(ProxyHost);
		FREE(ProxyHREF);
		HTAlert(URL_TOO_LONG);
		return -400;
	    }
	    SnipIn(command, "XGTITLE %.*s", 11, p1);
	} else if (group_wanted) {
	    char *slash = StrChr(p1, '/');

	    first = 0;
	    last = 0;
	    if (slash) {
		*slash = '\0';
		if (strlen(p1) >= sizeof(groupName)) {
		    FREE(ProxyHost);
		    FREE(ProxyHREF);
		    HTAlert(URL_TOO_LONG);
		    return -400;
		}
		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
		*slash = '/';
		(void) sscanf(slash + 1, "%d-%d", &first, &last);
		if ((first > 0) && (isdigit(UCH(*(slash + 1)))) &&
		    (StrChr(slash + 1, '-') == NULL || first == last)) {
		    /*
		     * We got a number greater than 0, which will be loaded as
		     * first, and either no range or the range computes to
		     * zero, so make last negative, as a flag to select the
		     * group and then fetch an article by number (first)
		     * instead of by messageID.  - FM
		     */
		    last = -1;
		}
	    } else {
		if (strlen(p1) >= sizeof(groupName)) {
		    FREE(ProxyHost);
		    FREE(ProxyHREF);
		    HTAlert(URL_TOO_LONG);
		    return -400;
		}
		LYStrNCpy(groupName, p1, sizeof(groupName) - 1);
	    }
	    SnipIn(command, "GROUP %.*s", 9, groupName);
	} else {
	    size_t add_open = (size_t) (StrChr(p1, '<') == 0);
	    size_t add_close = (size_t) (StrChr(p1, '>') == 0);

	    if (strlen(p1) + add_open + add_close >= 252) {
		FREE(ProxyHost);
		FREE(ProxyHREF);
		HTAlert(URL_TOO_LONG);
		return -400;
	    }
	    sprintf(command, "ARTICLE %s%.*s%s",
		    add_open ? "<" : "",
		    (int) (sizeof(command) - (11 + add_open + add_close)),
		    p1,
		    add_close ? ">" : "");
	}

	{
	    char *p = command + strlen(command);

	    /*
	     * Terminate command with CRLF, as in RFC 977.
	     */
	    *p++ = CR;		/* Macros to be correct on Mac */
	    *p++ = LF;
	    *p = 0;
	}
	StrAllocCopy(ListArg, p1);
    }				/* scope of p1 */

    if (!*arg) {
	FREE(NewsHREF);
	FREE(ProxyHost);
	FREE(ProxyHREF);
	FREE(ListArg);
	return NO;		/* Ignore if no name */
    }

    if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted ||
	  (group_wanted && last != -1) || list_wanted)) {
	head_wanted = anAnchor->isHEAD;
	if (head_wanted && !StrNCmp(command, "ARTICLE ", 8)) {
	    /* overwrite "ARTICLE" - hack... */
	    strcpy(command, "HEAD ");
	    for (cp = command + 5;; cp++)
		if ((*cp = *(cp + 3)) == '\0')
		    break;
	}
	rawtext = (BOOL) (head_wanted || keep_mime_headers);
    }
    if (rawtext) {
	rawtarget = HTStreamStack(WWW_PLAINTEXT,
				  format_out,
				  stream, anAnchor);
	if (!rawtarget) {
	    FREE(NewsHost);
	    FREE(NewsHREF);
	    FREE(ProxyHost);
	    FREE(ProxyHREF);
	    FREE(ListArg);
	    HTAlert(gettext("No target for raw text!"));
	    return (HT_NOT_LOADED);
	}			/* Copy routine entry points */
	rawtargetClass = *rawtarget->isa;
    } else
	/*
	 * Make a hypertext object with an anchor list.
	 */
    if (!(post_wanted || reply_wanted || spost_wanted || sreply_wanted)) {
	target = HTML_new(anAnchor, format_out, stream);
	targetClass = *target->isa;	/* Copy routine entry points */
    }

    /*
     * Now, let's get a stream setup up from the NewsHost.
     */
    for (retries = 0; retries < 2; retries++) {
	if (s < 0) {
	    /* CONNECTING to news host */
	    char url[260];

	    if (!strcmp(NewsHREF, STR_NEWS_URL)) {
		SnipIn(url, "lose://%.*s/", 9, NewsHost);
	    } else if (ProxyHREF) {
		SnipIn(url, "%.*s", 1, ProxyHREF);
	    } else {
		SnipIn(url, "%.*s", 1, NewsHREF);
	    }
	    CTRACE((tfp, "News: doing HTDoConnect on '%s'\n", url));

	    _HTProgress(gettext("Connecting to NewsHost ..."));

#ifdef USE_SSL
	    if (!using_proxy &&
		(!StrNCmp(arg, STR_SNEWS_URL, 6) ||
		 !StrNCmp(arg, "snewspost:", 10) ||
		 !StrNCmp(arg, "snewsreply:", 11)))
		status = HTDoConnect(url, "NNTPS", SNEWS_PORT, &s);
	    else
		status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
#else
	    status = HTDoConnect(url, "NNTP", NEWS_PORT, &s);
#endif /* USE_SSL */

	    if (status == HT_INTERRUPTED) {
		/*
		 * Interrupt cleanly.
		 */
		CTRACE((tfp,
			"HTNews: Interrupted on connect; recovering cleanly.\n"));
		_HTProgress(CONNECTION_INTERRUPTED);
		if (!(post_wanted || reply_wanted ||
		      spost_wanted || sreply_wanted)) {
		    ABORT_TARGET;
		}
		FREE(NewsHost);
		FREE(NewsHREF);
		FREE(ProxyHost);
		FREE(ProxyHREF);
		FREE(ListArg);
#ifdef USE_SSL
		if (Handle) {
		    SSL_free(Handle);
		    Handle = NULL;
		}
#endif /* USE_SSL */
		if (postfile) {
		    HTSYS_remove(postfile);
		    FREE(postfile);
		}
		return HT_NOT_LOADED;
	    }
	    if (status < 0) {
		NEWS_NETCLOSE(s);
		s = -1;
		CTRACE((tfp, "HTNews: Unable to connect to news host.\n"));
		if (retries < 1)
		    continue;
		if (!(post_wanted || reply_wanted ||
		      spost_wanted || sreply_wanted)) {
		    ABORT_TARGET;
		}
		HTSprintf0(&dbuf, gettext("Could not access %s."), NewsHost);
		FREE(NewsHost);
		FREE(NewsHREF);
		FREE(ProxyHost);
		FREE(ProxyHREF);
		FREE(ListArg);
		if (postfile) {
		    HTSYS_remove(postfile);
		    FREE(postfile);
		}
		return HTLoadError(stream, 500, dbuf);
	    } else {
		CTRACE((tfp, "HTNews: Connected to news host %s.\n",
			NewsHost));
#ifdef USE_SSL
		/*
		 * If this is an snews url, then do the SSL stuff here
		 */
		if (!using_proxy &&
		    (!StrNCmp(url, "snews", 5) ||
		     !StrNCmp(url, "snewspost:", 10) ||
		     !StrNCmp(url, "snewsreply:", 11))) {
		    Handle = HTGetSSLHandle();
		    SSL_set_fd(Handle, s);
		    HTSSLInitPRNG();
		    status = SSL_connect(Handle);

		    if (status <= 0) {
			unsigned long SSLerror;

			CTRACE((tfp,
				"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
				url, status));
			SSL_load_error_strings();
			while ((SSLerror = ERR_get_error()) != 0) {
			    CTRACE((tfp, "HTNews: SSL: %s\n",
				    ERR_error_string(SSLerror, NULL)));
			}
			HTAlert("Unable to make secure connection to remote host.");
			NEWS_NETCLOSE(s);
			s = -1;
			if (!(post_wanted || reply_wanted ||
			      spost_wanted || sreply_wanted))
			    (*targetClass._abort) (target, NULL);
			FREE(NewsHost);
			FREE(NewsHREF);
			FREE(ProxyHost);
			FREE(ProxyHREF);
			FREE(ListArg);
			if (postfile) {
#ifdef VMS
			    while (remove(postfile) == 0) ;	/* loop through all versions */
#else
			    remove(postfile);
#endif /* VMS */
			    FREE(postfile);
			}
			return HT_NOT_LOADED;
		    }
		    sprintf(SSLprogress,
			    "Secure %d-bit %s (%s) NNTP connection",
			    SSL_get_cipher_bits(Handle, NULL),
			    SSL_get_cipher_version(Handle),
			    SSL_get_cipher(Handle));
		    _HTProgress(SSLprogress);
		}
#endif /* USE_SSL */
		HTInitInput(s);	/* set up buffering */
		if (proxycmd[0]) {
		    status = (int) NEWS_NETWRITE(s, proxycmd, (int) strlen(proxycmd));
		    CTRACE((tfp,
			    "HTNews: Proxy command returned status '%d'.\n",
			    status));
		}
		if (((status = response(NULL)) / 100) != 2) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		    if (status == HT_INTERRUPTED) {
			_HTProgress(CONNECTION_INTERRUPTED);
			if (!(post_wanted || reply_wanted ||
			      spost_wanted || sreply_wanted)) {
			    ABORT_TARGET;
			}
			FREE(NewsHost);
			FREE(NewsHREF);
			FREE(ProxyHost);
			FREE(ProxyHREF);
			FREE(ListArg);
			if (postfile) {
			    HTSYS_remove(postfile);
			    FREE(postfile);
			}
			return (HT_NOT_LOADED);
		    }
		    if (retries < 1)
			continue;
		    FREE(ProxyHost);
		    FREE(ProxyHREF);
		    FREE(ListArg);
		    FREE(postfile);
		    if (!(post_wanted || reply_wanted ||
			  spost_wanted || sreply_wanted)) {
			ABORT_TARGET;
		    }
		    if (response_text[0]) {
			HTSprintf0(&dbuf,
				   gettext("Can't read news info.  News host %.20s responded: %.200s"),
				   NewsHost, response_text);
		    } else {
			HTSprintf0(&dbuf,
				   gettext("Can't read news info, empty response from host %s"),
				   NewsHost);
		    }
		    return HTLoadError(stream, 500, dbuf);
		}
		if (status == 200) {
		    HTCanPost = TRUE;
		} else {
		    HTCanPost = FALSE;
		    if (post_wanted || reply_wanted ||
			spost_wanted || sreply_wanted) {
			HTAlert(CANNOT_POST);
			FREE(NewsHREF);
			if (ProxyHREF) {
			    StrAllocCopy(NewsHost, ProxyHost);
			    FREE(ProxyHost);
			    FREE(ProxyHREF);
			}
			FREE(ListArg);
			if (postfile) {
			    HTSYS_remove(postfile);
			    FREE(postfile);
			}
			return (HT_NOT_LOADED);
		    }
		}
	    }
	}
	/* If needed opening */
	if (post_wanted || reply_wanted ||
	    spost_wanted || sreply_wanted) {
	    if (!HTCanPost) {
		HTAlert(CANNOT_POST);
		FREE(NewsHREF);
		if (ProxyHREF) {
		    StrAllocCopy(NewsHost, ProxyHost);
		    FREE(ProxyHost);
		    FREE(ProxyHREF);
		}
		FREE(ListArg);
		if (postfile) {
		    HTSYS_remove(postfile);
		    FREE(postfile);
		}
		return (HT_NOT_LOADED);
	    }
	    if (postfile == NULL) {
		postfile = LYNewsPost(ListArg,
				      (reply_wanted || sreply_wanted));
	    }
	    if (postfile == NULL) {
		HTProgress(CANCELLED);
		FREE(NewsHREF);
		if (ProxyHREF) {
		    StrAllocCopy(NewsHost, ProxyHost);
		    FREE(ProxyHost);
		    FREE(ProxyHREF);
		}
		FREE(ListArg);
		return (HT_NOT_LOADED);
	    }
	} else {
	    /*
	     * Ensure reader mode, but don't bother checking the status for
	     * anything but HT_INTERRUPTED or a 480 Authorization request,
	     * because if the reader mode command is not needed, the server
	     * probably returned a 500, which is irrelevant at this point.  -
	     * FM
	     */
	    char buffer[20];

	    sprintf(buffer, "mode reader%c%c", CR, LF);
	    if ((status = response(buffer)) == HT_INTERRUPTED) {
		_HTProgress(CONNECTION_INTERRUPTED);
		break;
	    }
	    if (status == 480) {
		NNTPAuthResult auth_result = HTHandleAuthInfo(NewsHost);

		if (auth_result == NNTPAUTH_CLOSE) {
		    if (s != -1 && !(ProxyHost || ProxyHREF)) {
			NEWS_NETCLOSE(s);
			s = -1;
		    }
		}
		if (auth_result != NNTPAUTH_OK) {
		    break;
		}
		if (response(buffer) == HT_INTERRUPTED) {
		    _HTProgress(CONNECTION_INTERRUPTED);
		    break;
		}
	    }
	}

      Send_NNTP_command:
#ifdef NEWS_DEB
	if (postfile)
	    printf("postfile = %s, command = %s", postfile, command);
	else
	    printf("command = %s", command);
#endif
	if ((status = response(command)) == HT_INTERRUPTED) {
	    _HTProgress(CONNECTION_INTERRUPTED);
	    break;
	}
	if (status < 0) {
	    if (retries < 1) {
		continue;
	    } else {
		break;
	    }
	}
	/*
	 * For some well known error responses which are expected to occur in
	 * normal use, break from the loop without retrying and without closing
	 * the connection.  It is unlikely that these are leftovers from a
	 * timed-out connection (but we do some checks to see whether the
	 * response corresponds to the last command), or that they will give
	 * anything else when automatically retried.  - kw
	 */
	if (status == 411 && group_wanted &&
	    !StrNCmp(command, "GROUP ", 6) &&
	    !strncasecomp(response_text + 3, " No such group ", 15) &&
	    !strcmp(response_text + 18, groupName)) {

	    HTAlert(response_text);
	    break;
	} else if (status == 430 && !group_wanted && !list_wanted &&
		   !StrNCmp(command, "ARTICLE <", 9) &&
		   !strcasecomp(response_text + 3, " No such article")) {

	    HTAlert(response_text);
	    break;
	}
	if ((status / 100) != 2 &&
	    status != 340 &&
	    status != 480) {
	    if (retries) {
		if (list_wanted && !StrNCmp(command, "XGTITLE", 7)) {
		    sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
		    goto Send_NNTP_command;
		}
		HTAlert(response_text);
	    } else {
		_HTProgress(response_text);
	    }
	    NEWS_NETCLOSE(s);
	    s = -1;
	    /*
	     * Message might be a leftover "Timeout-disconnected", so try again
	     * if the retries maximum has not been reached.
	     */
	    continue;
	}

	/*
	 * Post or load a group, article, etc
	 */
	if (status == 480) {
	    NNTPAuthResult auth_result;

	    /*
	     * Some servers return 480 for a failed XGTITLE.  - FM
	     */
	    if (list_wanted && !StrNCmp(command, "XGTITLE", 7) &&
		strstr(response_text, "uthenticat") == NULL &&
		strstr(response_text, "uthor") == NULL) {
		sprintf(command, "LIST NEWSGROUPS%c%c", CR, LF);
		goto Send_NNTP_command;
	    }
	    /*
	     * Handle Authorization.  - FM
	     */
	    if ((auth_result = HTHandleAuthInfo(NewsHost)) == NNTPAUTH_OK) {
		goto Send_NNTP_command;
	    } else if (auth_result == NNTPAUTH_CLOSE) {
		if (s != -1 && !(ProxyHost || ProxyHREF)) {
		    NEWS_NETCLOSE(s);
		    s = -1;
		}
		if (retries < 1)
		    continue;
	    }
	    status = HT_NOT_LOADED;
	} else if (post_wanted || reply_wanted ||
		   spost_wanted || sreply_wanted) {
	    /*
	     * Handle posting of an article.  - FM
	     */
	    if (status != 340) {
		HTAlert(CANNOT_POST);
		if (postfile) {
		    HTSYS_remove(postfile);
		}
	    } else {
		post_article(postfile);
	    }
	    FREE(postfile);
	    status = HT_NOT_LOADED;
	} else if (list_wanted) {
	    /*
	     * List available newsgroups.  - FM
	     */
	    _HTProgress(gettext("Reading list of available newsgroups."));
	    status = read_list(ListArg);
	} else if (group_wanted) {
	    /*
	     * List articles in a news group.  - FM
	     */
	    if (last < 0) {
		/*
		 * We got one article number rather than a range following the
		 * slash which followed the group name, or the range was zero,
		 * so now that we have selected that group, load ARTICLE and
		 * the the number (first) as the command and go back to send it
		 * and check the response.  - FM
		 */
		sprintf(command, "%s %d%c%c",
			head_wanted ? "HEAD" : "ARTICLE",
			first, CR, LF);
		group_wanted = FALSE;
		retries = 2;
		goto Send_NNTP_command;
	    }
	    _HTProgress(gettext("Reading list of articles in newsgroup."));
	    status = read_group(groupName, first, last);
	} else {
	    /*
	     * Get an article from a news group.  - FM
	     */
	    _HTProgress(gettext("Reading news article."));
	    status = read_article(anAnchor);
	}
	if (status == HT_INTERRUPTED) {
	    _HTProgress(CONNECTION_INTERRUPTED);
	    status = HT_LOADED;
	}
	if (!(post_wanted || reply_wanted ||
	      spost_wanted || sreply_wanted)) {
	    if (status == HT_NOT_LOADED) {
		ABORT_TARGET;
	    } else {
		FREE_TARGET;
	    }
	}
	FREE(NewsHREF);
	if (ProxyHREF) {
	    StrAllocCopy(NewsHost, ProxyHost);
	    FREE(ProxyHost);
	    FREE(ProxyHREF);
	}
	FREE(ListArg);
	if (postfile) {
	    HTSYS_remove(postfile);
	    FREE(postfile);
	}
	return status;
    }				/* Retry loop */

#if 0
    HTAlert(gettext("Sorry, could not load requested news."));
    NXRunAlertPanel(NULL, "Sorry, could not load `%s'.", NULL, NULL, NULL, arg);
    /* No -- message earlier wil have covered it */
#endif

    if (!(post_wanted || reply_wanted ||
	  spost_wanted || sreply_wanted)) {
	ABORT_TARGET;
    }
    FREE(NewsHREF);
    if (ProxyHREF) {
	StrAllocCopy(NewsHost, ProxyHost);
	FREE(ProxyHost);
	FREE(ProxyHREF);
    }
    FREE(ListArg);
    if (postfile) {
	HTSYS_remove(postfile);
	FREE(postfile);
    }
    return HT_NOT_LOADED;
}

/*
 *  This function clears all authorization information by
 *  invoking the free_NNTP_AuthInfo() function, which normally
 *  is invoked at exit.  It allows a browser command to do
 *  this at any time, for example, if the user is leaving
 *  the terminal for a period of time, but does not want
 *  to end the current session.  - FM
 */
void HTClearNNTPAuthInfo(void)
{
    /*
     * Need code to check cached documents and do something to ensure that any
     * protected documents no longer can be accessed without a new retrieval. 
     * - FM
     */

    /*
     * Now free all of the authorization info.  - FM
     */
    free_NNTP_AuthInfo();
}

#ifdef USE_SSL
static int HTNewsGetCharacter(void)
{
    if (!Handle)
	return HTGetCharacter();
    else
	return HTGetSSLCharacter((void *) Handle);
}

int HTNewsProxyConnect(int sock,
		       const char *url,
		       HTParentAnchor *anAnchor,
		       HTFormat format_out,
		       HTStream *sink)
{
    int status;
    const char *arg = url;
    char SSLprogress[256];

    s = channel_s = sock;
    Handle = HTGetSSLHandle();
    SSL_set_fd(Handle, s);
    HTSSLInitPRNG();
    status = SSL_connect(Handle);

    if (status <= 0) {
	unsigned long SSLerror;

	channel_s = -1;
	CTRACE((tfp,
		"HTNews: Unable to complete SSL handshake for '%s', SSL_connect=%d, SSL error stack dump follows\n",
		url, status));
	SSL_load_error_strings();
	while ((SSLerror = ERR_get_error()) != 0) {
	    CTRACE((tfp, "HTNews: SSL: %s\n", ERR_error_string(SSLerror, NULL)));
	}
	HTAlert("Unable to make secure connection to remote host.");
	NEWS_NETCLOSE(s);
	s = -1;
	return HT_NOT_LOADED;
    }
    sprintf(SSLprogress, "Secure %d-bit %s (%s) NNTP connection",
	    SSL_get_cipher_bits(Handle, NULL),
	    SSL_get_cipher_version(Handle),
	    SSL_get_cipher(Handle));
    _HTProgress(SSLprogress);
    status = HTLoadNews(arg, anAnchor, format_out, sink);
    channel_s = -1;
    return status;
}
#endif /* USE_SSL */

#ifdef GLOBALDEF_IS_MACRO
#define _HTNEWS_C_1_INIT { "news", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNews, _HTNEWS_C_1_INIT);
#define _HTNEWS_C_2_INIT { "nntp", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNNTP, _HTNEWS_C_2_INIT);
#define _HTNEWS_C_3_INIT { "newspost", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNewsPost, _HTNEWS_C_3_INIT);
#define _HTNEWS_C_4_INIT { "newsreply", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTNewsReply, _HTNEWS_C_4_INIT);
#define _HTNEWS_C_5_INIT { "snews", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNews, _HTNEWS_C_5_INIT);
#define _HTNEWS_C_6_INIT { "snewspost", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNewsPost, _HTNEWS_C_6_INIT);
#define _HTNEWS_C_7_INIT { "snewsreply", HTLoadNews, NULL }
GLOBALDEF(HTProtocol, HTSNewsReply, _HTNEWS_C_7_INIT);
#else
GLOBALDEF HTProtocol HTNews =
{"news", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNNTP =
{"nntp", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNewsPost =
{"newspost", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTNewsReply =
{"newsreply", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNews =
{"snews", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNewsPost =
{"snewspost", HTLoadNews, NULL};
GLOBALDEF HTProtocol HTSNewsReply =
{"snewsreply", HTLoadNews, NULL};
#endif /* GLOBALDEF_IS_MACRO */

#endif /* not DISABLE_NEWS */