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