commit beb6534db668aa2d9c620eb9c2914269fc10c4d3
parent 63a867ec2671306eb2c464c241064426087c77e8
Author: Mario Rosell R. Martinez <mario@mariorosell.es>
Date: Sat, 21 Mar 2026 12:48:36 +0100
Add wrappers over some things
Diffstat:
| M | anntp.h | | | 281 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | examples/nntpsh.c | | | 7 | ++++++- |
2 files changed, 282 insertions(+), 6 deletions(-)
diff --git a/anntp.h b/anntp.h
@@ -1,7 +1,7 @@
/**
- ** anntp v0.1.0 - public-domain nntp client implementation in C - by Mario Rosell an contributors
- ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
- ** use at your own risk.
+ ** anntp v0.1.0dev - public-domain nntp client implementation in C - by Mario Rosell an contributors
+ ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
+ ** use at your own risk.
** #include ...
**
** Add:
@@ -95,9 +95,9 @@
# if (defined(__GNUC__) && defined(_GNU_SOURCE)) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L)
# define ANNTP_STRDUP strdup
# else
-# define ANNTP_STRDUP anntp__NC__strdup
+# define ANNTP_STRDUP ______anntp__NC__strdup______
char*
-anntp__NC__strdup(const char* s) {
+______anntp__NC__strdup______(const char* s) {
size_t len = strlen(s) + 1;
char* p = (char*)malloc(len);
if (p) memcpy(p, s, len);
@@ -116,6 +116,7 @@ anntp__NC__strdup(const char* s) {
#define ANNTP_CODE(x) ((AnntpErrCode)(-(x)))
typedef unsigned char uchar_t;
+typedef long AnntpArticleNumber;
enum anntp_state {
ANS_OFF,
@@ -152,7 +153,26 @@ struct anntp_connection {
#endif
};
+struct anntp_group {
+ char* name;
+ AnntpArticleNumber first;
+ AnntpArticleNumber last;
+ long count;
+ char mode; /* y for posting allowed, n for no posting allowed, m for moderated */
+};
+
+struct anntp_article {
+ AnntpArticleNumber n;
+ char* id;
+ char* from;
+ char* date;
+ char* body;
+ char* subject;
+};
+
typedef struct anntp_connection AnntpConnection;
+typedef struct anntp_article AnntpArticle;
+typedef struct anntp_group AnntpGroup;
typedef int (*AnntpLineCb)(const char* line, void* extra);
@@ -164,8 +184,13 @@ ANNTP_API void anntp_freeconn(AnntpConnection* conn);
ANNTP_API ssize_t anntp_read(AnntpConnection* conn, uchar_t* buf, size_t bufsize);
ANNTP_API ssize_t anntp_write(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
ANNTP_API ssize_t anntp_write_all(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
+ANNTP_API int anntp_group(AnntpConnection* conn, const char* groupname, AnntpGroup* out);
+ANNTP_API ssize_t anntp_list(AnntpConnection* conn, AnntpGroup** out_groups);
+ANNTP_API ssize_t anntp_article(AnntpConnection* conn, AnntpArticleNumber n, AnntpArticle* out_art);
ANNTP_API ssize_t anntp_writeline(AnntpConnection* conn, const char* buf);
ANNTP_API ssize_t anntp_readline(AnntpConnection* conn, char* buf, size_t maxlen);
+ANNTP_API void anntp_free_article(AnntpArticle* a);
+ANNTP_API void anntp_free_groups(AnntpGroup* gs, size_t count);
ANNTP_API ssize_t anntp_readdot(AnntpConnection* conn, char* buf, size_t maxlen);
ANNTP_API int anntp_readdot_cb(AnntpConnection* conn, AnntpLineCb cb, void* extra);
ANNTP_API int anntp_auth(AnntpConnection* conn, const char* login, const char* password);
@@ -284,6 +309,225 @@ anntp_freeconn(AnntpConnection* cv)
ANNTP_FREE(cv);
}
+int
+anntp_group(AnntpConnection* cv, const char* group, AnntpGroup* out)
+{
+ if (!cv || !group || !out)
+ return ANE_PARAMS;
+
+ char cmd[ANNTP_BUFSIZE];
+ snprintf(cmd, sizeof(cmd), "GROUP %s", group);
+
+ if (anntp_writeline(cv, cmd) <= 0)
+ return ANE_IO;
+
+ char line[ANNTP_BUFSIZE];
+ if (anntp_readline(cv, line, sizeof(line)) <= 0)
+ return ANE_IO;
+
+ /* expect a 221 for OK */
+ if (strncmp(line, "211", 3) != 0)
+ return ANE_PROTO;
+
+ int count;
+ int first, last;
+ char mode;
+ char name[128];
+
+ /* validate */
+ if (sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name) != 4)
+ return ANE_PROTO; /* malformed line */
+
+ strncpy(name, group, sizeof(name)-1);
+
+ out->name = ANNTP_STRDUP(name);
+ if (!out->name)
+ return ANE_IO;
+
+ out->first = (AnntpArticleNumber)first;
+ out->last = (AnntpArticleNumber)last;
+ out->count = (long)count;
+
+ out->mode = '?'; /* we can't really know the status of a group without LIST ACTIVE */
+
+ return ANE_OK;
+}
+
+ssize_t
+anntp_list(AnntpConnection* cv, AnntpGroup** out_groups)
+{
+ if (!cv || !out_groups) return ANNTPE(ANE_PARAMS);
+
+ size_t count = 0;
+ *out_groups = NULL;
+
+ if (anntp_writeline(cv, "LIST ACTIVE") <= 0)
+ return ANNTPE(ANE_IO);
+
+ char line[ANNTP_BUFSIZE];
+
+ if (anntp_readline(cv, line, sizeof(line)) <= 0)
+ return ANNTPE(ANE_IO);
+
+ size_t cap = 16;
+ AnntpGroup* groups = (AnntpGroup*)ANNTP_MALLOC(cap * sizeof(AnntpGroup));
+ if (!groups)
+ return ANNTPE(ANE_IO);
+
+ for (;;) {
+ ssize_t n = anntp_readline(cv, line, sizeof(line));
+ if (n <= 0)
+ goto fail;
+
+ if (strncmp(line, ".\r\n", 3) == 0)
+ break;
+
+ char name[256] = {0};
+ long last = 0, first = 0;
+ char mode = '?';
+
+ if (sscanf(line, "%255s %ld %ld %c", name, &last, &first, &mode) != 4)
+ continue;
+
+ if (count >= cap) {
+ cap *= 2;
+ AnntpGroup* tmp = (AnntpGroup*)realloc(groups, cap * sizeof(AnntpGroup));
+ if (!tmp)
+ goto fail;
+ groups = tmp;
+ }
+
+ groups[count].name = ANNTP_STRDUP(name);
+ if (!groups[count].name)
+ goto fail;
+
+ groups[count].first = (AnntpArticleNumber)first;
+ groups[count].last = (AnntpArticleNumber)last;
+ groups[count].mode = mode;
+
+ count++;
+ }
+
+ *out_groups = groups;
+ return (ssize_t)count;
+
+fail:
+ if (groups) {
+ for (size_t i = 0; i < count; i++) {
+ if (groups[i].name)
+ ANNTP_FREE(groups[i].name);
+ }
+ ANNTP_FREE(groups);
+ }
+ return ANNTPE(ANE_IO);
+}
+
+struct __ArticleStream {
+ AnntpArticle* out;
+ char* body;
+ size_t len;
+ size_t cap;
+ Bool headers_done;
+};
+
+static int
+_article_line_cb(const char* line, void* extra)
+{
+ struct __ArticleStream* s = (struct __ArticleStream*)extra;
+
+ if (!s->headers_done) {
+ if (line[0] == '\0') { /* blank line is end of headers */
+ s->headers_done = true;
+ return 0;
+ }
+
+ if (strncmp(line, "From:", 5) == 0 && !s->out->from)
+ s->out->from = ANNTP_STRDUP(line + 5);
+ else if (strncmp(line, "Date:", 5) == 0 && !s->out->date)
+ s->out->date = ANNTP_STRDUP(line + 5);
+ else if (strncmp(line, "Subject:", 8) == 0 && !s->out->subject)
+ s->out->subject = ANNTP_STRDUP(line + 8);
+ return 0;
+ }
+
+ size_t line_len = strlen(line);
+ if (s->len + line_len + 2 >= s->cap) {
+ s->cap *= 2;
+ char* tmp = (char*)realloc(s->body, s->cap);
+ if (!tmp) return 1; /* stop on OOM */
+ s->body = tmp;
+ }
+
+ memcpy(s->body + s->len, line, line_len);
+ s->len += line_len;
+ s->body[s->len++] = '\r';
+ s->body[s->len++] = '\n';
+
+ return 0;
+}
+
+ssize_t
+anntp_article(AnntpConnection* cv, AnntpArticleNumber n, AnntpArticle* out)
+{
+ if (!cv || !out)
+ return ANNTPE(ANE_PARAMS);
+
+ char cmd[128];
+ char line[ANNTP_BUFSIZE];
+
+ snprintf(cmd, sizeof(cmd), "ARTICLE %ld", (long)n);
+
+ if (anntp_writeline(cv, cmd) <= 0)
+ return ANNTPE(ANE_IO);
+
+ if (anntp_readline(cv, line, sizeof(line)) <= 0)
+ return ANNTPE(ANE_IO);
+
+ if (strncmp(line, "220", 3) != 0)
+ return ANNTPE(ANE_PROTO);
+
+ long num = 0;
+ char msgid[256] = {0};
+ sscanf(line, "220 %ld %255s", &num, msgid);
+
+ /* init out */
+ out->n = (AnntpArticleNumber)num;
+ out->id = ANNTP_STRDUP(msgid);
+ out->from = NULL;
+ out->date = NULL;
+ out->body = NULL;
+
+ if (!out->id)
+ return ANNTPE(ANE_IO);
+
+ /* setup streamer */
+ struct __ArticleStream stream;
+ stream.out = out;
+ stream.cap = ANNTP_BUFSIZE;
+ stream.len = 0;
+ stream.headers_done = false;
+ stream.body = (char*)ANNTP_MALLOC(stream.cap);
+ if (!stream.body)
+ return ANNTPE(ANE_IO);
+
+ ssize_t ret = anntp_readdot_cb(cv, _article_line_cb, &stream);
+ if (ret < 0) {
+ ANNTP_FREE(stream.body);
+ return ret;
+ }
+
+ /* finalize body */
+ if (stream.len > 0) {
+ stream.body[stream.len] = '\0';
+ out->body = stream.body;
+ } else {
+ ANNTP_FREE(stream.body);
+ out->body = NULL;
+ }
+
+ return stream.len;
+}
+
ssize_t
anntp_read(AnntpConnection* cv, uchar_t* buf, size_t len)
{
@@ -559,5 +803,32 @@ anntp_strerror(AnntpErrCode c)
}
}
+void
+anntp_free_article(AnntpArticle* a)
+{
+ if (!a) return;
+ if (a->id) { ANNTP_FREE(a->id); a->id = NULL; }
+ if (a->from) { ANNTP_FREE(a->from); a->from = NULL; }
+ if (a->date) { ANNTP_FREE(a->date); a->date = NULL; }
+ if (a->body) { ANNTP_FREE(a->body); a->body = NULL; }
+ if (a->subject) { ANNTP_FREE(a->subject); a->subject = NULL; }
+}
+
+void
+anntp_free_groups(AnntpGroup* groups, size_t count)
+{
+ if (!groups) return;
+
+ for (size_t i = 0; i < count; i++) {
+ if (groups[i].name) {
+ ANNTP_FREE(groups[i].name);
+ groups[i].name = NULL;
+ }
+ }
+
+ ANNTP_FREE(groups);
+}
+
+
#endif /* ANNTP_IMPLEMENTATION */
diff --git a/examples/nntpsh.c b/examples/nntpsh.c
@@ -16,6 +16,7 @@
#define ANNTP_IMPLEMENTATION
#include "../anntp.h"
+static char argv0[256];
static char host[0x100];
static char port[0x10];
static bool usetls;
@@ -52,13 +53,17 @@ cli(int argc, char** argv)
/* copy port */
strncpy(port, argv[2], sizeof(port) - 1);
port[sizeof(port) - 1] = '\0';
+
+ /* copy argv0 */
+ strncpy(argv0, argv[0], sizeof(argv0) - 1);
+ argv0[sizeof(argv0) - 1] = '\0';
}
void
setup(void) {
c = anntp_mkconn(host, port, usetls);
if (!c) {
- fprintf(stderr, "%s: can't create connection\n");
+ fprintf(stderr, "%s: can't create connection\n", argv0);
exit(EXIT_FAILURE);
}