anntp

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

anntp.h (15286B)


      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 is low level (higher-level wrappers coming soon, but don't expect a lot, this is by design).
     28  ** - This library has and will always have a limited API by design.
     29  ** - This library is in development, everything can change fast.
     30  ** - This library doesn't work in Windows, and I doubt it will be easy to port...
     31  ** + This library is small, relatively readable to be networked C, and hackable.
     32  ** + This library is minimalist.
     33  ** + This library is very easy to use.
     34  ** + This library has good error handling.
     35  ** + This library uses idiomatic C.
     36  ** + This library exists, which makes it one of the first exclusively-NNTP libraries.
     37  ** + Technically, this library is capable of doing everything (although it can be tedious!).
     38  **
     39  ** The model is based on connections, which are basically client-server, command-response abstractions. The rest is
     40  ** pretty straight forward.
     41  **
     42  ** LICENSE
     43  **   See the ./LICENSE file on the distribution (or click the LICENSE link on the Git viewer) to see copyright info.
     44  **   Exclusively CC0, so cool :).
     45  **
     46  ** CONTRIBUTORS
     47  ** - Mario Rosell <mario@mariorosell.es>	Most things
     48  **
     49  ** TODOS:
     50  **   See the TODOS file.
     51  **/
     52 
     53 #ifndef ANNTP_H
     54 #define ANNTP_H
     55 
     56 #include <arpa/inet.h>   /* for inet_ntoa, inet_ntop */
     57 #include <errno.h>       /* for errno */
     58 #include <netdb.h>       /* for gethostbyname, struct hostent */
     59 #include <netinet/in.h>  /* for struct in_addr */
     60 #include <stddef.h>      /* for size_t */
     61 #include <stdio.h>       /* for fprintf, perror, stderr */
     62 #include <stdlib.h>      /* for malloc, free */
     63 #include <string.h>      /* for memset, memcpy, strdup */
     64 #include <sys/socket.h>  /* for socket, connect */
     65 #include <unistd.h>      /* for close */
     66 #include <fcntl.h>       /* for fcntl */
     67 
     68 #ifdef ANNTP_TLS
     69 #include <openssl/ssl.h>
     70 #include <openssl/err.h>
     71 #endif
     72 
     73 /*** config ***/
     74 
     75 #ifdef _WIN32
     76 #       error This project uses the Anntp library, which is not compatible with Windows, sorry :(
     77 #endif
     78 
     79 #ifndef ANNTP_MALLOC
     80 #       define ANNTP_MALLOC malloc
     81 #endif
     82 
     83 #ifndef ANNTP_FREE
     84 #       define ANNTP_FREE free
     85 #endif
     86 
     87 #ifndef ANNTP_BUFSIZE
     88 #       define ANNTP_BUFSIZE 0x1000
     89 #endif
     90 
     91 #ifndef ANNTP_API
     92 #       define ANNTP_API extern
     93 #endif
     94 
     95 #ifndef ANNTP_PRINTF
     96 #	define ANNTP_PRINTF printf
     97 #endif
     98 
     99 #ifndef ANNTP_STRDUP
    100 #	if (defined(__GNUC__) && defined(_GNU_SOURCE)) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L)
    101 #		define ANNTP_STRDUP strdup
    102 #	else
    103 #		define ANNTP_STRDUP anntp__NC__strdup
    104 char*
    105 anntp__NC__strdup(const char* s) {
    106 	size_t len = strlen(s) + 1;
    107 	char* p = (char*)malloc(len);
    108 	if (p) memcpy(p, s, len);
    109 	return p;
    110 }
    111 #	endif
    112 #endif
    113 
    114 /*** defs ***/
    115 
    116 #define Bool char
    117 #define true 1
    118 #define false 0
    119 #define ANNTPE(e) (-(ssize_t)(e))
    120 #define ANNTP_ISERR(x) ((x) < 0)
    121 #define ANNTP_CODE(x) ((AnntpErrCode)(-(x)))
    122 
    123 typedef unsigned char uchar_t;
    124 typedef long AnntpArticleNumber;
    125 
    126 enum anntp_state {
    127 	ANS_OFF,
    128 	ANS_CONNECTING,
    129 	ANS_READY,
    130 	ANS_ERROR,
    131 };
    132 
    133 /* NOTE: often given as negative for functions returning ssize_t */
    134 enum anntp_errcode {
    135 	ANE_OK = 0,
    136 	ANE_PARAMS,
    137 	ANE_TLS,
    138 	ANE_IO,
    139 	ANE_PROTO,
    140 	ANE_AUTH,
    141 };
    142 
    143 typedef enum anntp_state AnntpState;
    144 typedef enum anntp_errcode AnntpErrCode;
    145 
    146 struct anntp_connection {
    147 	int             fd;           /* socket file descriptor */
    148 	AnntpState      state;        /* current connection state */
    149 	Bool            use_tls;      /* whether to use TLS */
    150 	char*           host;         /* hostname string */
    151 	struct hostent* host_he;      /* resolved host entry */
    152 	struct in_addr  addr;         /* resolved IP address */
    153 	uchar_t         read_buffer[ANNTP_BUFSIZE]; /* read buffer */
    154 	size_t          read_pos;     /* used for buffering */
    155 	size_t          read_len;     /* length of data in buffer */
    156 #ifdef ANNTP_TLS
    157 	SSL*            ssl;          /* TLS session */
    158 #endif
    159 };
    160 
    161 struct anntp_group {
    162 	char* name;
    163 	AnntpArticleNumber first;
    164 	AnntpArticleNumber last;
    165 	char mode; /* y for posting allowed, n for no posting allowed, m for moderated */
    166 };
    167 
    168 struct anntp_article {
    169 	AnntpArticleNumber n;
    170 	char*              id;
    171 	char*              from;
    172 	char*              date;
    173 	char*              body;
    174 };
    175 
    176 struct anntp_overview {
    177 	char** lines;
    178 	size_t count;
    179 };
    180 
    181 typedef struct anntp_connection AnntpConnection;
    182 typedef struct anntp_article AnntpArticle;
    183 typedef struct anntp_overview AnntpOverview;
    184 typedef struct anntp_group AnntpGroup;
    185 
    186 typedef int (*AnntpLineCb)(const char* line, void* extra);
    187 
    188 /*** function declarations ***/
    189 
    190 ANNTP_API void             anntp_init(void);
    191 ANNTP_API AnntpConnection* anntp_mkconn(const char* host, const char* port, Bool tls);
    192 ANNTP_API void             anntp_freeconn(AnntpConnection* conn);
    193 ANNTP_API ssize_t          anntp_read(AnntpConnection* conn, uchar_t* buf, size_t bufsize);
    194 ANNTP_API ssize_t          anntp_write(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
    195 ANNTP_API int              anntp_group(AnntpConnection* conn, const char* group, AnntpGroup* out_group);
    196 ANNTP_API ssize_t          anntp_write_all(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
    197 ANNTP_API ssize_t          anntp_writeline(AnntpConnection* conn, const char* buf);
    198 ANNTP_API ssize_t          anntp_readline(AnntpConnection* conn, char* buf, size_t maxlen);
    199 ANNTP_API ssize_t          anntp_readdot(AnntpConnection* conn, char* buf, size_t maxlen);
    200 ANNTP_API int              anntp_readdot_cb(AnntpConnection* conn, AnntpLineCb cb, void* extra);
    201 ANNTP_API int              anntp_auth(AnntpConnection* conn, const char* login, const char* password);
    202 ANNTP_API char*            anntp_strerror(AnntpErrCode err);
    203 
    204 #endif /* ANNTP_H */
    205 
    206 #ifdef ANNTP_IMPLEMENTATION
    207 
    208 static ssize_t _afillbuf(AnntpConnection* c);
    209 
    210 void
    211 anntp_init(void)
    212 {
    213 #ifdef ANNTP_TLS
    214 	SSL_load_error_strings();
    215 	SSL_library_init();
    216 	OpenSSL_add_ssl_algorithms();
    217 #endif
    218 }
    219 
    220 /* create connection */
    221 AnntpConnection*
    222 anntp_mkconn(const char* host, const char* port, Bool tls)
    223 {
    224 	AnntpConnection* cv = (AnntpConnection*)ANNTP_MALLOC(sizeof(AnntpConnection));
    225 	if (!cv) return NULL;
    226 
    227 	memset(cv, 0, sizeof(*cv));
    228 	cv->host = ANNTP_STRDUP(host);
    229 	cv->use_tls = tls;
    230 	cv->state = ANS_OFF;
    231 	cv->fd = -1;
    232 
    233 	/* create socket */
    234 	cv->fd = socket(AF_INET, SOCK_STREAM, 0);
    235 	if (cv->fd < 0) {
    236 		ANNTP_PRINTF("anntp - making socket: %s\n", strerror(errno));
    237 		goto cleanup;
    238 	}
    239 
    240 	/* make socket blocking for greeting */
    241 	int flags = fcntl(cv->fd, F_GETFL, 0);
    242 	flags &= ~O_NONBLOCK;
    243 	fcntl(cv->fd, F_SETFL, flags);
    244 
    245 	/* resolve host */
    246 	struct addrinfo hints, *res = NULL;
    247 	memset(&hints, 0, sizeof(hints));
    248 	hints.ai_family = AF_INET;
    249 	hints.ai_socktype = SOCK_STREAM;
    250 
    251 	int err = getaddrinfo(host, port, &hints, &res);
    252 	if (err != 0 || !res) {
    253 		ANNTP_PRINTF("anntp - cant resolve host `%s': %s\n", host, gai_strerror(err));
    254 		goto cleanup;
    255 	}
    256 
    257 	struct sockaddr_in* sa = (struct sockaddr_in*)res->ai_addr;
    258 	cv->addr = sa->sin_addr;
    259 
    260 	if (connect(cv->fd, (struct sockaddr*)sa, sizeof(*sa)) < 0) {
    261 		ANNTP_PRINTF("anntp - connecting: %s\n", strerror(errno));
    262 		goto cleanup_addr;
    263 	}
    264 
    265 	cv->state = ANS_CONNECTING;
    266 
    267 #ifdef ANNTP_TLS
    268 	if (tls) {
    269 		SSL_CTX* ctx = SSL_CTX_new(TLS_client_method());
    270 		if (!ctx) goto cleanup_addr;
    271 
    272 		cv->ssl = SSL_new(ctx);
    273 		if (!cv->ssl) {
    274 			SSL_CTX_free(ctx); goto cleanup_addr;
    275 		}
    276 
    277 		SSL_set_fd(cv->ssl, cv->fd);
    278 		if (SSL_connect(cv->ssl) <= 0) {
    279 			SSL_free(cv->ssl);
    280 			cv->ssl = NULL;
    281 			SSL_CTX_free(ctx);
    282 			goto cleanup_addr;
    283 		}
    284 
    285 		SSL_CTX_free(ctx);
    286 	}
    287 
    288 #endif
    289 
    290 	cv->state = ANS_READY;
    291 
    292 	freeaddrinfo(res);
    293 	return cv;
    294 
    295 cleanup_addr:
    296 	freeaddrinfo(res);
    297 cleanup:
    298 	if (cv->host) ANNTP_FREE(cv->host);
    299 	if (cv->fd >= 0) close(cv->fd);
    300 	ANNTP_FREE(cv);
    301 	return NULL;
    302 }
    303 
    304 void
    305 anntp_freeconn(AnntpConnection* cv)
    306 {
    307 	if (!cv) return;
    308 
    309 #ifdef ANNTP_TLS
    310 	if (cv->ssl) {
    311 		SSL_shutdown(cv->ssl);
    312 		SSL_free(cv->ssl);
    313 	}
    314 #endif
    315 
    316 	if (cv->fd >= 0) close(cv->fd);
    317 	if (cv->host) ANNTP_FREE(cv->host);
    318 	ANNTP_FREE(cv);
    319 }
    320 
    321 int
    322 anntp_group(AnntpConnection* cv, const char* group, AnntpGroup* out)
    323 {
    324 	if (!cv || !group || !out)
    325 		return ANE_PARAMS;
    326 
    327 	char cmd[ANNTP_BUFSIZE];
    328 	snprintf(cmd, sizeof(cmd), "GROUP %s", group);
    329 
    330 	if (anntp_writeline(cv, cmd) <= 0)
    331 		return ANE_IO;
    332 
    333 	char line[ANNTP_BUFSIZE];
    334 	if (anntp_readline(cv, line, sizeof(line)) <= 0)
    335 		return ANE_IO;
    336 
    337 	if (strncmp(line, "211", 3) != 0)
    338 		return ANE_PROTO;
    339 
    340 	int count;
    341 	int first, last;
    342 	char mode = '?';
    343 	char name[128];
    344 
    345 	/* validate */
    346 	int parsed = sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name);
    347 	if (parsed < 4) {
    348 		return ANE_PROTO;
    349 	}
    350 
    351 	out->name = ANNTP_STRDUP(name);
    352 	if (!out->name)
    353 		return ANNTPE(ANE_IO);
    354 	out->first = (AnntpArticleNumber)first;
    355 	out->last = (AnntpArticleNumber)last;
    356 	out->mode = mode;
    357 
    358 	return ANE_OK;
    359 }
    360 
    361 ssize_t
    362 anntp_read(AnntpConnection* cv, uchar_t* buf, size_t len)
    363 {
    364 	if (!cv || !buf) return ANNTPE(ANE_PARAMS);
    365 
    366 #ifdef ANNTP_TLS
    367 	if (cv->use_tls && cv->ssl) {
    368 		int n = SSL_read(cv->ssl, buf, (int)len);
    369 		return (n <= 0) ? ANNTPE(ANE_TLS) : n;
    370 	}
    371 #endif
    372 
    373 	ssize_t n = read(cv->fd, buf, len);
    374 	return (n < 0) ? ANNTPE(ANE_IO) : n;
    375 }
    376 
    377 ssize_t
    378 anntp_write(AnntpConnection* cv, const uchar_t* buf, size_t len)
    379 {
    380 	if (!cv || !buf) return ANNTPE(ANE_PARAMS);
    381 
    382 #ifdef ANNTP_TLS
    383 	if (cv->use_tls && cv->ssl) {
    384 		int n = SSL_write(cv->ssl, buf, (int)len);
    385 		return (n <= 0) ? ANNTPE(ANE_TLS) : n;
    386 	}
    387 #endif
    388 
    389 	ssize_t n = write(cv->fd, buf, (int)len);
    390 	return (n < 0) ? ANNTPE(ANE_IO) : n;
    391 }
    392 
    393 ssize_t
    394 anntp_write_all(AnntpConnection* cv, const uchar_t* buf, size_t len)
    395 {
    396 	size_t sent = 0;
    397 	while (sent < len) {
    398 		ssize_t n = anntp_write(cv, buf + sent, len - sent);
    399 		if (n <= 0) return n;
    400 		sent += (size_t)n;
    401 	}
    402 	return (ssize_t)sent;
    403 }
    404 
    405 ssize_t
    406 anntp_readline(AnntpConnection* cv, char* buf, size_t maxlen)
    407 {
    408 	if (!cv || !buf || maxlen == 0) return ANNTPE(ANE_PARAMS);
    409 
    410 	size_t pos = 0;
    411 
    412 	while (pos < maxlen - 1) {
    413 		/* refill buffer if empty */
    414 		if (cv->read_pos >= cv->read_len) {
    415 			ssize_t n = _afillbuf(cv);
    416 			if (n <= 0) {
    417 				if (pos == 0) return n;
    418 				break;
    419 			}
    420 		}
    421 
    422 		char ch = (char)cv->read_buffer[cv->read_pos++];
    423 		buf[pos++] = ch;
    424 
    425 		if (ch == '\n')
    426 			break;
    427 	}
    428 
    429 	buf[pos] = '\0';
    430 	return (ssize_t)pos;
    431 }
    432 
    433 ssize_t
    434 anntp_writeline(AnntpConnection* cv, const char* line)
    435 {
    436 	if (!cv || !line) return -1;
    437 
    438 	size_t len = strlen(line);
    439 	ssize_t n;
    440 
    441 	/* write the line itself */
    442 	n = anntp_write_all(cv, (const uchar_t*)line, len);
    443 	if (n <= 0) return n;
    444 
    445 	/* write CRLF */
    446 	n = anntp_write_all(cv, (const uchar_t*)"\r\n", 2);
    447 	return n;
    448 }
    449 
    450 ssize_t
    451 anntp_readdot(AnntpConnection* cv, char* buf, size_t maxlen)
    452 {
    453 	if (!cv || !buf) return -1;
    454 
    455 	char* line = (char*)ANNTP_MALLOC(ANNTP_BUFSIZE);
    456 	if (!line) return ANNTPE(ANE_IO);
    457 	size_t pos = 0;
    458 	ssize_t n;
    459 
    460 	for (;;) {
    461 		n = anntp_readline(cv, line, ANNTP_BUFSIZE);
    462 		if (n <= 0)
    463 			break; /* closed or error */
    464 
    465 		/* end of response is a single period */
    466 		if (strcmp(line, ".\r\n") == 0)
    467 			break;
    468 
    469 		/* lines starting with .. are dot-stuffed, do a /^\.\././ ;) */
    470 		char* out = line;
    471 		if (line[0] == '.' && line[1] == '.')
    472 			++out;
    473 
    474 		size_t len = strlen(out);
    475 		if (pos + len >= maxlen - 1 && maxlen != 0)
    476 			break; /* Say NO to overflows */
    477 		
    478 		memcpy(buf + pos, out, len);
    479 		pos += len;
    480 	}
    481 
    482 	ANNTP_FREE(line);
    483 	if (pos < maxlen) buf[pos] = '\0';
    484 	else buf[maxlen - 1] = '\0'; /* null terminate */
    485 	return (ssize_t)pos;
    486 }
    487 
    488 int
    489 anntp_readdot_cb(AnntpConnection* cv, AnntpLineCb cb, void* extra)
    490 {
    491 	if (!cv || !cb) return ANE_PARAMS;
    492 
    493 	char line[ANNTP_BUFSIZE];
    494 	ssize_t n;
    495 
    496 	for (;;) {
    497 		n = anntp_readline(cv, line, sizeof(line));
    498 		if (n <= 0)
    499 			return ANE_IO;
    500 
    501 		/* logic here is a bit like on anntp_readdot */
    502 		if (strcmp(line, ".\r\n") == 0)
    503 			break;
    504 
    505 		char* out = line;
    506 		if (line[0] == '.' && line[1] == '.')
    507 			out++;
    508 
    509 		if (cb(out, extra) != 0)
    510 			return ANE_OK; /* user aborted */
    511 	}
    512 
    513 	return ANE_OK;
    514 }
    515 
    516 /* NOTE: I have a gut feeling this auth mechanism is probably a bit insecure... */
    517 int
    518 anntp_auth(AnntpConnection* cv, const char* user, const char* pass)
    519 {
    520 	/*
    521 	 * NNTP auth:
    522 	 *   C: AUTHINFO USER <user>
    523 	 *   S: 381 (need password) OR 281 (already authenticated) OR error
    524 	 *   C: AUTHINFO PASS <pass>
    525 	 *   S: 281 (success) OR error
    526 	 */
    527 
    528 	if (!cv || !user || !pass)
    529 		return ANE_PARAMS;
    530 
    531 	char line[ANNTP_BUFSIZE];
    532 	char cmd[ANNTP_BUFSIZE];
    533 	ssize_t n;
    534 
    535 	/* send login */
    536 	snprintf(cmd, sizeof(cmd), "AUTHINFO USER %s", user);
    537 
    538 	n = anntp_writeline(cv, cmd);
    539 	if (n <= 0)
    540 		return ANE_IO;
    541 
    542 	/* read response */
    543 	n = anntp_readline(cv, line, sizeof(line));
    544 	if (n <= 0)
    545 		return ANE_IO;
    546 
    547 	/* already authenticated */
    548 	if (strncmp(line, "281", 3) == 0) {
    549 		return ANE_OK; /* it isn't an error to be already authenticated */
    550 	}
    551 
    552 	/* must be 381 to continue */
    553 	if (strncmp(line, "381", 3) != 0) {
    554 		return ANE_PROTO; /* that isn't a valid response, probably */
    555 	}
    556 
    557 	snprintf(cmd, sizeof(cmd), "AUTHINFO PASS %s", pass);
    558 
    559 	n = anntp_writeline(cv, cmd);
    560 
    561 	/* probably a good idea to wipe password from stack */
    562 	memset(cmd, 0, sizeof(cmd));
    563 
    564 	if (n <= 0)
    565 		return ANE_IO;
    566 
    567 	n = anntp_readline(cv, line, sizeof(line));
    568 	if (n <= 0)
    569 		return ANE_IO;
    570 
    571 	/* success */
    572 	if (strncmp(line, "281", 3) == 0) {
    573 		return ANE_OK;
    574 	}
    575 
    576 	/* failure */
    577 	return ANE_AUTH;
    578 }
    579 
    580 /* fill read buffer with data */
    581 static ssize_t
    582 _afillbuf(AnntpConnection* c)
    583 {
    584 	ssize_t n;
    585 
    586 #ifdef ANNTP_TLS
    587 	if (c->use_tls && c->ssl)
    588 		n = SSL_read(c->ssl, c->read_buffer, ANNTP_BUFSIZE);
    589 	else
    590 #endif
    591 		n = read(c->fd, c->read_buffer, ANNTP_BUFSIZE);
    592 
    593 	if (n > 0) {
    594 		c->read_len = (size_t)n;
    595 		c->read_pos = 0;
    596 		return n;
    597 	}
    598 
    599 	c->read_len = 0;
    600 
    601 	if (n == 0)
    602 		return 0; /* EOF */
    603 
    604 #ifdef ANNTP_TLS
    605 	if (c->use_tls && c->ssl)
    606 		return ANNTPE(ANE_TLS);
    607 #endif
    608 
    609 	return ANNTPE(ANE_IO);
    610 }
    611 
    612 char*
    613 anntp_strerror(AnntpErrCode c)
    614 {
    615 	/* ssize_t-returning functions will report errors in negative */
    616 	if (c < 0)
    617 		c = (AnntpErrCode)(-c); /* "i never said const!" */
    618 
    619 	switch (c) {
    620 	case ANE_OK:
    621 		return "<ok>";
    622 	case ANE_PARAMS:
    623 		return "invalid parameters";
    624 	case ANE_TLS:
    625 		return "security (cryptography/TLS) error";
    626 	case ANE_IO:
    627 		return "I/O error";
    628 	case ANE_PROTO:
    629 		return "protocol error, unexpected server response";
    630 	case ANE_AUTH:
    631 		return "authentication failed";
    632 
    633 	default:
    634 		return "unknown error";
    635 	}
    636 }
    637 
    638 #endif /* ANNTP_IMPLEMENTATION */
    639