/*
 *  Project   : tin - a Usenet reader
 *  Module    : nntplib.c
 *  Author    : S. Barber & I. Lea
 *  Created   : 1991-01-12
 *  Updated   : 2022-12-23
 *  Notes     : NNTP client routines taken from clientlib.c 1.5.11 (1991-02-10)
 *  Copyright : (c) Copyright 1991-99 by Stan Barber & Iain Lea
 *              Permission is hereby granted to copy, reproduce, redistribute
 *              or otherwise use this software  as long as: there is no
 *              monetary  profit  gained  specifically  from the use or
 *              reproduction or this software, it is not  sold, rented,
 *              traded or otherwise marketed, and this copyright notice
 *              is included prominently in any copy made.
 */


#ifndef TIN_H
#	include "tin.h"
#endif /* !TIN_H */
#ifndef TCURSES_H
#	include "tcurses.h"
#endif /* !TCURSES_H */
#ifndef TNNTP_H
#	include "tnntp.h"
#endif /* !TNNTP_H */

char *nntp_server = NULL;
#ifdef NO_POSTING
	t_bool can_post = FALSE;
#else
	t_bool can_post = TRUE;
#endif /* NO_POSTING */

#ifdef NNTP_ABLE
	/* Flag to show whether tin did reconnect in last get_server() */
	t_bool reconnected_in_last_get_server = FALSE;
	/* Flag used in LIST ACVTIVE loop */
	t_bool did_reconnect = FALSE;
#endif /* NNTP_ABLE */

#ifdef NNTP_ABLE
	/* Copy of last NNTP command sent, so we can retry it if needed */
	static char last_put[NNTP_STRLEN];
	static constext *xover_cmds = "XOVER";
	static constext *xhdr_cmds = "XHDR";
	/* Set so we don't reconnect just to QUIT */
	static t_bool quitting = FALSE;
#endif /* NNTP_ABLE */

/*
 * local prototypes
 */
#ifdef NNTP_ABLE
	static int mode_reader(t_bool *sec);
	static int reconnect(int retry);
	static int server_init(char *machine, const char *cservice, unsigned short port, char *text, size_t mlen);
	static void close_server(t_bool send_no_quit);
	static void list_motd(void);
#	ifdef INET6
		static int get_tcp6_socket(char *machine, unsigned short port);
#	else
		static int get_tcp_socket(char *machine, char *service, unsigned short port);
#	endif /* INET6 */
#	ifdef DECNET
		static int get_dnet_socket(char *machine, char *service);
#	endif /* DECNET */

struct simplebuf {
	unsigned char buf[4096];
	unsigned lb; /* lower bound */
	unsigned ub; /* upper bound */
};

struct nntpbuf {
	struct simplebuf rd;
	struct simplebuf wr;
	int fd;
#ifdef NNTPS_ABLE
	void *tls_ctx;
#endif /* NNTPS_ABLE */
};

#ifdef NNTPS_ABLE
#	define NNTPBUF_INITIALIZER { { {0}, 0, 0 }, { {0}, 0, 0 }, -1, NULL }
#else
#	define NNTPBUF_INITIALIZER { { {0}, 0, 0 }, { {0}, 0, 0 }, -1 }
#endif /* NNTPS_ABLE */

static struct nntpbuf nntp_buf = NNTPBUF_INITIALIZER;

static int nntpbuf_refill(struct nntpbuf *buf);
static int nntpbuf_flush(struct nntpbuf* buf);
static int nntpbuf_puts(const char* data, struct nntpbuf* buf);
static int nntpbuf_getc(struct nntpbuf *buf);
static int nntpbuf_ungetc(int c, struct nntpbuf *buf);
static char *nntpbuf_gets(char *s, int size, struct nntpbuf *buf);
static void nntpbuf_close(struct nntpbuf *buf);
static int nntpbuf_is_open(struct nntpbuf *buf);

#endif /* NNTP_ABLE */


/*
 * getserverbyfile(file)
 *
 * Get the name of a server from a named file.
 *	Handle white space and comments.
 *	Use NNTPSERVER environment variable if set.
 *
 *	Parameters: "file" is the name of the file to read.
 *
 *	Returns: Pointer to static data area containing the
 *	         first non-ws/comment line in the file.
 *	         NULL on error (or lack of entry in file).
 *
 *	Side effects: None.
 */
char *
getserverbyfile(
	const char *file)
{
	static char buf[256];
#ifdef NNTP_ABLE
	FILE *fp;
	char *cp;
#	if !defined(HAVE_SETENV) && defined(HAVE_PUTENV)
	char tmpbuf[256];
	char *new_env;
	static char *old_env = NULL;
#	endif /* !HAVE_SETENV && HAVE_PUTENV */
#endif /* NNTP_ABLE */

	if (read_saved_news) {
		STRCPY(buf, "reading saved news");
		return buf;
	}

	if (!read_news_via_nntp) {
		STRCPY(buf, "local");	/* what if a server is named "local"? */
		return buf;
	}

#ifdef NNTP_ABLE
	if (cmdline.args & CMDLINE_NNTPSERVER) {
		get_nntpserver(buf, sizeof(buf), cmdline.nntpserver);
#	ifdef HAVE_SETENV
		setenv("NNTPSERVER", buf, 1);
#	else
#		ifdef HAVE_PUTENV
		snprintf(tmpbuf, sizeof(tmpbuf), "NNTPSERVER=%s", buf);
		new_env = my_strdup(tmpbuf);
		putenv(new_env);
		FreeIfNeeded(old_env);
		old_env = new_env; /* we are 'leaking' the last malloced mem at exit here */
#		endif /* HAVE_PUTENV */
#	endif /* HAVE_SETENV */
		return buf;
	}

	if ((cp = getenv("NNTPSERVER")) != NULL) {
		get_nntpserver(buf, sizeof(buf), cp);
		return buf;
	}

	if (file == NULL)
		return NULL;

	if ((fp = fopen(file, "r")) != NULL) {

		while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
			if (*buf == '\n' || *buf == '#')
				continue;

			if ((cp = strrchr(buf, '\n')) != NULL)
				*cp = '\0';

			(void) fclose(fp);
			return buf;
		}
		(void) fclose(fp);
	}

#	ifdef NNTP_DEFAULT_SERVER
	if (*(NNTP_DEFAULT_SERVER))
		return strcpy(buf, NNTP_DEFAULT_SERVER);
#	endif /* NNTP_DEFAULT_SERVER */

#endif /* NNTP_ABLE */
	return NULL;	/* No entry */
}


/*
 * server_init  Get a connection to the remote server.
 *
 *	Parameters:	"machine" is the machine to connect to.
 *			"service" is the service to connect to on the machine.
 *			"port" is the service port to connect to.
 *
 *	Returns:	server's initial response code
 *			or -errno
 *
 *	Side effects:	Connects to server.
 *			"nntp_rd_fp" and "nntp_wr_fp" are fp's
 *			for reading and writing to server.
 *			"text" is updated to contain the rest of response string from server
 */
#ifdef NNTP_ABLE
static int
server_init(
	char *machine,
	const char *cservice,	/* usually a literal */
	unsigned short port,
	char *text,
	size_t mlen)
{
#	ifndef INET6
	char temp[256];
	char *service = strncpy(temp, cservice, sizeof(temp) - 1); /* ...calls non-const funcs */
#	endif /* !INET6 */
	int sock_fd;

#	ifdef DECNET
	char *cp;

	cp = strchr(machine, ':');

	if (cp && cp[1] == ':') {
		*cp = '\0';
		sock_fd = get_dnet_socket(machine, service);
	} else
		sock_fd = get_tcp_socket(machine, service, port);
#	else
#		ifdef INET6
	sock_fd = get_tcp6_socket(machine, port);
#		else
	sock_fd = get_tcp_socket(machine, service, port);
#		endif /* INET6 */
#	endif /* DECNET */

	if (sock_fd < 0)
		return sock_fd;

#	ifdef TLI /* Transport Level Interface */
	if (t_sync(sock_fd) < 0) {	/* Sync up new fd with TLI */
		t_error("server_init: t_sync()");
		close(sock_fd);
		return -EPROTO;
	}
#	endif /* TLI */

#ifdef NNTPS_ABLE

	if (use_nntps) {
		int result;

		result = tintls_open(machine, sock_fd, &nntp_buf.tls_ctx);
		if (result < 0) {
			return result;
		}

		result = tintls_handshake(nntp_buf.tls_ctx);
		if (result < 0) {
			return result;
		}
	}

#endif /* NNTPS_ABLE */

	nntp_buf.fd = sock_fd;

	last_put[0] = '\0';		/* no retries in get_respcode */
	/*
	 * Now get the server's signon message
	 */
	return (get_respcode(text, mlen));
}
#endif /* NNTP_ABLE */


/*
 * get_tcp_socket -- get us a socket connected to the specified server.
 *
 *	Parameters:	"machine" is the machine the server is running on.
 *			"service" is the service to connect to on the server.
 *			"port" is the port to connect to on the server.
 *
 *	Returns:	Socket connected to the server if all if ok
 *			EPROTO		for internal socket errors
 *			EHOSTUNREACH	if specified NNTP port/server can't be located
 *			errno		any valid errno value on connection
 *
 *	Side effects:	Connects to server.
 *
 *	Errors:		Returned & printed via perror.
 */
#if defined(NNTP_ABLE) && !defined(INET6)
static int
get_tcp_socket(
	char *machine,		/* remote host */
	char *service,		/* nttp/smtp etc. */
	unsigned short port)	/* tcp port number */
{
	int s = -1;
	int save_errno = 0;
	struct sockaddr_in sock_in;
#	ifdef TLI /* Transport Level Interface */
	char device[20];
	char *env_device;
	extern int t_errno;
	extern struct hostent *gethostbyname();
	struct hostent *hp;
	struct t_call *callptr;

	/*
	 * Create a TCP transport endpoint.
	 */
	if ((env_device = getenv("DEV_TCP")) != NULL) /* SCO uses DEV_TCP, most other OS use /dev/tcp */
		STRCPY(device, env_device);
	else
		strcpy(device, "/dev/tcp");

	if ((s = t_open(device, O_RDWR, (struct t_info *) 0)) < 0) {
		t_error(txt_error_topen);
		return -EPROTO;
	}
	if (t_bind(s, (struct t_bind *) 0, (struct t_bind *) 0) < 0) {
		t_error("t_bind");
		t_close(s);
		return -EPROTO;
	}
	memset((char *) &sock_in, '\0', sizeof(sock_in));
	sock_in.sin_family = AF_INET;
	sock_in.sin_port = htons(port);

	if (!isdigit((unsigned char) *machine)
#		ifdef HAVE_INET_ATON
	    || !inet_aton(machine, &sock_in)
#		else
#			ifdef HAVE_INET_ADDR
	    || (long) (sock_in.sin_addr.s_addr = inet_addr(machine)) == INADDR_NONE
#			endif /* HAVE_INET_ADDR */
#		endif /* HAVE_INET_ATON */
	) {
		if ((hp = gethostbyname(machine)) == NULL) {
			my_fprintf(stderr, _(txt_gethostbyname), "gethostbyname() ", machine);
			t_close(s);
			return -EHOSTUNREACH;
		}
		memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
	}

	/*
	 * Allocate a t_call structure and initialize it.
	 * Let t_alloc() initialize the addr structure of the t_call structure.
	 */
	if ((callptr = (struct t_call *) t_alloc(s, T_CALL, T_ADDR)) == NULL) {
		t_error("t_alloc");
		t_close(s);
		return -EPROTO;
	}

	callptr->addr.maxlen = sizeof(sock_in);
	callptr->addr.len = sizeof(sock_in);
	callptr->addr.buf = (char *) &sock_in;
	callptr->opt.len = 0;			/* no options */
	callptr->udata.len = 0;			/* no user data with connect */

	/*
	 * Connect to the server.
	 */
	if (t_connect(s, callptr, (struct t_call *) 0) < 0) {
		save_errno = t_errno;
		if (save_errno == TLOOK)
			fprintf(stderr, "%s", _(txt_error_server_unavailable));
		else
			t_error("t_connect");
		t_free((char *) callptr, T_CALL);
		t_close(s);
		return -save_errno;
	}

	/*
	 * Now replace the timod module with the tirdwr module so that
	 * standard read() and write() system calls can be used on the
	 * descriptor.
	 */

	t_free((char *) callptr, T_CALL);

	if (ioctl(s, I_POP, NULL) < 0) {
		perror("I_POP(timod)");
		t_close(s);
		return -EPROTO;
	}

	if (ioctl(s, I_PUSH, "tirdwr") < 0) {
		perror("I_PUSH(tirdwr)");
		t_close(s);
		return -EPROTO;
	}

#	else
#		ifndef EXCELAN
	struct servent *sp;
	struct hostent *hp;
#			ifdef h_addr
	int x = 0;
	char **cp;
#			endif /* h_addr */
#			ifdef HAVE_HOSTENT_H_ADDR_LIST
	static char *alist[2] = {0, 0};
#			endif /* HAVE_HOSTENT_H_ADDR_LIST */
	static struct hostent def;
	static struct in_addr defaddr;
	static char namebuf[256];

#			ifdef HAVE_GETSERVBYNAME
	if ((sp = (struct servent *) getservbyname(service, "tcp")) == NULL) {
		my_fprintf(stderr, _(txt_error_unknown_service), service);
		return -EHOSTUNREACH;
	}
#			else
	sp = my_malloc(sizeof(struct servent));
	sp->s_port = htons(IPPORT_NNTP);
#			endif /* HAVE_GETSERVBYNAME */

	/* If not a raw ip address, try nameserver */
	if (!isdigit((unsigned char) *machine)
#			ifdef HAVE_INET_ATON
	    || !inet_aton(machine, &defaddr)
#			else
#				ifdef HAVE_INET_ADDR
	    || (long) (defaddr.s_addr = (long) inet_addr(machine)) == -1
#				endif /* HAVE_INET_ADDR */
#			endif /* HAVE_INET_ATON */
	    )
	{
		hp = gethostbyname(machine);
	} else {
		/* Raw ip address, fake */
		STRCPY(namebuf, machine);
		def.h_name = (char *) namebuf;
#			ifdef HAVE_HOSTENT_H_ADDR_LIST
		def.h_addr_list = alist;
		def.h_addr_list[0] = (char *) &defaddr;
#			else
		def.h_addr = (char *) &defaddr;
#			endif /* HAVE_HOSTENT_H_ADDR_LIST */
		def.h_length = sizeof(struct in_addr);
		def.h_addrtype = AF_INET;
		def.h_aliases = 0;
		hp = &def;
	}

	if (hp == NULL) {
		my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
		return -EHOSTUNREACH;
	}

	memset((char *) &sock_in, '\0', sizeof(sock_in));
	sock_in.sin_family = hp->h_addrtype;
	sock_in.sin_port = htons(port);
/*	sock_in.sin_port = sp->s_port; */
#		else
	memset((char *) &sock_in, '\0', sizeof(sock_in));
	sock_in.sin_family = AF_INET;
#		endif /* !EXCELAN */

	/*
	 * The following is kinda gross. The name server under 4.3
	 * returns a list of addresses, each of which should be tried
	 * in turn if the previous one fails. However, 4.2 hostent
	 * structure doesn't have this list of addresses.
	 * Under 4.3, h_addr is a #define to h_addr_list[0].
	 * We use this to figure out whether to include the NS specific
	 * code...
	 */

#		ifdef h_addr
	/*
	 * Get a socket and initiate connection -- use multiple addresses
	 */
	for (cp = hp->h_addr_list; cp && *cp; cp++) {
#			if defined(__hpux) && defined(SVR4)
		unsigned long socksize, socksizelen;
#			endif /* __hpux && SVR4 */

		if ((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
			perror("socket");
			return -errno;
		}

		memcpy((char *) &sock_in.sin_addr, *cp, hp->h_length);

#			ifdef HAVE_INET_NTOA
		if (x < 0)
			my_fprintf(stderr, _(txt_trying), (char *) inet_ntoa(sock_in.sin_addr));
#			endif /* HAVE_INET_NTOA */

#			if defined(__hpux) && defined(SVR4)	/* recommended by raj@cup.hp.com */
#				define HPSOCKSIZE 0x8000
		getsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
		if (socksize < HPSOCKSIZE) {
			socksize = HPSOCKSIZE;
			setsockopt(s, SOL_SOCKET, SO_SNDBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
		}
		socksize = 0;
		socksizelen = sizeof(socksize);
		getsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, /* (caddr_t) */ &socksizelen);
		if (socksize < HPSOCKSIZE) {
			socksize = HPSOCKSIZE;
			setsockopt(s, SOL_SOCKET, SO_RCVBUF, /* (caddr_t) */ &socksize, sizeof(socksize));
		}
#			endif /* __hpux && SVR4 */

		if ((x = connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in))) == 0)
			break;

		save_errno = errno;									/* Keep for later */
#			ifdef HAVE_INET_NTOA
		my_fprintf(stderr, _(txt_connection_to), (char *) inet_ntoa(sock_in.sin_addr));
		perror("");
#			endif /* HAVE_INET_NTOA */
		(void) s_close(s);
	}

	if (x < 0) {
		my_fprintf(stderr, "%s", _(txt_giving_up));
		return -save_errno;					/* Return the last errno we got */
	}
#		else

#			ifdef EXCELAN
	if ((s = socket(SOCK_STREAM, (struct sockproto *) NULL, &sock_in, SO_KEEPALIVE)) < 0) {
		perror("socket");
		return -errno;
	}

	/* set up addr for the connect */
	memset((char *) &sock_in, '\0', sizeof(sock_in));
	sock_in.sin_family = AF_INET;
	sock_in.sin_port = htons(IPPORT_NNTP);

	if ((sock_in.sin_addr.s_addr = rhost(&machine)) == -1) {
		my_fprintf(stderr, _(txt_gethostbyname), "\n", machine);
		return -1;
	}

	/* And connect */
	if (connect(s, (struct sockaddr *) &sock_in) < 0) {
		save_errno = errno;
		perror("connect");
		(void) s_close(s);
		return -save_errno;
	}

#			else
	if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return -errno;
	}

	/* And then connect */
#				ifdef HAVE_HOSTENT_H_ADDR_LIST
	memcpy((char *) &sock_in.sin_addr, hp->h_addr_list[0], hp->h_length);
#				else
	memcpy((char *) &sock_in.sin_addr, hp->h_addr, hp->h_length);
#				endif /* HAVE_HOSTENT_H_ADDR_LIST */
	if (connect(s, (struct sockaddr *) &sock_in, sizeof(sock_in)) < 0) {
		save_errno = errno;
		perror("connect");
		(void) s_close(s);
		return -save_errno;
	}

#			endif /* EXCELAN */
#		endif /* h_addr */
#	endif /* TLI */
	return s;
}
#endif /* NNTP_ABLE && !INET6 */


#if defined(NNTP_ABLE) && defined(INET6)
/*
 * get_tcp6_socket -- get us a socket connected to the server.
 *
 * Parameters:   "machine" is the machine the server is running on.
 *                "port" is the portnumber to connect to.
 *
 * Returns:      Socket connected to the news server if
 *               all is ok, else -1 on error.
 *
 * Side effects: Connects to server via IPv4 or IPv6.
 *
 * Errors:       Printed via my_fprintf.
 */
static int
get_tcp6_socket(
	char *machine,
	unsigned short port)
{
	char mymachine[MAXHOSTNAMELEN + 1];
	char myport[12];
	int s = -1, err, ec = 0, es = 0;
	struct addrinfo hints, *res, *res0;

	snprintf(mymachine, sizeof(mymachine), "%s", machine);
	snprintf(myport, sizeof(myport), "%u", port);

/* just in case */
#	ifdef AF_UNSPEC
#		define ADDRFAM	AF_UNSPEC
#	else
#		ifdef PF_UNSPEC
#			define ADDRFAM	PF_UNSPEC
#		else
#			define ADDRFAM	AF_INET
#		endif /* PF_UNSPEC */
#	endif /* AF_UNSPEC */
	memset(&hints, 0, sizeof(hints));
/*	hints.ai_flags = AI_CANONNAME; */
	hints.ai_family = (force_ipv4 ? AF_INET : (force_ipv6 ? AF_INET6 : ADDRFAM));
	hints.ai_socktype = SOCK_STREAM;
	res0 = (struct addrinfo *) 0;
	if ((err = getaddrinfo(mymachine, myport, &hints, &res0))) {
		my_fprintf(stderr, "\ngetaddrinfo: %s\n", gai_strerror(err));
		return -1;
	}
	err = -1;
	for (res = res0; res; res = res->ai_next) {
		if ((s = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) {
			es = errno;
			continue;
		}
		if (connect(s, res->ai_addr, res->ai_addrlen) != 0) {
			ec = errno;
			close(s);
		} else {
			es = ec = err = 0;
			break;
		}
	}
	if (res0 != NULL)
		freeaddrinfo(res0);
	if (err < 0) {
		my_fprintf(stderr, "%s", _(txt_error_socket_or_connect_problem));
		if (es)
			my_fprintf(stderr, "\tsocket(2): %s\n", strerror(es));
		if (ec)
			my_fprintf(stderr, "\tconnect(2): %s\n", strerror(ec));
		sleep(3);
		return -1;
	}
	return s;
}
#endif /* NNTP_ABLE && INET6 */


#ifdef DECNET
/*
 * get_dnet_socket -- get us a socket connected to the server.
 *
 *	Parameters:	"machine" is the machine the server is running on.
 *			"service" is the name of the service to connect to.
 *
 *	Returns:	Socket connected to the news server if
 *			all is ok, else -1 on error.
 *
 *	Side effects:	Connects to server.
 *
 *	Errors:		Printed via nerror.
 */
static int
get_dnet_socket(
	char *machine,
	char *service)
{
#	ifdef NNTP_ABLE
	int s, area, node;
	struct sockaddr_dn sdn;
	struct nodeent *getnodebyname(), *np;

	memset((char *) &sdn, '\0', sizeof(sdn));

	switch (s = sscanf(machine, "%d%*[.]%d", &area, &node)) {
		case 1:
			node = area;
			area = 0;
			/* FALLTHROUGH */
		case 2:
			node += area * 1024;
			sdn.sdn_add.a_len = 2;
			sdn.sdn_family = AF_DECnet;
			sdn.sdn_add.a_addr[0] = node % 256;
			sdn.sdn_add.a_addr[1] = node / 256;
			break;

		default:
			if ((np = getnodebyname(machine)) == NULL) {
				my_fprintf(stderr, _(txt_gethostbyname), "", machine);
				return -1;
			} else {
				memcpy((char *) sdn.sdn_add.a_addr, np->n_addr, np->n_length);
				sdn.sdn_add.a_len = np->n_length;
				sdn.sdn_family = np->n_addrtype;
			}
			break;
	}
	sdn.sdn_objnum = 0;
	sdn.sdn_flags = 0;
	sdn.sdn_objnamel = strlen("NNTP");
	memcpy(&sdn.sdn_objname[0], "NNTP", sdn.sdn_objnamel);

	if ((s = socket(AF_DECnet, SOCK_STREAM, 0)) < 0) {
		nerror("socket");
		return -1;
	}

	/* And then connect */

	if (connect(s, (struct sockaddr *) &sdn, sizeof(sdn)) < 0) {
		nerror("connect");
		s_close(s);
		return -1;
	}

	return s;
#	else
	return -1;
#	endif /* NNTP_ABLE */
}
#endif /* DECNET */


/*----------------------------------------------------------------------
 * Ideally the code after this point should be the only interface to the
 * NNTP internals...
 */

/*
 * u_put_server -- send data to the server. Do not flush output.
 */
#ifdef NNTP_ABLE
void
u_put_server(
	const char *string)
{
	nntpbuf_puts(string, &nntp_buf);
#	ifdef DEBUG
	if (debug & DEBUG_NNTP) {
		if (strcmp(string, "\r\n"))
			debug_print_file("NNTP", ">>>%s%s", logtime(), string);
	}
#	endif /* DEBUG */
}


/*
 * Send 'string' to the NNTP server, terminating it with CR and LF, as per
 * ARPA standard.
 *
 * Returns: Nothing.
 *
 *	Side effects: Talks to the server.
 *	              Closes connection if things are not right.
 *
 * Note: This routine flushes the buffer each time it is called. For large
 *       transmissions (i.e., posting news) don't use it. Instead, do the
 *       fprintf's yourself, and then a final fflush.
 *       Only cache commands, don't cache data transmissions.
 */
void
put_server(
	const char *string)
{
	if (*string && strlen(string)) {
		DEBUG_IO((stderr, "put_server(%s)\n", string));
		nntpbuf_puts(string, &nntp_buf);
		nntpbuf_puts("\r\n", &nntp_buf);
#	ifdef DEBUG
		if (debug & DEBUG_NNTP)
			debug_print_file("NNTP", ">>>%s%s", logtime(), string);
#	endif /* DEBUG */
		/*
		 * remember the last command we wrote to be able to resend it after a
		 * reconnect. reconnection is handled by get_server()
		 *
		 * don't cache "LIST [ACTIVE|COUNTS|NEWSGROUPS] something" as we
		 * would need to resend all of them but we remember just the last
		 * one. we cache "LIST cmd." instead, this will slow down things, but
		 * that's ok on reconnect.
		 */
		if (strcmp(last_put, string))
			STRCPY(last_put, string);
		if (!strncmp(string, "LIST ACTIVE ", 12))
			last_put[11] = '\0'; /* "LIST ACTIVE" */
		else if (!strncmp(string, "LIST COUNTS ", 12))
			last_put[11] = '\0'; /* "LIST COUNTS" */
		else if (!strncmp(string, "LIST NEWSGROUPS ", 16))
			last_put[15] = '\0'; /* "LIST NEWSGROUPS" */
	}
	(void) nntpbuf_flush(&nntp_buf);
}


/*
 * Reconnect to server after a timeout, reissue last command to
 * get us back into the pre-timeout state
 */
static int
reconnect(
	int retry)
{
	char buf[NNTP_STRLEN];
	int save_signal_context = signal_context;

	/*
	 * Tear down current connection
	 * Close the NNTP connection with prejudice
	 */
	nntpbuf_close(&nntp_buf);

	if (!tinrc.auto_reconnect)
		ring_bell();

	DEBUG_IO((stderr, _("\nServer timed out, trying reconnect # %d\n"), retry));

	/*
	 * set signal_context temporary to cReconnect to avoid trouble when receiving
	 * SIGWINCH while being in prompt_yn()
	 */
	signal_context = cReconnect;

	/*
	 * Exit tin if there are no more tries or if the user says no to reconnect.
	 * The exit code stops tin from trying to disconnect again - the connection
	 * is already dead
	 */
	if (retry > NNTP_TRY_RECONNECT || (!tinrc.auto_reconnect && prompt_yn(_(txt_reconnect_to_news_server), TRUE) != 1)) {
		if (!strcmp("POST", last_put)) {
			unlink(backup_article_name(article_name));
			rename_file(article_name, dead_article);
			if (tinrc.keep_dead_articles)
				append_file(dead_article, dead_articles);
		}
		if (retry > NNTP_TRY_RECONNECT) {
#	ifdef DEBUG
			/* TODO: -> lang.c */
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "reconnect(%d) limit %d reached, giving up.", retry, NNTP_TRY_RECONNECT);
#	endif /* DEBUG */
		}
		tin_done(NNTP_ERROR_EXIT, _("NNTP connection error. Exiting..."));		/* user said no to reconnect or no more retries */
	}

	/* reset signal_context */
	signal_context = save_signal_context;

	clear_message();
	strcpy(buf, last_put);			/* Keep copy here, it will be clobbered a lot otherwise */

	if (!nntp_open()) {
		/* Re-establish our current group and resend last command */
		if (curr_group != NULL) {
			DEBUG_IO((stderr, _("Rejoin current group\n")));
			snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
			put_server(last_put);
			if (nntpbuf_gets(last_put, NNTP_STRLEN, &nntp_buf) == NULL)
				*last_put = '\0';
#	ifdef DEBUG
			if (debug & DEBUG_NNTP)
				debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
#	endif /* DEBUG */
			DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
		}
		DEBUG_IO((stderr, _("Resend last command (%s)\n"), buf));
		put_server(buf);
		did_reconnect = TRUE;
		retry = NNTP_TRY_RECONNECT;
	}

	return retry;
}


int
fgetc_server(
	FILE *stream)
{
	int c = EOF;

	if (stream != FAKE_NNTP_FP) {
		DEBUG_IO((stderr, "fgetc_server: BAD fp\n"));
		return EOF;
	}

	c = nntpbuf_getc(&nntp_buf);
	DEBUG_IO((stderr, "fgetc_server: %c / %d\n", c, c));

	return c;
}


int
ungetc_server(
	int c,
	FILE *stream)
{
	if (stream != FAKE_NNTP_FP) {
		DEBUG_IO((stderr, "fgetc_server: BAD fp\n"));
		return EOF;
	}

	DEBUG_IO((stderr, "ungetc_server: %c / %d\n", c, c));

	return nntpbuf_ungetc(c, &nntp_buf);
}


/*
 * Read a line of data from the NNTP socket. If something gives, do reconnect
 *
 *	Parameters:	"string" has the buffer space for the line received.
 *			"size" is maximum size of the buffer to read.
 *
 *	Returns:	NULL on end of stream, or a line of data.
 *				Basically, we try to act like fgets() on an NNTP stream.
 *
 *	Side effects:	Talks to server, changes contents of "string".
 *			Reopens connection when necessary and requested.
 *			Exits via tin_done() if fatal error occurs.
 */
char *
get_server(
	char *string,
	int size)
{
	static int retry_cnt = 0;

	reconnected_in_last_get_server = FALSE;
	errno = 0;

	/*
	 * NULL socket reads indicates socket has closed. Try a few times more
	 *
	 * Leave the s_gets() after a timeout for these cases:
	 *   -some servers do not close the connection but simply do not send any
	 *    response data
	 *   -the network connection went down
	 */
#	if defined(HAVE_ALARM) && defined(SIGALRM)
	alarm((unsigned) tinrc.nntp_read_timeout_secs);
#	endif /* HAVE_ALARM && SIGALRM */
	while (!nntpbuf_is_open(&nntp_buf) || nntpbuf_gets(string, size, &nntp_buf) == NULL) {
		if (errno == EINTR) {
			errno = 0;
#	if defined(HAVE_ALARM) && defined(SIGALRM)
			alarm((unsigned) tinrc.nntp_read_timeout_secs);		/* Restart the timer */
#	endif /* HAVE_ALARM && SIGALRM */
			continue;
		}
#	if defined(HAVE_ALARM) && defined(SIGALRM)
		alarm(0);
#	endif /* HAVE_ALARM && SIGALRM */
		if (quitting)						/* Don't bother to reconnect */
			tin_done(NNTP_ERROR_EXIT, NULL);		/* And don't try to disconnect again! */

#	ifdef DEBUG
		if (errno != 0 && errno != EINTR)	/* Will only confuse end users */
			perror_message("get_server()");
#	endif /* DEBUG */

		/*
		 * Reconnect only if command was not "QUIT" anyway (in which case a
		 * reconnection would be useless because the connection will be
		 * closed immediately). Also prevents tin from asking to reconnect
		 * when user is quitting tin if tinrc.auto_reconnect is false.
		 */
		if (strcmp(last_put, "QUIT")) {
			/*
			 * Typhoon v2.1.1.363 colses the connection right after an unknown
			 * command, (i.e. CAPABILITIES) so we avoid to reissue it on a
			 * reconnect if it was the last command.
			 */
			if (!strcmp(last_put, "CAPABILITIES")) {
				strcpy(last_put, "MODE READER");
				nntp_caps.type = BROKEN;
			}
			retry_cnt = reconnect(++retry_cnt);		/* Will abort when out of tries */
			reconnected_in_last_get_server = TRUE;
		} else {
			/*
			 * Use standard NNTP closing message and response code if user is
			 * quitting tin and leave loop.
			 */
			strncpy(string, _(txt_nntp_ok_goodbye), (size_t) (size - 3));
			strcat(string, "\r\n");		/* tin_fgets() needs CRLF */
			break;
		}
	}
#	if defined(HAVE_ALARM) && defined(SIGALRM)
	alarm(0);
#	endif /* HAVE_ALARM && SIGALRM */
	retry_cnt = 0;
	return string;
}


/*
 * Send "QUIT" command and close the connection to the server
 *
 * Side effects: Closes the connection to the server.
 *	              You can't use "put_server" or "get_server" after this
 *	              routine is called.
 *
 * TODO: remember servers response string and if it contains anything else
 *       than just "." (i.e. transfer statistics) present it to the user?
 *
 */
static void
close_server(
	t_bool send_no_quit)
{
	if (!send_no_quit && nntpbuf_is_open(&nntp_buf)) {
		if (!batch_mode || verbose) {
			char *msg;

			msg = strunc(_(txt_disconnecting), cCOLS - 1);
			my_fputs(msg, stdout);
			my_fputc('\n', stdout);
			free(msg);
		}
		nntp_command("QUIT", OK_GOODBYE, NULL, 0);
		quitting = TRUE;										/* Don't reconnect just for this */
	}
	nntpbuf_close(&nntp_buf);
}


/*
 * Try and use CAPABILITIES here. Get this list before issuing other NNTP
 * commands because the correct methods may be mentioned in the list of
 * extensions.
 *
 * Sets up: t_capabilities nntp_caps
 */
int
check_extensions(
	int rvl)
{
	char *d;
	char *ptr;
	char buf[NNTP_STRLEN];
	int i;
	int ret = 0;

	buf[0] = '\0';

	/* rvl > 0 = manually send "CAPABILITIES" to avoid endless AUTH loop */
	i = rvl ? rvl : new_nntp_command("CAPABILITIES", INF_CAPABILITIES, buf, sizeof(buf));
	switch (i) {
		case INF_CAPABILITIES:
			/* clear capabilities */
			nntp_caps.type = CAPABILITIES;
			nntp_caps.version = 0;
			nntp_caps.mode_reader = FALSE;
			nntp_caps.reader = FALSE;
			nntp_caps.post = FALSE;
			nntp_caps.list_active = FALSE;
			nntp_caps.list_active_times = FALSE;
			nntp_caps.list_distrib_pats = FALSE;
			nntp_caps.list_headers = FALSE;
			FreeAndNull(nntp_caps.headers_range);
			FreeAndNull(nntp_caps.headers_id);
			nntp_caps.list_newsgroups = FALSE;
			nntp_caps.list_overview_fmt = FALSE;
			nntp_caps.list_motd = FALSE;
			nntp_caps.list_subscriptions = FALSE;
			nntp_caps.list_distributions = FALSE;
			nntp_caps.list_moderators = FALSE;
			nntp_caps.list_counts = FALSE;
			nntp_caps.xpat = TRUE; /* toggles to false if fails, INN > 2.7.0 announces it */
			nntp_caps.hdr = FALSE;
			nntp_caps.hdr_cmd = NULL;
			nntp_caps.over = FALSE;
			nntp_caps.over_msgid = FALSE;
			nntp_caps.over_cmd = NULL;
			nntp_caps.newnews = FALSE;
			FreeAndNull(nntp_caps.implementation);
			nntp_caps.starttls = FALSE;
			nntp_caps.authinfo_user = FALSE;
			nntp_caps.authinfo_sasl = FALSE;
			nntp_caps.authinfo_state = FALSE;
			nntp_caps.sasl = SASL_NONE;
			nntp_caps.compress = FALSE;
			nntp_caps.compress_algorithm = COMPRESS_NONE;
			/* nntp_caps.maxartnum will be init it nntp_open() */
#	if 0
			nntp_caps.streaming = FALSE;
			nntp_caps.ihave = FALSE;
#	endif /* 0 */
#	ifndef BROKEN_LISTGROUP
			nntp_caps.broken_listgroup = FALSE;
#	else
			nntp_caps.broken_listgroup = TRUE;
#	endif /* !BROKEN_LISTGROUP */

			while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
#	ifdef DEBUG
				if (debug & DEBUG_NNTP)
					debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
#	endif /* DEBUG */
				/* look for version number(s) */
				if (!nntp_caps.version && nntp_caps.type == CAPABILITIES) {
					if (!strncasecmp(ptr, "VERSION", 7)) {
						d = ptr + 7;
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							nntp_caps.version = (unsigned int) atoi(d);
							d = strpbrk(d, " \t");
						}
					}
				}
				/* we currently only support CAPABILITIES VERSION 2 */
				if (nntp_caps.version == 2) {
					/*
					 * check for LIST variants
					 */
					if (!strncasecmp(ptr, "LIST", 4)) {
						d = ptr + 4;
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							if (!strncasecmp(d, "ACTIVE.TIMES", 12))
								nntp_caps.list_active_times = TRUE;
							else if (!strncasecmp(d, "ACTIVE", 6))
								nntp_caps.list_active = TRUE;
							else if (!strncasecmp(d, "DISTRIB.PATS", 12))
								nntp_caps.list_distrib_pats = TRUE;
							else if (!strncasecmp(d, "DISTRIBUTIONS", 13)) /* RFC 6048 */
								nntp_caps.list_distributions = TRUE;
							else if (!strncasecmp(d, "HEADERS", 7))
								nntp_caps.list_headers = TRUE; /* HDR requires LIST HEADERS, but not vice versa */
							else if (!strncasecmp(d, "NEWSGROUPS", 10))
								nntp_caps.list_newsgroups = TRUE;
							else if (!strncasecmp(d, "OVERVIEW.FMT", 12)) /* OVER requires OVERVIEW.FMT, but not vice versa */
								nntp_caps.list_overview_fmt = TRUE;
							else if (!strncasecmp(d, "MOTD", 4)) /* RFC 6048 */
								nntp_caps.list_motd = TRUE;
							else if (!strncasecmp(d, "SUBSCRIPTIONS", 13)) /* RFC 6048 */
								nntp_caps.list_subscriptions = TRUE;
							else if (!strncasecmp(d, "MODERATORS", 10)) /* RFC 6048 */
								nntp_caps.list_moderators = TRUE;
							else if (!strncasecmp(d, "COUNTS", 6)) /* RFC 6048 */
								nntp_caps.list_counts = TRUE;
							d = strpbrk(d, " \t");
						}
					} else if (!strncasecmp(ptr, "IMPLEMENTATION", 14)) {
						FreeIfNeeded(nntp_caps.implementation);
						nntp_caps.implementation = my_strdup(ptr + 14);
						str_trim(nntp_caps.implementation);
					} else if (!strcasecmp(ptr, "MODE-READER")) {
						if (!nntp_caps.reader)
							nntp_caps.mode_reader = TRUE;
					} else if (!strcasecmp(ptr, "READER")) { /* if we saw READER, "LIST ACTIVE" and "LIST NEWSGROUPS" must be implemented */
						nntp_caps.reader = TRUE;
						nntp_caps.mode_reader = FALSE;
						nntp_caps.list_newsgroups = TRUE;
						nntp_caps.list_active = TRUE;
					} else if (!strcasecmp(ptr, "POST"))
						nntp_caps.post = TRUE;
					else if (!strcasecmp(ptr, "NEWNEWS"))
						nntp_caps.newnews = TRUE;
					else if (!strcasecmp(ptr, "XPAT")) /* extension, RFC 2980 */
						nntp_caps.xpat = TRUE;
					else if (!strcasecmp(ptr, "STARTTLS"))
						nntp_caps.starttls = TRUE;
					/*
					 * NOTE: if we saw OVER, LIST OVERVIEW.FMT _must_ be implemented
					 */
					else if (!strncasecmp(ptr, &xover_cmds[1], strlen(&xover_cmds[1]))) {
						nntp_caps.list_overview_fmt = nntp_caps.over = TRUE;
						nntp_caps.over_cmd = &xover_cmds[1];
						d = ptr + strlen(&xover_cmds[1]);
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							if (!strcasecmp(d, "MSGID"))
								nntp_caps.over_msgid = TRUE;
							d = strpbrk(d, " \t");
						}
					}
					/*
					 * NOTE: if we saw HDR, LIST HEADERS _must_ be implemented
					 */
					else if (!strncasecmp(ptr, &xhdr_cmds[1], strlen(&xhdr_cmds[1]))) {
						nntp_caps.hdr_cmd = &xhdr_cmds[1];
						nntp_caps.list_headers = nntp_caps.hdr = TRUE;
						nntp_caps.headers_range = my_strdup("");
						nntp_caps.headers_id = my_strdup("");
					} else if (!strncasecmp(ptr, "AUTHINFO", 8)) {
						d = ptr + 8;
						d = strpbrk(d, " \t");
						if (d == NULL) /* AUTHINFO without args */
							nntp_caps.authinfo_state = TRUE;
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							if (!strncasecmp(d, "USER", 4))
								nntp_caps.authinfo_user = TRUE;
							if (!strncasecmp(d, "SASL", 4))
								nntp_caps.authinfo_sasl = TRUE;
							d = strpbrk(d, " \t");
						}
					} else if (!strncasecmp(ptr, "SASL", 4)) {
						d = ptr + 4;
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							if (!strncasecmp(d, "CRAM-MD5", 8)) { /* RFC 2195 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_CRAM_MD5;
							}
							if (!strncasecmp(d, "DIGEST-MD5", 10)) { /* RFC 2831 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_DIGEST_MD5;
							}
							if (!strncasecmp(d, "PLAIN", 5)) { /* RFC 4616 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_PLAIN;
							}
							if (!strncasecmp(d, "GSSAPI", 6)) { /* RFC 4752 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_GSSAPI;
							}
							if (!strncasecmp(d, "EXTERNAL", 8)) { /* RFC 4422 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_EXTERNAL;
							}
							/* inn also can do these */
							if (!strncasecmp(d, "OTP", 3)) { /* RFC 2444 */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_OTP;
							}
							if (!strncasecmp(d, "NTLM", 4)) { /* Microsoft */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_NTLM;
							}
							if (!strncasecmp(d, "LOGIN", 5)) { /* Microsoft */
								nntp_caps.authinfo_sasl = TRUE;
								nntp_caps.sasl |= SASL_LOGIN;
							}
						}
					} else if (!strncasecmp(ptr, "COMPRESS", 8)) { /* RFC 8054 */
						d = ptr + 8;
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							if (!strncasecmp(d, "DEFLATE", 7)) {
								nntp_caps.compress = TRUE;
								nntp_caps.compress_algorithm |= COMPRESS_DEFLATE;
							}
						}
					}
#	if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
					/*
					 * MAXARTNUM - <tnqm14$35bas$1@news.trigofacile.com>
					 *
					 * if server responds with MAXARTNUM we (re)parse it
					 * as we may have changed the state (auth) and it's
					 * the servers job to not advertised MAXARTNUM again
					 * after it had been used ...
					 */
					else if (!strncasecmp(ptr, "MAXARTNUM", 9)) {
						d = ptr + 9;
						d = strpbrk(d, " \t");
						while (d != NULL && (d + 1 < (ptr + strlen(ptr)))) {
							d++;
							nntp_caps.maxartnum = MIN(atoartnum(d), ARTNUM_MAX);
							d = strpbrk(d, " \t");
						}
					}
#	endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
#	if 0 /* we don't need these */
					else if (!strcasecmp(ptr, "IHAVE"))
						nntp_caps.ihave = TRUE;
					else if (!strcasecmp(ptr, "STREAMING"))
						nntp_caps.streaming = TRUE;
#	endif /* 0 */
				/* XZVER, XZHDR, ... */
				} else
					nntp_caps.type = NONE;
			}
			break;

		/*
		 * XanaNewz 2 Server Version 2.0.0.3 doesn't know CAPABILITIES
		 * but responds with 400 _without_ closing the connection. If
		 * you use tin on a XanaNewz 2 Server comment out the following
		 * case.
		 */
#	if 1
		case ERR_GOODBYE:
			ret = i;
			error_message(2, "%s", buf);
			break;
#	endif /* 1 */

		default:
			break;
	}

#	if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
#		if 0 /* likely to be dropped from the draft */
	if (nntp_caps.maxartnum == T_ARTNUM_CONST(0)) /* a value of 0 (zero) indicates that the server can handle article numbers of any size */
		nntp_caps.maxartnum = ARTNUM_MAX;
#		endif /* 0 */
	if (nntp_caps.maxartnum <= T_ARTNUM_CONST(2147483647)) /* RFC 3977 "Article numbers MUST lie between 1 and 2,147,483,647, inclusive." */
		nntp_caps.maxartnum = T_ARTNUM_CONST(0);
#	endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */

	return ret;
}


/*
 * Switch INN into NNRP mode with 'mode reader'
 */
static int
mode_reader(
	t_bool *sec)
{
	int ret = 0;

	if (!nntp_caps.reader) {
		char line[NNTP_STRLEN];
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "mode_reader(MODE READER)");
#	endif /* DEBUG */
		DEBUG_IO((stderr, "nntp_command(MODE READER)\n"));
		put_server("MODE READER");

		/*
		 * According to RFC 3977 (5.3), MODE READER may only return the
		 * following response codes:
		 *
		 *   200 (OK_CANPOST)     Hello, you can post
		 *   201 (OK_NOPOST)      Hello, you can't post
		 *   502 (ERR_ACCESS)     Service unavailable
		 *
		 * However, there are servers out there (e.g. Delegate 9.8.x) that do
		 * not implement this command and therefore return ERR_COMMAND (500).
		 * Unfortunately there are some new servers out there (i.e. INN 2.4.0
		 * (20020220 prerelease) which do return ERR_COMMAND if they are feed
		 * only servers.
		 */

		switch ((ret = get_respcode(line, sizeof(line)))) {
			case OK_CANPOST:
				can_post = TRUE && !force_no_post;
				*sec = TRUE;
				ret = 0;
				break;

			case OK_NOPOST:
				can_post = FALSE;
				*sec = TRUE;
				ret = 0;
				break;

			case ERR_GOODBYE:
			case ERR_ACCESS:
				error_message(2, "%s", line);
				return ret;

			case ERR_COMMAND:
#	if 1
				ret = 0;
				break;
#	endif /* 1 */

			default:
				break;
		}
	}
	return ret;
}
#endif /* NNTP_ABLE */


/*
 * Open a connection to the NNTP server. Authenticate if necessary or
 * desired, and test if the server supports XOVER.
 * Returns: 0	success
 *        > 0	NNTP error response code
 *        < 0	-errno from system call or similar error
 */
int
nntp_open(
	void)
{
#ifdef NNTP_ABLE
	char *linep;
	char line[NNTP_STRLEN] = { '\0' };
	int ret;
	t_bool sec = FALSE;
	/* It appears that is_reconnect guards code that should be run only once */
	static t_bool is_reconnect = FALSE;

	if (!read_news_via_nntp)
		return 0;

#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "nntp_open() BEGIN");
#	endif /* DEBUG */

	if (nntp_server == NULL) {
		error_message(2, _(txt_cannot_get_nntp_server_name));
		error_message(2, _(txt_server_name_in_file_env_var), NNTP_SERVER_FILE);
		return -EHOSTUNREACH;
	}

	if (!batch_mode || verbose) {
		if (nntp_tcp_port != IPPORT_NNTP)
			wait_message(0, _(txt_connecting_port), nntp_server, nntp_tcp_port);
		else
			wait_message(0, _(txt_connecting), nntp_server);
	}

	if ((!batch_mode || verbose) && use_nntps)
		my_fputc('\n', stdout);

#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "nntp_open() %s:%d", nntp_server, nntp_tcp_port);
#	endif /* DEBUG */

	ret = server_init(nntp_server, NNTP_TCP_NAME, nntp_tcp_port, line, sizeof(line));
	DEBUG_IO((stderr, "server_init returns %d,%s\n", ret, line));

	if ((!batch_mode || verbose) && ret >= 0 && !use_nntps)
		my_fputc('\n', stdout);

#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "nntp_open() %s", line);
#	endif /* DEBUG */

	switch (ret) {
		/*
		 * ret < 0 : some error from system call
		 * ret > 0 : NNTP response code
		 *
		 * According to the ietf-nntp mailinglist:
		 *   200 you may (try to) do anything
		 *   201 you may not POST
		 *   All unrecognised 200 series codes should be assumed as success.
		 *   All unrecognised 300 series codes should be assumed as notice to continue.
		 *   All unrecognised 400 series codes should be assumed as temporary error.
		 *   All unrecognised 500 series codes should be assumed as error.
		 */

		case OK_CANPOST:
			can_post = TRUE && !force_no_post;
			break;

		case OK_NOPOST:
			can_post = FALSE;
			break;

		default:
			if (ret >= 200 && ret <= 299) {
				can_post = TRUE && !force_no_post;
				break;
			}
			if (ret < 0)
				error_message(2, _(txt_failed_to_connect_to_server), nntp_server);
			else
				error_message(2, "%s", line);

			return ret;
	}
	if (!is_reconnect && *line) {
		/* remove leading whitespace and save server's initial response */
		linep = line;
		while (isspace((int) *linep))
			linep++;

		STRCPY(bug_nntpserver1, linep);
	}

#	if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
	nntp_caps.maxartnum = T_ARTNUM_CONST(-1);
#	endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */

	/*
	 * Find out which NNTP extensions are available
	 * - Typhoon v2.1.1.363 closes the connection after an unknown command
	 *   (i.e. CAPABILITIES) but as we are not allowed to cache CAPABILITIES
	 *   we reissue the command on reconnect. To prevent a loop we catch this
	 *   case.
	 *
	 * TODO: The authentication method required may be mentioned in the list
	 *       of extensions. (For details about authentication methods, see
	 *       RFC 4643).
	 */
	if (nntp_caps.type != BROKEN)
		check_extensions(0);

	/*
	 * If the user wants us to authenticate on connection startup, do it now.
	 * Some news servers return "201 no posting" first, but after successful
	 * authentication you get a "200 posting allowed". To find out if we are
	 * allowed to post after authentication issue a "MODE READER" again and
	 * interpret the response code.
	 */

	if (nntp_caps.type == CAPABILITIES && !nntp_caps.reader) {
		if (nntp_caps.mode_reader) {
			char buf[NNTP_STRLEN];

#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "nntp_open(MODE READER)");
#	endif /* DEBUG */
			put_server("MODE READER");
			switch (get_only_respcode(buf, sizeof(buf))) {
				/* just honor critical errors */
				case ERR_GOODBYE:
				case ERR_ACCESS:
					error_message(2, "%s", buf);
					return -1;

				default:
					break;
			}
			check_extensions(0);
		}
	}

	if (force_auth_on_conn_open) {
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "nntp_open(authenticate(force_auth_on_conn_open))");
#	endif /* DEBUG */

		if (!authenticate(nntp_server, userid, FALSE))	/* 3rd parameter is FALSE as we need to get prompted for username password here */
			return -1;
	}

	if ((nntp_caps.type == CAPABILITIES && nntp_caps.mode_reader) || nntp_caps.type != CAPABILITIES) {
		if ((ret = mode_reader(&sec))) {
			if (nntp_caps.type == CAPABILITIES)
				can_post = nntp_caps.post && !force_no_post;

			return ret;
		}
		if (nntp_caps.type == CAPABILITIES)
			check_extensions(0);
	}

	if (nntp_caps.type == CAPABILITIES) {
		if (!nntp_caps.reader) {
#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "CAPABILITIES did not announce READER");
#	endif /* DEBUG */
			error_message(2, _("CAPABILITIES did not announce READER")); /* TODO: -> lang.c */
			return -1; /* give up */
		}
		can_post = nntp_caps.post && !force_no_post;
	}

	if (!is_reconnect && *line) {
#	if 0
	/*
	 * gives wrong results if RFC 3977 server requests auth after
	 * CAPABILITIES is parsed (with no posting allowed) and after auth
	 * posting is allowed. as we will inform the user later on when he
	 * actually tries to post it should do no harm to skip this message
	 */
		/* Inform user if he cannot post */
		if (!can_post && !batch_mode)
			wait_message(0, "%s\n", _(txt_cannot_post));
#	endif /* 0 */

		/* Remove leading white space and save server's second response */
		linep = line;
		while (isspace((int) *linep))
			linep++;

		STRCPY(bug_nntpserver2, linep);

		/*
		 * Show user last server response line, do some nice formatting if
		 * response is longer than a screen wide.
		 *
		 * TODO: This only breaks the line once, but the response could be
		 * longer than two lines ...
		 */
		if (!batch_mode || verbose) {
			char *chr1, *chr2;
			int j;

			j = atoi(get_val("COLUMNS", "80"));
			chr1 = my_strdup((sec ? bug_nntpserver2 : bug_nntpserver1));

			if (j > MIN_COLUMNS_ON_TERMINAL && ((int) strlen(chr1)) >= j) {
				chr2 = chr1 + strlen(chr1) - 1;
				while (chr2 - chr1 >= j)
					chr2--;
				while (chr2 > chr1 && *chr2 != ' ')
					chr2--;
				if (chr2 != chr1)
					*chr2 = '\n';
			}

			wait_message(0, "%s\n", chr1);
			free(chr1);
		}
	}

	/*
	 * If CAPABILITIES failed, check if NNTP supports XOVER or OVER command
	 * We have to check that we _don't_ get an ERR_COMMAND
	 *
	 * TODO: this should be done when the command is first used
	 */
	if (nntp_caps.type != CAPABILITIES) {
		int i, j = 0;

		for (i = 0; i < 2 && j >= 0; i++) {
			j = new_nntp_command(&xover_cmds[i], ERR_NCING, line, sizeof(line));
			switch (j) {
				case ERR_COMMAND:
					break;

				case OK_XOVER:	/* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
					nntp_caps.over_cmd = &xover_cmds[i];
#	ifdef DEBUG
					if ((debug & DEBUG_NNTP) && verbose > 1)
						debug_print_file("NNTP", "nntp_open() %s skipping data", &xover_cmds[i]);
#	endif /* DEBUG */
					while (tin_fgets(FAKE_NNTP_FP, FALSE))
						;
					j = -1;
					break;

				default:
					nntp_caps.over_cmd = &xover_cmds[i];
					j = -1;
					break;
			}
		}
		for (i = 0, j = 0; i < 2 && j >= 0; i++) {
			j = new_nntp_command(&xhdr_cmds[i], ERR_CMDSYN, line, sizeof(line));
			switch (j) {
				case ERR_COMMAND:
					break;

				case 221:	/* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
					nntp_caps.hdr_cmd = &xhdr_cmds[i];
#	ifdef DEBUG
					if ((debug & DEBUG_NNTP) && verbose > 1)
						debug_print_file("NNTP", "nntp_open() %s skipping data", &xhdr_cmds[i]);
#	endif /* DEBUG */
					while (tin_fgets(FAKE_NNTP_FP, FALSE))
						;
					j = -1;
					break;

				default:	/* usually ERR_CMDSYN (args missing), Typhoon/Twister sends ERR_NCING */
					nntp_caps.hdr_cmd = &xhdr_cmds[i];
					j = -1;
					break;
			}
		}
		/* no XPAT probing here, we do when it's needed */
		nntp_caps.xpat = TRUE;
#	if 0
		switch (new_nntp_command("XPAT Newsgroups <0> *", ERR_NOART, line, sizeof(line))) {
			case ERR_NOART:
				nntp_caps.xpat = TRUE;
				break;

			default:
				break;
		}
#	endif /* 0 */
	} else {
		if (!nntp_caps.over_cmd) {
			/*
			 * CAPABILITIES didn't mention OVER or XOVER, try XOVER
			 */
			switch (new_nntp_command(xover_cmds, ERR_NCING, line, sizeof(line))) {
				case ERR_COMMAND:
					break;

				case OK_XOVER:	/* unexpected multiline ok, e.g.: Synchronet 3.13 NNTP Service 1.92 or on reconnect if last cmd was GROUP */
					nntp_caps.over_cmd = xover_cmds;
#	ifdef DEBUG
					if ((debug & DEBUG_NNTP) && verbose > 1)
						debug_print_file("NNTP", "nntp_open() %s skipping data", xover_cmds);
#	endif /* DEBUG */
					while (tin_fgets(FAKE_NNTP_FP, FALSE))
						;
					break;

				default:
					nntp_caps.over_cmd = xover_cmds;
					break;
			}
		}
		if (!nntp_caps.hdr_cmd) {
			/*
			 * CAPABILITIES didn't mention HDR or XHDR, try XHDR
			 */
			switch (new_nntp_command(xhdr_cmds, ERR_NCING, line, sizeof(line))) {
				case ERR_COMMAND:
					break;

				case 221:	/* unexpected multiline ok, e.g.: SoftVelocity Discussions 2.5q */
					nntp_caps.hdr_cmd = xhdr_cmds;
#	ifdef DEBUG
					if ((debug & DEBUG_NNTP) && verbose > 1)
						debug_print_file("NNTP", "nntp_open() %s skipping data", xhdr_cmds);
#	endif /* DEBUG */
					while (tin_fgets(FAKE_NNTP_FP, FALSE))
						;
					break;

				default:	/* ERR_NCING or ERR_CMDSYN */
					nntp_caps.hdr_cmd = xhdr_cmds;
					break;
			}
		}
	}

	if (!nntp_caps.over_cmd) {
		if (!is_reconnect && !batch_mode) {
			wait_message(2, _(txt_no_xover_support));

			if (tinrc.cache_overview_files)
				wait_message(2, _(txt_caching_on));
			else
				wait_message(2, _(txt_caching_off));
		}
	}
#	if 0
	else {
		/*
		 * TODO: issue warning if old index files found?
		 *	      in index_newsdir?
		 */
	}
#	endif /* 0 */

#	if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
	set_maxartnum(is_reconnect);
#	endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */

	if (!is_reconnect && !batch_mode && show_description && check_for_new_newsgroups) {
		/*
		 * TODO:
		 * - add a tinrc var to turn LIST MOTD on/off?
		 *   (currently done automatically for -d, -q and -Q)
		 */
		if (nntp_caps.list_motd)
			list_motd();
	}

	is_reconnect = TRUE;
#endif /* NNTP_ABLE */

	DEBUG_IO((stderr, "nntp_open okay\n"));
	return 0;
}


/*
 * 'Public' function to shutdown the NNTP connection
 */
void
nntp_close(
	t_bool send_no_quit)
{
#ifdef NNTP_ABLE
	if (read_news_via_nntp && !read_saved_news) {
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "nntp_close(%s) END", bool_unparse(send_no_quit));
#	endif /* DEBUG */
		close_server(send_no_quit);
	}
#endif /* NNTP_ABLE */
}


#ifdef NNTP_ABLE
/*
 * Get a response code from the server.
 * Returns:
 *	+ve NNTP return code
 *	-1  on an error or user abort. We don't differentiate.
 * If 'message' is not NULL, then any trailing text after the response
 * code is copied into it.
 * Does not perform authentication if required; use get_respcode()
 * instead.
 */
int
get_only_respcode(
	char *message,
	size_t mlen)
{
	int respcode;
	char *end, *ptr;

	ptr = tin_fgets(FAKE_NNTP_FP, FALSE);

	if (tin_errno || ptr == NULL) {
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "Error: tin_error<>0 or ptr==NULL in get_only_respcode()");
#	endif /* DEBUG */
		return -1;
	}

#	ifdef DEBUG
	if (debug & DEBUG_NNTP)
		debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
#	endif /* DEBUG */
	respcode = (int) strtol(ptr, &end, 10);
	if (end == ptr) /* no leading numbers in response */
		respcode = -1;
	DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));

	/*
	 * we also reconnect on ERR_FAULT if last_put was ARTICLE or LIST or POST
	 * as inn (2.2.3) sends ERR_FAULT on timeout
	 *
	 * what about other LIST cmds? (ACTIVE|COUNTS|OVERVIEW.FMT|...)
	 */
	if (last_put[0] != '\0' && ((respcode == ERR_FAULT && (!strncmp(last_put, "ARTICLE", 7) || !strcmp(last_put, "POST") || !strcmp(last_put, "LIST"))) || respcode == ERR_GOODBYE || respcode == OK_GOODBYE) && strcmp(last_put, "QUIT")) {
		/*
		 * Maybe server timed out.
		 * If so, retrying will force a reconnect.
		 */
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "get_only_respcode() timeout");
#	endif /* DEBUG */
		put_server(last_put);
		ptr = tin_fgets(FAKE_NNTP_FP, FALSE);

		if (tin_errno || ptr == NULL) {
#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "Error: tin_errno<>0 or ptr==NULL in get_only_respcode(retry)");
#	endif /* DEBUG */
			return -1;
		}

#	ifdef DEBUG
		if (debug & DEBUG_NNTP)
			debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
#	endif /* DEBUG */
		respcode = (int) strtol(ptr, &end, 10);
		if (end == ptr) /* no leading numbers in response */
			respcode = -1;
		DEBUG_IO((stderr, "get_only_respcode(%d)\n", respcode));
	}
	if (message != NULL && mlen > 1 && *end != '\0')		/* Pass out the rest of the text */
		my_strncpy(message, ++end, mlen - 1);

	return respcode;
}


/*
 * Get a response code from the server.
 * Returns:
 *	+ve NNTP return code
 *	-1  on an error
 * If 'message' is not NULL, then any trailing text after the response
 *	code is copied into it.
 * Performs authentication if required and repeats the last command if
 * necessary after a timeout.
 *
 * TODO: make this handle 401 and 483 (RFC 3977) return codes.
 *       as 401 requires the examination of the returned text besides the
 *       return value, we have to "fix" all nntp_command(..., NULL, 0) and
 *       get_only_respcode(NULL, 0) calls to do this properly.
 */
int
get_respcode(
	char *message,
	size_t mlen)
{
	int respcode;
	char savebuf[NNTP_STRLEN];
	char *ptr, *end;

	respcode = get_only_respcode(message, mlen);
	if ((respcode == ERR_NOAUTH) || (respcode == NEED_AUTHINFO)) {
		/*
		 * Server requires authentication.
		 */
#	ifdef DEBUG
		if ((debug & DEBUG_NNTP) && verbose > 1)
			debug_print_file("NNTP", "get_respcode() authentication");
#	endif /* DEBUG */
		STRCPY(savebuf, last_put);

		if (!authenticate(nntp_server, userid, FALSE))
			tin_done(EXIT_FAILURE, _(txt_auth_failed), nntp_caps.type == CAPABILITIES ? ERR_AUTHFAIL : ERR_ACCESS);

		if (nntp_caps.type == CAPABILITIES)
			can_post = nntp_caps.post && !force_no_post;
		else {
			put_server("MODE READER");
			if (get_only_respcode(message, mlen) == OK_CANPOST)
				can_post = TRUE && !force_no_post;
		}
		if (curr_group != NULL) {
			DEBUG_IO((stderr, _("Rejoin current group\n")));
			snprintf(last_put, sizeof(last_put), "GROUP %s", curr_group->name);
			put_server(last_put);
			if (nntpbuf_gets(last_put, NNTP_STRLEN, &nntp_buf) == NULL)
				*last_put = '\0';
#	ifdef DEBUG
			if (debug & DEBUG_NNTP)
				debug_print_file("NNTP", "<<<%s%s", logtime(), last_put);
#	endif /* DEBUG */
			DEBUG_IO((stderr, _("Read (%s)\n"), last_put));
		}
		STRCPY(last_put, savebuf);

		put_server(last_put);
		ptr = tin_fgets(FAKE_NNTP_FP, FALSE);

		if (tin_errno) {
#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "Error: tin_errno <> 0");
#	endif /* DEBUG */
			return -1;
		}

#	ifdef DEBUG
		if (debug & DEBUG_NNTP)
			debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
#	endif /* DEBUG */
		if (ptr == NULL)
			return -1;

		respcode = (int) strtol(ptr, &end, 10);
		if (end == ptr)	/* no leading numbers in response */
			return -1;

		if (message != NULL && mlen > 1)				/* Pass out the rest of the text */
			strncpy(message, end, mlen - 1);
	}
	return respcode;
}


/*
 * Do an NNTP command. Send command to server, and read the reply.
 * If the reply code matches success, then return an open file stream
 * Return NULL if we did not see the response we wanted.
 * If message is not NULL, then the trailing text of the reply string is
 * copied into it for the caller to process.
 */
FILE *
nntp_command(
	const char *command,
	int success,
	char *message,
	size_t mlen)
{
DEBUG_IO((stderr, "nntp_command(%s)\n", command));
#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "nntp_command(%s)", command);
#	endif /* DEBUG */
	put_server(command);

	if (!bool_equal(dangerous_signal_exit, TRUE)) {
		if (get_respcode(message, mlen) != success) {
#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "nntp_command(%s) NOT_OK", command);
#	endif /* DEBUG */
			/* error_message(2, "%s", message); */
			return (FILE *) 0;
		}
	}
#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "nntp_command(%s) OK", command);
#	endif /* DEBUG */
	return FAKE_NNTP_FP;
}


/*
 * same as above, but with a slightly more useful return code.
 * TODO: use it instead of nntp_command in the rest of the code
 *       (wherever it is more useful).
 */
int
new_nntp_command(
	const char *command,
	int success,
	char *message,
	size_t mlen)
{
	int respcode = 0;

DEBUG_IO((stderr, "new_nntp_command(%s)\n", command));
#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "new_nntp_command(%s)", command);
#	endif /* DEBUG */
	put_server(command);

	if (!bool_equal(dangerous_signal_exit, TRUE)) {
		if ((respcode = get_respcode(message, mlen)) != success) {
#	ifdef DEBUG
			if ((debug & DEBUG_NNTP) && verbose > 1)
				debug_print_file("NNTP", "new_nntp_command(%s) NOT_OK - Expected: %d, got: %d", command, success, respcode);
#	endif /* DEBUG */
			return respcode;
		}
	}
#	ifdef DEBUG
	if ((debug & DEBUG_NNTP) && verbose > 1)
		debug_print_file("NNTP", "new_nntp_command(%s) OK", command);
#	endif /* DEBUG */
	return respcode;
}


static void
list_motd(
	void)
{
	char *ptr;
	char *p;
	char buf[NNTP_STRLEN];
	int i;
	size_t len;
	unsigned int l = 0;

	buf[0] = '\0';
	i = new_nntp_command("LIST MOTD", OK_MOTD, buf, sizeof(buf));

	switch (i) {
		case OK_MOTD:
#	ifdef HAVE_COLOR
			fcol(tinrc.col_message);
#	endif /* HAVE_COLOR */
			while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
#	ifdef DEBUG
				if (debug & DEBUG_NNTP)
					debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
#	endif /* DEBUG */
				/*
				 * RFC 6048 2.5.2 "The information MUST be in UTF-8"
				 *
				 * TODO: - store a hash value of the entire motd in the server-rc
				 *         and only if it differs from the old value display the
				 *         motd?
				 *       - use some sort of pager?
				 *       - -> lang.c
				 */
				p = my_strdup(ptr);
				len = strlen(p);
				process_charsets(&p, &len, "UTF-8", tinrc.mm_local_charset, FALSE);
				my_printf("%s%s\n", _("MOTD: "), p);
				free(p);
				l++;
			}
#	ifdef HAVE_COLOR
			fcol(tinrc.col_normal);
#	endif /* HAVE_COLOR */
			if (l) {
				my_flush();
				sleep((l >> 1) | 0x01);
			}
			break;

		default:	/* common response codes are 500, 501, 503 */
			break;
	}
}

#define SZ(a) sizeof((a))

/*
 * write data from write buffer to NNTP connection when requested
 */
static int
nntpbuf_flush(
	struct nntpbuf* buf)
{
	if (!buf)
		return EOF;

	while (buf->wr.ub > buf->wr.lb) {
		ssize_t bytes_written;

#ifdef NNTPS_ABLE
		if (buf->tls_ctx)
			bytes_written = tintls_write(buf->tls_ctx, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
		else
			bytes_written = write(buf->fd, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
#else
		bytes_written = write(buf->fd, buf->wr.buf + buf->wr.lb, buf->wr.ub - buf->wr.lb);
#endif /* NNTPS_ABLE */

		if (bytes_written < 0)
			return EOF;

		buf->wr.lb += bytes_written;
	}

	buf->wr.ub = buf->wr.lb = 0;

	return 0;
}


/*
 * fputs(3) replacement using the NNTP connection
 */
static int
nntpbuf_puts(
	const char* data,
	struct nntpbuf* buf)
{
	int bytes_written = 0;
	unsigned len, l;

	if (!buf || SZ(buf->wr.buf) == 0 || buf->wr.lb > buf->wr.ub)
		return EOF;

	if (!data)
		return 0;

	len = strlen(data);
	while (len) {
		if (buf->wr.ub == SZ(buf->wr.buf)) {
			int retval = nntpbuf_flush(buf);

			if (retval != 0)
				return retval;
		}

		l = len;
		if (l > SZ(buf->wr.buf) - buf->wr.ub)
			l = SZ(buf->wr.buf) - buf->wr.ub;

		memcpy(buf->wr.buf + buf->wr.ub, data, l);
		buf->wr.ub += l;
		data += l;
		bytes_written += l;
		len -= l;
	}

	return bytes_written;
}


/*
 * internal helper to refill the read buffer using the NNTP connection
 */
static int
nntpbuf_refill(
	struct nntpbuf *buf)
{
	unsigned free_b;

	if (buf->rd.ub == buf->rd.lb)
		buf->rd.ub = buf->rd.lb = 0;

	free_b = SZ(buf->rd.buf) - buf->rd.ub;
	if (free_b) {
		ssize_t bytes_read;

#ifdef NNTPS_ABLE
		if (buf->tls_ctx)
			bytes_read = tintls_read(buf->tls_ctx, buf->rd.buf + buf->rd.ub, free_b);
		else
			bytes_read = read(buf->fd, buf->rd.buf + buf->rd.ub, free_b);
#else
		bytes_read = read(buf->fd, buf->rd.buf + buf->rd.ub, free_b);
#endif /* NNTPS_ABLE */

		if (bytes_read > 0)
			buf->rd.ub += bytes_read;

		return (int) bytes_read;
	}
	return 0;
}


/*
 * fgetc(3) replacement using the NNTP connection
 */
static int
nntpbuf_getc(
	struct nntpbuf *buf)
{
	int c = EOF;

	if (buf->rd.ub - buf->rd.lb == 0) {
		int retval = nntpbuf_refill(buf);

		if (retval <= 0)
			return retval;
	}

	c = buf->rd.buf[buf->rd.lb];
	buf->rd.lb += 1;

	return c;
}


/*
 * ungetc(3) replacement using the NNTP connection
 */
static int
nntpbuf_ungetc(
	int c,
	struct nntpbuf *buf)
{
	if (buf->rd.lb == 0) {
		if (buf->rd.ub == SZ(buf->rd.buf)) {
			errno = ENOSPC;
			return EOF;
		}
		memmove(buf->rd.buf+1, buf->rd.buf, buf->rd.ub);
		buf->rd.lb += 1;
	}

	buf->rd.lb -= 1;
	buf->rd.buf[buf->rd.lb] = (unsigned char)c;

	return c;
}


/*
 * fgets(3) replacement using the NNTP connection
 */
static char *
nntpbuf_gets(
	char *s,
	int size,
	struct nntpbuf *buf)
{
	int write_at = 0;

	if (s == NULL || size == 0)
		return s;

	s[size - 1] = '\0';
	size -= 1;

	while (size) {
		if (buf->rd.ub - buf->rd.lb == 0) {
			int retval = nntpbuf_refill(buf);

			if (retval <= 0)
				return NULL;
		}

		while (size && (buf->rd.ub - buf->rd.lb) > 0) {
			s[write_at++] = buf->rd.buf[buf->rd.lb++];
			size -= 1;

			if (s[write_at - 1] == '\n' && size) {
				s[write_at] = '\0';
				goto out;
			}
		}
	}

out:
	return s;
}


static void
nntpbuf_close(
	struct nntpbuf *buf)
{
	if (!buf)
		return;

#ifdef NNTPS_ABLE
	if (buf->tls_ctx) {
		int result = tintls_close(buf->tls_ctx);

		if (result != 0) {
			/* warn? */
		}
	}
	buf->tls_ctx = NULL;
#endif /* NNTPS_ABLE */

	if (buf->fd >= 0)
		close(buf->fd);

	buf->fd = -1;

	buf->rd.lb = buf->rd.ub = 0;
	buf->wr.lb = buf->wr.ub = 0;
}


static int
nntpbuf_is_open(
	struct nntpbuf *buf)
{
	return buf->fd != -1;
}

#undef SZ

int
nntp_conninfo(
	FILE *stream)
{
	int retval = 0;

	fprintf(stream, "\nConnection details:\n");
	fprintf(stream, "-------------------\n");
	fprintf(stream, "NNTPSERVER    : %s\n", nntp_server);
	fprintf(stream, "NNTPPORT      : %d\n", nntp_tcp_port);
	if (nntp_caps.type == CAPABILITIES) {
		if (*nntp_caps.implementation)
			fprintf(stream, "IMPLEMENTATION: %s\n", nntp_caps.implementation);
#if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
		if (nntp_caps.maxartnum)
			fprintf(stream, "MAXARTNUM     : %"T_ARTNUM_PFMT"\n", nntp_caps.maxartnum);
#endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
	}

#ifdef NNTPS_ABLE
	if (nntp_buf.tls_ctx)
		retval = tintls_conninfo(nntp_buf.tls_ctx, stream);

#endif /* NNTPS_ABLE */

	return retval;
}


#	if defined(MAXARTNUM) && defined(USE_LONG_ARTICLE_NUMBERS)
/*
 * MAXARTNUM - <tnqm14$35bas$1@news.trigofacile.com>
 */
void
set_maxartnum(
	t_bool reconnect)
{
	char cmd[NNTP_STRLEN];
	char line[NNTP_STRLEN] = { '\0' };
	static int cnt = 0;

	if (nntp_caps.maxartnum < T_ARTNUM_CONST(2147483647) || (!reconnect && cnt))
		return;
	else
		cnt++;

	snprintf(cmd, sizeof(cmd), "MAXARTNUM %"T_ARTNUM_PFMT, nntp_caps.maxartnum);

	switch (new_nntp_command(cmd, OK_EXTENSIONS, line, sizeof(line))) {
		case OK_EXTENSIONS:
			nntp_caps.mode_reader = FALSE;
			nntp_caps.reader = TRUE;
			nntp_caps.list_newsgroups = TRUE;
			nntp_caps.list_active = TRUE;
			break;

		default:
			nntp_caps.maxartnum = T_ARTNUM_CONST(0);
			break;
	}
}
#	endif /* MAXARTNUM && USE_LONG_ARTICLE_NUMBERS */
#endif /* NNTP_ABLE */
