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