anntp

a nntp implementation in pure C99
Log | Files | Refs | README | LICENSE

anntp.h (21628B)


      1 /**
      2  ** anntp v0.1.0 - public-domain nntp client implementation in C - by Mario Rosell an contributors
      3  **                THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
      4  **                    use at your own risk.
      5  ** 	#include ...
      6  **
      7  ** Add:
      8  ** 	#define ANNTP_IMPLEMENTATION
      9  ** before including anntp on exactly ONE C or C++ file to generate all implementation code. Else you will get
     10  ** ``undefined reference to ...'' erorrs :).
     11  **
     12  ** It should look something like:
     13  **
     14  ** 	#include ...
     15  ** 	#include ...
     16  ** 	#define ANNTP_IMPLEMENTATION
     17  ** 	[extra config here]
     18  **     #include "anntp.h"
     19  **
     20  ** You can define ANNTP_BUFSIZE to set the buffer size for networking, ANNTP_MALLOC and ANNTP_FREE for the memory
     21  ** allocator and free function, respectively, and ANNTP_API to add a declaration specifier for all API functions.
     22  **
     23  ** You can also define ANNTP_STRDUP to set a custom (perhaps portable) string duplication function.
     24  **
     25  ** NOTES
     26  ** - This library is slow (although work is being done to make it better).
     27  ** - This library has and will always have a limited API by design.
     28  ** - This library is in development, everything can change fast.
     29  ** - This library doesn't work in Windows, and I doubt it will be easy to port...
     30  ** + This library is small, relatively readable to be networked C, and hackable.
     31  ** + This library is minimalist.
     32  ** + This library is very easy to use.
     33  ** + This library has good error handling.
     34  ** + This library uses idiomatic C.
     35  ** + This library exists, which makes it one of the first exclusively-NNTP libraries.
     36  ** + Technically, this library is capable of doing everything (although it can be tedious!).
     37  **
     38  ** The model is based on connections, which are basically client-server, command-response abstractions. The rest is
     39  ** pretty straight forward.
     40  **
     41  ** LICENSE
     42  **   See the ./LICENSE file on the distribution (or click the LICENSE link on the Git viewer) to see copyright info.
     43  **   Exclusively CC0, so cool :).
     44  **
     45  ** CONTRIBUTORS
     46  ** - Mario Rosell <mario@mariorosell.es>	Most things
     47  **
     48  **/
     49 
     50 #ifndef ANNTP_H
     51 #define ANNTP_H
     52 
     53 #include <arpa/inet.h>   /* for inet_ntoa, inet_ntop */
     54 #include <errno.h>       /* for errno */
     55 #include <netdb.h>       /* for gethostbyname, struct hostent */
     56 #include <netinet/in.h>  /* for struct in_addr */
     57 #include <stddef.h>      /* for size_t */
     58 #include <stdio.h>       /* for fprintf, perror, stderr */
     59 #include <stdlib.h>      /* for malloc, free */
     60 #include <string.h>      /* for memset, memcpy, strdup */
     61 #include <sys/socket.h>  /* for socket, connect */
     62 #include <unistd.h>      /* for close */
     63 #include <fcntl.h>       /* for fcntl */
     64 
     65 #ifdef ANNTP_TLS
     66 #include <openssl/ssl.h>
     67 #include <openssl/err.h>
     68 #endif
     69 
     70 /*** config ***/
     71 
     72 #ifdef _WIN32
     73 #       error This project uses the Anntp library, which is not compatible with Windows, sorry :(
     74 #endif
     75 
     76 #ifndef ANNTP_MALLOC
     77 #       define ANNTP_MALLOC malloc
     78 #endif
     79 
     80 #ifndef ANNTP_FREE
     81 #       define ANNTP_FREE free
     82 #endif
     83 
     84 #ifndef ANNTP_BUFSIZE
     85 #       define ANNTP_BUFSIZE 0x1000
     86 #endif
     87 
     88 #ifndef ANNTP_API
     89 #       define ANNTP_API extern
     90 #endif
     91 
     92 #ifndef ANNTP_PRINTF
     93 #	define ANNTP_PRINTF printf
     94 #endif
     95 
     96 #ifndef ANNTP_STRDUP
     97 #	if (defined(__GNUC__) && defined(_GNU_SOURCE)) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L)
     98 #		define ANNTP_STRDUP strdup
     99 #	else
    100 #		define ANNTP_STRDUP anntp__NC__strdup
    101 static char*
    102 anntp__NC__strdup(const char* s) {
    103 	size_t len = strlen(s) + 1;
    104 	char* p = (char*)malloc(len);
    105 	if (p) memcpy(p, s, len);
    106 	return p;
    107 }
    108 #	endif
    109 #endif
    110 
    111 /*** defs ***/
    112 
    113 #define Bool char
    114 #define true 1
    115 #define false 0
    116 #define ANNTPE(e) (-(ssize_t)(e))
    117 #define ANNTP_ISERR(x) ((x) < 0)
    118 #define ANNTP_CODE(x) ((AnntpErrCode)(-(x)))
    119 
    120 typedef unsigned char uchar_t;
    121 typedef long AnntpArticleNumber;
    122 
    123 enum anntp_state {
    124 	ANS_OFF,
    125 	ANS_CONNECTING,
    126 	ANS_READY,
    127 	ANS_ERROR,
    128 };
    129 
    130 /* NOTE: often given as negative for functions returning ssize_t */
    131 enum anntp_errcode {
    132 	ANE_OK = 0,
    133 	ANE_PARAMS,
    134 	ANE_TLS,
    135 	ANE_IO,
    136 	ANE_PROTO,
    137 	ANE_AUTH,
    138 };
    139 
    140 typedef enum anntp_state AnntpState;
    141 typedef enum anntp_errcode AnntpErrCode;
    142 
    143 struct anntp_connection {
    144 	int             fd;           /* socket file descriptor */
    145 	AnntpState      state;        /* current connection state */
    146 	Bool            use_tls;      /* whether to use TLS */
    147 	char*           host;         /* hostname string */
    148 	struct hostent* host_he;      /* resolved host entry */
    149 	struct in_addr  addr;         /* resolved IP address */
    150 	uchar_t         read_buffer[ANNTP_BUFSIZE]; /* read buffer */
    151 	size_t          read_pos;     /* used for buffering */
    152 	size_t          read_len;     /* length of data in buffer */
    153 #ifdef ANNTP_TLS
    154 	SSL*            ssl;          /* TLS session */
    155 #endif
    156 };
    157 
    158 struct anntp_group {
    159 	char* name;
    160 	AnntpArticleNumber first;
    161 	AnntpArticleNumber last;
    162 	char mode; /* y for posting allowed, n for no posting allowed, m for moderated */
    163 };
    164 
    165 struct anntp_article {
    166 	AnntpArticleNumber n;
    167 	char*              id;
    168 	char*              from;
    169 	char*              date;
    170 	char*              body;
    171 };
    172 
    173 struct anntp_overview {
    174 	char** lines;
    175 	size_t count;
    176 };
    177 
    178 typedef struct anntp_connection AnntpConnection;
    179 typedef struct anntp_article AnntpArticle;
    180 typedef struct anntp_overview AnntpOverview;
    181 typedef struct anntp_group AnntpGroup;
    182 
    183 typedef int (*AnntpLineCb)(const char* line, void* extra);
    184 
    185 /*** function declarations ***/
    186 
    187 ANNTP_API void             anntp_init(void);
    188 ANNTP_API AnntpConnection* anntp_mkconn(const char* host, const char* port, Bool tls);
    189 ANNTP_API void             anntp_freeconn(AnntpConnection* conn);
    190 ANNTP_API ssize_t          anntp_read(AnntpConnection* conn, uchar_t* buf, size_t bufsize);
    191 ANNTP_API ssize_t          anntp_write(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
    192 ANNTP_API int              anntp_group(AnntpConnection* conn, const char* group, AnntpGroup* out_group);
    193 ANNTP_API ssize_t          anntp_article(AnntpConnection*, size_t num, AnntpArticle* out_art);
    194 ANNTP_API void             anntp_article_free(AnntpArticle* art);
    195 ANNTP_API int              anntp_article_cb(AnntpConnection* cv, size_t n, AnntpLineCb cb, void* extra);
    196 ANNTP_API void             anntp_overview_free(AnntpOverview* ov);
    197 ANNTP_API ssize_t          anntp_overview(AnntpConnection* cv, size_t start, size_t end, AnntpOverview* ov);
    198 ANNTP_API int              anntp_overview_cb(AnntpConnection*, size_t start, size_t end, AnntpLineCb, void*);
    199 ANNTP_API ssize_t          anntp_write_all(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
    200 ANNTP_API ssize_t          anntp_writeline(AnntpConnection* conn, const char* buf);
    201 ANNTP_API ssize_t          anntp_readline(AnntpConnection* conn, char* buf, size_t maxlen);
    202 ANNTP_API ssize_t          anntp_readdot(AnntpConnection* conn, char* buf, size_t maxlen);
    203 ANNTP_API int              anntp_readdot_cb(AnntpConnection* conn, AnntpLineCb cb, void* extra);
    204 ANNTP_API int              anntp_auth(AnntpConnection* conn, const char* login, const char* password);
    205 ANNTP_API char*            anntp_strerror(AnntpErrCode err);
    206  
    207 #endif /* ANNTP_H */
    208 
    209 #ifdef ANNTP_IMPLEMENTATION
    210 
    211 #ifndef ANNTP_IMPLEMENTATION_ONCE
    212 #define ANNTP_IMPLEMENTATION_ONCE
    213 
    214 static ssize_t _afillbuf(AnntpConnection* c);
    215 
    216 void
    217 anntp_init(void)
    218 {
    219 #ifdef ANNTP_TLS
    220 	SSL_load_error_strings();
    221 	SSL_library_init();
    222 	OpenSSL_add_ssl_algorithms();
    223 #endif
    224 }
    225 
    226 /* create connection */
    227 AnntpConnection*
    228 anntp_mkconn(const char* host, const char* port, Bool tls)
    229 {
    230 	AnntpConnection* cv = (AnntpConnection*)ANNTP_MALLOC(sizeof(AnntpConnection));
    231 	if (!cv) return NULL;
    232 
    233 	memset(cv, 0, sizeof(*cv));
    234 	cv->host = ANNTP_STRDUP(host);
    235 	cv->use_tls = tls;
    236 	cv->state = ANS_OFF;
    237 	cv->fd = -1;
    238 
    239 	/* create socket */
    240 	cv->fd = socket(AF_INET, SOCK_STREAM, 0);
    241 	if (cv->fd < 0) {
    242 		ANNTP_PRINTF("anntp - making socket: %s\n", strerror(errno));
    243 		goto cleanup;
    244 	}
    245 
    246 	/* make socket blocking for greeting */
    247 	int flags = fcntl(cv->fd, F_GETFL, 0);
    248 	flags &= ~O_NONBLOCK;
    249 	fcntl(cv->fd, F_SETFL, flags);
    250 
    251 	/* resolve host */
    252 	struct addrinfo hints, *res = NULL;
    253 	memset(&hints, 0, sizeof(hints));
    254 	hints.ai_family = AF_INET;
    255 	hints.ai_socktype = SOCK_STREAM;
    256 
    257 	int err = getaddrinfo(host, port, &hints, &res);
    258 	if (err != 0 || !res) {
    259 		ANNTP_PRINTF("anntp - cant resolve host `%s': %s\n", host, gai_strerror(err));
    260 		goto cleanup;
    261 	}
    262 
    263 	struct sockaddr_in* sa = (struct sockaddr_in*)res->ai_addr;
    264 	cv->addr = sa->sin_addr;
    265 
    266 	if (connect(cv->fd, (struct sockaddr*)sa, sizeof(*sa)) < 0) {
    267 		ANNTP_PRINTF("anntp - connecting: %s\n", strerror(errno));
    268 		goto cleanup_addr;
    269 	}
    270 
    271 	cv->state = ANS_CONNECTING;
    272 
    273 #ifdef ANNTP_TLS
    274 	if (tls) {
    275 		SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
    276 		if (!ctx) goto cleanup_addr;
    277 
    278 		cv->ssl = SSL_new(ctx);
    279 		if (!cv->ssl) {
    280 			SSL_CTX_free(ctx); goto cleanup_addr;
    281 		}
    282 
    283 		SSL_set_fd(cv->ssl, cv->fd);
    284 		if (SSL_connect(cv->ssl) <= 0) {
    285 			SSL_free(cv->ssl);
    286 			cv->ssl = NULL;
    287 			SSL_CTX_free(ctx);
    288 			goto cleanup_addr;
    289 		}
    290 
    291 		SSL_CTX_free(ctx);
    292 	}
    293 
    294 #endif
    295 
    296 	cv->state = ANS_READY;
    297 
    298 	freeaddrinfo(res);
    299 	return cv;
    300 
    301 cleanup_addr:
    302 	freeaddrinfo(res);
    303 cleanup:
    304 	if (cv->host) ANNTP_FREE(cv->host);
    305 	if (cv->fd >= 0) close(cv->fd);
    306 	ANNTP_FREE(cv);
    307 	return NULL;
    308 }
    309 
    310 ssize_t
    311 anntp_overview(AnntpConnection* cv, size_t start, size_t end, AnntpOverview* ov)
    312 {
    313 	if (!cv || !ov || start > end)
    314 		return ANNTPE(ANE_PARAMS);
    315 
    316 	char cmd[ANNTP_BUFSIZE];
    317 	char line[ANNTP_BUFSIZE];
    318 
    319 	memset(ov, 0, sizeof(*ov));
    320 
    321 	snprintf(cmd, sizeof(cmd), "XOVER %zu-%zu", start, end);
    322 
    323 	if (anntp_writeline(cv, cmd) <= 0)
    324 		return ANNTPE(ANE_IO);
    325 
    326 	ssize_t r = anntp_readline(cv, line, sizeof(line));
    327 	if (r <= 0)
    328 		return ANNTPE(ANE_IO);
    329 
    330 	/* expect 224 */
    331 	if (strncmp(line, "224", 3) != 0)
    332 		return ANNTPE(ANE_PROTO);
    333 
    334 	size_t cap = 16;
    335 	ov->lines = (char**)ANNTP_MALLOC(sizeof(char*) * cap);
    336 	if (!ov->lines)
    337 		return ANNTPE(ANE_IO);
    338 
    339 	size_t count = 0;
    340 
    341 	for (;;) {
    342 		r = anntp_readline(cv, line, sizeof(line));
    343 		if (r <= 0)
    344 			goto error;
    345 
    346 		/* end */
    347 		if (strcmp(line, ".\r\n") == 0)
    348 			break;
    349 
    350 		/* grow */
    351 		if (count >= cap) {
    352 			size_t ncap = cap * 2;
    353 			char** tmp = (char**)realloc(ov->lines, sizeof(char*) * ncap);
    354 			if (!tmp)
    355 				goto error;
    356 
    357 			ov->lines = tmp;
    358 			cap = ncap;
    359 		}
    360 
    361 		/* handle dot-stuffing */
    362 		char* out = line;
    363 		if (out[0] == '.' && out[1] == '.')
    364 			out++;
    365 
    366 		/* strip trailing newline */
    367 		size_t len = strlen(out);
    368 		while (len && (out[len-1] == '\n' || out[len-1] == '\r'))
    369 			out[--len] = '\0';
    370 
    371 		ov->lines[count] = ANNTP_STRDUP(out);
    372 		if (!ov->lines[count])
    373 			goto error;
    374 
    375 		count++;
    376 	}
    377 
    378 	ov->count = count;
    379 	return (ssize_t)count;
    380 
    381 error:
    382 	if (ov->lines) {
    383 		for (size_t i = 0; i < count; i++)
    384 			ANNTP_FREE(ov->lines[i]);
    385 		ANNTP_FREE(ov->lines);
    386 	}
    387 
    388 	memset(ov, 0, sizeof(*ov));
    389 	return ANNTPE(ANE_IO);
    390 }
    391 
    392 int
    393 anntp_overview_cb(AnntpConnection* cv, size_t start, size_t end, AnntpLineCb cb, void* extra)
    394 {
    395 	if (!cv || !cb || start > end)
    396 		return ANE_PARAMS;
    397 
    398 	char cmd[ANNTP_BUFSIZE];
    399 	char line[ANNTP_BUFSIZE];
    400 
    401 	snprintf(cmd, sizeof(cmd), "XOVER %zu-%zu", start, end);
    402 
    403 	if (anntp_writeline(cv, cmd) <= 0)
    404 		return ANE_IO;
    405 
    406 	ssize_t r = anntp_readline(cv, line, sizeof(line));
    407 	if (r <= 0)
    408 		return ANE_IO;
    409 
    410 	/* expect 224 */
    411 	if (strncmp(line, "224", 3) != 0)
    412 		return ANE_PROTO;
    413 
    414 	for (;;) {
    415 		r = anntp_readline(cv, line, sizeof(line));
    416 		if (r <= 0)
    417 			return ANE_IO;
    418 
    419 		/* end of response */
    420 		if (strcmp(line, ".\r\n") == 0)
    421 			break;
    422 
    423 		/* dot-stuffing removal */
    424 		char* out = line;
    425 		if (out[0] == '.' && out[1] == '.')
    426 			out++;
    427 
    428 		/* strip CRLF for callback */
    429 		size_t len = strlen(out);
    430 		while (len && (out[len-1] == '\n' || out[len-1] == '\r'))
    431 			out[--len] = '\0';
    432 
    433 		if (cb(out, extra) != 0)
    434 			return ANE_OK; /* user aborted early */
    435 	}
    436 
    437 	return ANE_OK;
    438 } 
    439 
    440 static char*
    441 anntp__trim_left(char* s)
    442 {
    443 	while (*s == ' ' || *s == '\t')
    444 		s++;
    445 	return s;
    446 }
    447 
    448 ssize_t
    449 anntp_article(AnntpConnection* cv, size_t n, AnntpArticle* art)
    450 {
    451 	if (!cv || !art)
    452 		return ANNTPE(ANE_PARAMS);
    453 
    454 	char cmd[ANNTP_BUFSIZE];
    455 	char line[ANNTP_BUFSIZE];
    456 
    457 	memset(art, 0, sizeof(*art));
    458 
    459 	snprintf(cmd, sizeof(cmd), "ARTICLE %zu", n);
    460 
    461 	if (anntp_writeline(cv, cmd) <= 0)
    462 		return ANNTPE(ANE_IO);
    463 
    464 	ssize_t r = anntp_readline(cv, line, sizeof(line));
    465 	if (r <= 0)
    466 		return ANNTPE(ANE_IO);
    467 
    468 	if (strncmp(line, "220", 3) != 0)
    469 		return ANNTPE(ANE_PROTO);
    470 
    471 	size_t cap = ANNTP_BUFSIZE * 8;
    472 	char* buf = (char*)ANNTP_MALLOC(cap);
    473 	if (!buf)
    474 		return ANNTPE(ANE_IO);
    475 
    476 	ssize_t len = anntp_readdot(cv, buf, cap);
    477 	if (len < 0)
    478 		goto error;
    479 
    480 	char* p = buf;
    481 	char* body_start = NULL;
    482 
    483 	while (*p) {
    484 		char* line_start = p;
    485 
    486 		char* nl = strchr(p, '\n');
    487 		if (!nl)
    488 			break;
    489 
    490 		*nl = '\0';
    491 		p = nl + 1;
    492 
    493 		/* end of headers */
    494 		if (line_start[0] == '\r' || line_start[0] == '\0') {
    495 			body_start = p;
    496 			break;
    497 		}
    498 
    499 		if (strncmp(line_start, "Message-ID:", 11) == 0) {
    500 			char* v = anntp__trim_left(line_start + 11);
    501 			art->id = ANNTP_STRDUP(v);
    502 		}
    503 		else if (strncmp(line_start, "From:", 5) == 0) {
    504 			char* v = anntp__trim_left(line_start + 5);
    505 			art->from = ANNTP_STRDUP(v);
    506 		}
    507 		else if (strncmp(line_start, "Date:", 5) == 0) {
    508 			char* v = anntp__trim_left(line_start + 5);
    509 			art->date = ANNTP_STRDUP(v);
    510 		}
    511 	}
    512 
    513 	if (body_start) {
    514 		size_t blen = strlen(body_start);
    515 		art->body = (char*)ANNTP_MALLOC(blen + 1);
    516 		if (!art->body)
    517 			goto error;
    518 
    519 		memcpy(art->body, body_start, blen + 1);
    520 	}
    521 
    522 	ANNTP_FREE(buf);
    523 	return (ssize_t)len;
    524 
    525 error:
    526 	anntp_article_free(art);
    527 	ANNTP_FREE(buf);
    528 	return ANNTPE(ANE_IO);
    529 }
    530 
    531 void
    532 anntp_article_free(AnntpArticle* art)
    533 {
    534 	if (!art)
    535 		return;
    536 
    537 	if (art->id) {
    538 		ANNTP_FREE(art->id);
    539 		art->id = NULL;
    540 	}
    541 
    542 	if (art->from) {
    543 		ANNTP_FREE(art->from);
    544 		art->from = NULL;
    545 	}
    546 
    547 	if (art->date) {
    548 		ANNTP_FREE(art->date);
    549 		art->date = NULL;
    550 	}
    551 
    552 	if (art->body) {
    553 		ANNTP_FREE(art->body);
    554 		art->body = NULL;
    555 	}
    556 
    557 	art->n = 0;
    558 }
    559 
    560 int
    561 anntp_article_cb(AnntpConnection* cv, size_t n, AnntpLineCb cb, void* extra)
    562 {
    563 	if (!cv || !cb)
    564 		return ANE_PARAMS;
    565 
    566 	char cmd[ANNTP_BUFSIZE];
    567 	char line[ANNTP_BUFSIZE];
    568 
    569 	snprintf(cmd, sizeof(cmd), "ARTICLE %zu", n);
    570 
    571 	if (anntp_writeline(cv, cmd) <= 0)
    572 		return ANE_IO;
    573 
    574 	ssize_t r = anntp_readline(cv, line, sizeof(line));
    575 	if (r <= 0)
    576 		return ANE_IO;
    577 
    578 	if (strncmp(line, "220", 3) != 0)
    579 		return ANE_PROTO;
    580 
    581 	/* stream dot-terminated response */
    582 	int in_headers = 1;
    583 
    584 	for (;;) {
    585 		r = anntp_readline(cv, line, sizeof(line));
    586 		if (r <= 0)
    587 			return ANE_IO;
    588 
    589 		/* end of article */
    590 		if (strcmp(line, ".\r\n") == 0)
    591 			break;
    592 
    593 		/* dot-stuffing removal */
    594 		char* out = line;
    595 		if (out[0] == '.' && out[1] == '.')
    596 			out++;
    597 
    598 		/* header/body split */
    599 		if (in_headers) {
    600 			if (out[0] == '\r' || out[0] == '\n') {
    601 				in_headers = 0;
    602 				continue;
    603 			}
    604 		}
    605 
    606 		/* callback */
    607 		if (cb(out, extra) != 0)
    608 			return ANE_OK;
    609 	}
    610 
    611 	return ANE_OK;
    612 }
    613 
    614 void
    615 anntp_freeconn(AnntpConnection* cv)
    616 {
    617 	if (!cv) return;
    618 
    619 #ifdef ANNTP_TLS
    620 	if (cv->ssl) {
    621 		SSL_shutdown(cv->ssl);
    622 		SSL_free(cv->ssl);
    623 	}
    624 #endif
    625 
    626 	if (cv->fd >= 0) close(cv->fd);
    627 	if (cv->host) ANNTP_FREE(cv->host);
    628 	ANNTP_FREE(cv);
    629 }
    630 
    631 int
    632 anntp_group(AnntpConnection* cv, const char* group, AnntpGroup* out)
    633 {
    634 	if (!cv || !group || !out)
    635 		return ANE_PARAMS;
    636 
    637 	char cmd[ANNTP_BUFSIZE];
    638 	snprintf(cmd, sizeof(cmd), "GROUP %s", group);
    639 
    640 	if (anntp_writeline(cv, cmd) <= 0)
    641 		return ANE_IO;
    642 
    643 	char line[ANNTP_BUFSIZE];
    644 	if (anntp_readline(cv, line, sizeof(line)) <= 0)
    645 		return ANE_IO;
    646 
    647 	if (strncmp(line, "211", 3) != 0)
    648 		return ANE_PROTO;
    649 
    650 	int count;
    651 	int first, last;
    652 	char mode = '?';
    653 	char name[128];
    654 
    655 	/* validate */
    656 	int parsed = sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name);
    657 	if (parsed < 4) {
    658 		return ANE_PROTO;
    659 	}
    660 
    661 	out->name = ANNTP_STRDUP(name);
    662 	if (!out->name)
    663 		return ANNTPE(ANE_IO);
    664 	out->first = (AnntpArticleNumber)first;
    665 	out->last = (AnntpArticleNumber)last;
    666 	out->mode = mode;
    667 
    668 	return ANE_OK;
    669 }
    670 
    671 ssize_t
    672 anntp_read(AnntpConnection* cv, uchar_t* buf, size_t len)
    673 {
    674 	if (!cv || !buf) return ANNTPE(ANE_PARAMS);
    675 
    676 #ifdef ANNTP_TLS
    677 	if (cv->use_tls && cv->ssl) {
    678 		int n = SSL_read(cv->ssl, buf, (int)len);
    679 		return (n <= 0) ? ANNTPE(ANE_TLS) : n;
    680 	}
    681 #endif
    682 
    683 	ssize_t n = read(cv->fd, buf, len);
    684 	return (n < 0) ? ANNTPE(ANE_IO) : n;
    685 }
    686 
    687 ssize_t
    688 anntp_write(AnntpConnection* cv, const uchar_t* buf, size_t len)
    689 {
    690 	if (!cv || !buf) return ANNTPE(ANE_PARAMS);
    691 
    692 #ifdef ANNTP_TLS
    693 	if (cv->use_tls && cv->ssl) {
    694 		int n = SSL_write(cv->ssl, buf, (int)len);
    695 		return (n <= 0) ? ANNTPE(ANE_TLS) : n;
    696 	}
    697 #endif
    698 
    699 	ssize_t n = write(cv->fd, buf, (int)len);
    700 	return (n < 0) ? ANNTPE(ANE_IO) : n;
    701 }
    702 
    703 ssize_t
    704 anntp_write_all(AnntpConnection* cv, const uchar_t* buf, size_t len)
    705 {
    706 	size_t sent = 0;
    707 	while (sent < len) {
    708 		ssize_t n = anntp_write(cv, buf + sent, len - sent);
    709 		if (n <= 0) return n;
    710 		sent += (size_t)n;
    711 	}
    712 	return (ssize_t)sent;
    713 }
    714 
    715 ssize_t
    716 anntp_readline(AnntpConnection* cv, char* buf, size_t maxlen)
    717 {
    718 	if (!cv || !buf || maxlen == 0) return ANNTPE(ANE_PARAMS);
    719 
    720 	size_t pos = 0;
    721 
    722 	while (pos < maxlen - 1) {
    723 		/* refill buffer if empty */
    724 		if (cv->read_pos >= cv->read_len) {
    725 			ssize_t n = _afillbuf(cv);
    726 			if (n <= 0) {
    727 				if (pos == 0) return n;
    728 				break;
    729 			}
    730 		}
    731 
    732 		char ch = (char)cv->read_buffer[cv->read_pos++];
    733 		buf[pos++] = ch;
    734 
    735 		if (ch == '\n')
    736 			break;
    737 	}
    738 
    739 	buf[pos] = '\0';
    740 	return (ssize_t)pos;
    741 }
    742 
    743 ssize_t
    744 anntp_writeline(AnntpConnection* cv, const char* line)
    745 {
    746 	if (!cv || !line) return -1;
    747 
    748 	size_t len = strlen(line);
    749 	ssize_t n;
    750 
    751 	/* write the line itself */
    752 	n = anntp_write_all(cv, (const uchar_t*)line, len);
    753 	if (n <= 0) return n;
    754 
    755 	/* write CRLF */
    756 	n = anntp_write_all(cv, (const uchar_t*)"\r\n", 2);
    757 	return n;
    758 }
    759 
    760 ssize_t
    761 anntp_readdot(AnntpConnection* cv, char* buf, size_t maxlen)
    762 {
    763 	if (!cv || !buf) return -1;
    764 
    765 	char* line = (char*)ANNTP_MALLOC(ANNTP_BUFSIZE);
    766 	if (!line) return ANNTPE(ANE_IO);
    767 	size_t pos = 0;
    768 	ssize_t n;
    769 
    770 	for (;;) {
    771 		n = anntp_readline(cv, line, ANNTP_BUFSIZE);
    772 		if (n <= 0)
    773 			break; /* closed or error */
    774 
    775 		/* end of response is a single period */
    776 		if (strcmp(line, ".\r\n") == 0)
    777 			break;
    778 
    779 		/* lines starting with .. are dot-stuffed, do a /^\.\././ ;) */
    780 		char* out = line;
    781 		if (line[0] == '.' && line[1] == '.')
    782 			++out;
    783 
    784 		size_t len = strlen(out);
    785 		if (pos + len >= maxlen - 1 && maxlen != 0)
    786 			break; /* Say NO to overflows */
    787 		
    788 		memcpy(buf + pos, out, len);
    789 		pos += len;
    790 	}
    791 
    792 	ANNTP_FREE(line);
    793 	if (pos < maxlen) buf[pos] = '\0';
    794 	else buf[maxlen - 1] = '\0'; /* null terminate */
    795 	return (ssize_t)pos;
    796 }
    797 
    798 void
    799 anntp_overview_free(AnntpOverview* ov)
    800 {
    801 	if (!ov)
    802 		return;
    803 
    804 	if (ov->lines) {
    805 		for (size_t i = 0; i < ov->count; i++) {
    806 			if (ov->lines[i])
    807 				ANNTP_FREE(ov->lines[i]);
    808 		}
    809 		ANNTP_FREE(ov->lines);
    810 	}
    811 
    812 	ov->lines = NULL;
    813 	ov->count = 0;
    814 }
    815 
    816 int
    817 anntp_readdot_cb(AnntpConnection* cv, AnntpLineCb cb, void* extra)
    818 {
    819 	if (!cv || !cb) return ANE_PARAMS;
    820 
    821 	char line[ANNTP_BUFSIZE];
    822 	ssize_t n;
    823 
    824 	for (;;) {
    825 		n = anntp_readline(cv, line, sizeof(line));
    826 		if (n <= 0)
    827 			return ANE_IO;
    828 
    829 		/* logic here is a bit like on anntp_readdot */
    830 		if (strcmp(line, ".\r\n") == 0)
    831 			break;
    832 
    833 		char* out = line;
    834 		if (line[0] == '.' && line[1] == '.')
    835 			out++;
    836 
    837 		if (cb(out, extra) != 0)
    838 			return ANE_OK; /* user aborted */
    839 	}
    840 
    841 	return ANE_OK;
    842 }
    843 
    844 /* NOTE: I have a gut feeling this auth mechanism is probably a bit insecure... */
    845 int
    846 anntp_auth(AnntpConnection* cv, const char* user, const char* pass)
    847 {
    848 	/*
    849 	 * NNTP auth:
    850 	 *   C: AUTHINFO USER <user>
    851 	 *   S: 381 (need password) OR 281 (already authenticated) OR error
    852 	 *   C: AUTHINFO PASS <pass>
    853 	 *   S: 281 (success) OR error
    854 	 */
    855 
    856 	if (!cv || !user || !pass)
    857 		return ANE_PARAMS;
    858 
    859 	char line[ANNTP_BUFSIZE];
    860 	char cmd[ANNTP_BUFSIZE];
    861 	ssize_t n;
    862 
    863 	/* send login */
    864 	snprintf(cmd, sizeof(cmd), "AUTHINFO USER %s", user);
    865 
    866 	n = anntp_writeline(cv, cmd);
    867 	if (n <= 0)
    868 		return ANE_IO;
    869 
    870 	/* read response */
    871 	n = anntp_readline(cv, line, sizeof(line));
    872 	if (n <= 0)
    873 		return ANE_IO;
    874 
    875 	/* already authenticated */
    876 	if (strncmp(line, "281", 3) == 0) {
    877 		return ANE_OK; /* it isn't an error to be already authenticated */
    878 	}
    879 
    880 	/* must be 381 to continue */
    881 	if (strncmp(line, "381", 3) != 0) {
    882 		return ANE_PROTO; /* that isn't a valid response, probably */
    883 	}
    884 
    885 	snprintf(cmd, sizeof(cmd), "AUTHINFO PASS %s", pass);
    886 
    887 	n = anntp_writeline(cv, cmd);
    888 
    889 	/* probably a good idea to wipe password from stack */
    890 	memset(cmd, 0, sizeof(cmd));
    891 
    892 	if (n <= 0)
    893 		return ANE_IO;
    894 
    895 	n = anntp_readline(cv, line, sizeof(line));
    896 	if (n <= 0)
    897 		return ANE_IO;
    898 
    899 	/* success */
    900 	if (strncmp(line, "281", 3) == 0) {
    901 		return ANE_OK;
    902 	}
    903 
    904 	/* failure */
    905 	return ANE_AUTH;
    906 }
    907 
    908 /* fill read buffer with data */
    909 static ssize_t
    910 _afillbuf(AnntpConnection* c)
    911 {
    912 	ssize_t n;
    913 
    914 #ifdef ANNTP_TLS
    915 	if (c->use_tls && c->ssl)
    916 		n = SSL_read(c->ssl, c->read_buffer, ANNTP_BUFSIZE);
    917 	else
    918 #endif
    919 		n = read(c->fd, c->read_buffer, ANNTP_BUFSIZE);
    920 
    921 	if (n > 0) {
    922 		c->read_len = (size_t)n;
    923 		c->read_pos = 0;
    924 		return n;
    925 	}
    926 
    927 	c->read_len = 0;
    928 
    929 	if (n == 0)
    930 		return 0; /* EOF */
    931 
    932 #ifdef ANNTP_TLS
    933 	if (c->use_tls && c->ssl)
    934 		return ANNTPE(ANE_TLS);
    935 #endif
    936 
    937 	return ANNTPE(ANE_IO);
    938 }
    939 
    940 char*
    941 anntp_strerror(AnntpErrCode c)
    942 {
    943 	/* ssize_t-returning functions will report errors in negative */
    944 	if (c < 0)
    945 		c = (AnntpErrCode)(-c); /* "i never said const!" */
    946 
    947 	switch (c) {
    948 	case ANE_OK:
    949 		return "<ok>";
    950 	case ANE_PARAMS:
    951 		return "invalid parameters";
    952 	case ANE_TLS:
    953 		return "security (cryptography/TLS) error";
    954 	case ANE_IO:
    955 		return "I/O error";
    956 	case ANE_PROTO:
    957 		return "protocol error, unexpected server response";
    958 	case ANE_AUTH:
    959 		return "authentication failed";
    960 
    961 	default:
    962 		return "unknown error";
    963 	}
    964 }
    965 
    966 #endif /* ANNTP_IMPLEMENTATION_ONCE */
    967 #endif /* ANNTP_IMPLEMENTATION */
    968