1. added MB to deal with message in a safe way; 2. trim unified; 3. mail processing made safer;
This commit is contained in:
parent
8f2ec43dca
commit
b7dfef0a55
@ -1,4 +1,23 @@
|
|||||||
/* mail_processor.h - Header parsing, header rewriting, and UC 4.2-4.5 routing */
|
/*
|
||||||
|
* this is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* this is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
|
||||||
|
*
|
||||||
|
* (c) Alexander Vdolainen 2007, 2026 <alex@vapaa.xyz>
|
||||||
|
* subrelay: a minimalistic maillist server based on external IMAPs/SMTPs
|
||||||
|
*
|
||||||
|
* mail_processor.h: RFC 822/RFC 5322 header parsing/rewriting and routing
|
||||||
|
*
|
||||||
|
*/
|
||||||
#ifndef __MAIL_PROCESSOR_H__
|
#ifndef __MAIL_PROCESSOR_H__
|
||||||
#define __MAIL_PROCESSOR_H__
|
#define __MAIL_PROCESSOR_H__
|
||||||
|
|
||||||
@ -10,15 +29,13 @@
|
|||||||
typedef struct mail_processor {
|
typedef struct mail_processor {
|
||||||
subrelay_config_t *cfg;
|
subrelay_config_t *cfg;
|
||||||
subscriber_pool_t *subscribers;
|
subscriber_pool_t *subscribers;
|
||||||
pending_pool_t *pending;
|
pending_pool_t *pending;
|
||||||
} mail_processor_t;
|
} mail_processor_t;
|
||||||
|
|
||||||
int mail_processor_init(mail_processor_t *mp,
|
int mail_processor_init(mail_processor_t *mp, subrelay_config_t *cfg,
|
||||||
subrelay_config_t *cfg,
|
subscriber_pool_t *subscribers, pending_pool_t *pending);
|
||||||
subscriber_pool_t *subscribers,
|
|
||||||
pending_pool_t *pending);
|
|
||||||
|
|
||||||
/* Processes a single fetched message according to UC 4.2 - 4.5 */
|
/* Processes a single fetched message according to defined use cases */
|
||||||
int mail_processor_execute(mail_processor_t *mp, mail_message_t *msg);
|
int mail_processor_execute(mail_processor_t *mp, mail_message_t *msg);
|
||||||
|
|
||||||
#endif /* __MAIL_PROCESSOR_H__ */
|
#endif /* __MAIL_PROCESSOR_H__ */
|
||||||
|
|||||||
@ -27,5 +27,23 @@
|
|||||||
/* Build time constants */
|
/* Build time constants */
|
||||||
#define PENDING_ID_LEN 16
|
#define PENDING_ID_LEN 16
|
||||||
#define DEFAULT_PENDING_TTL 86400 /* default TTL for confirmation email */
|
#define DEFAULT_PENDING_TTL 86400 /* default TTL for confirmation email */
|
||||||
|
#define EMAILLEN_MAX 256 /* Max length of the email address */
|
||||||
|
#define TAGLEN_MAX 512 /* Max length of the tag subject in broadcast email */
|
||||||
|
|
||||||
|
/* message builder -- a safe growing buffer for message rebuilds */
|
||||||
|
typedef struct {
|
||||||
|
char *data;
|
||||||
|
size_t len;
|
||||||
|
size_t cap;
|
||||||
|
} message_builder_t;
|
||||||
|
|
||||||
|
/* MB functions */
|
||||||
|
int mb_reserve(message_builder_t *b, size_t additional);
|
||||||
|
int mb_append(message_builder_t *b, const void *data, size_t len);
|
||||||
|
int mb_append_cstr(message_builder_t *b, const char *s);
|
||||||
|
|
||||||
|
/* helper functions */
|
||||||
|
/* Trims whitespace and trailing \r\n */
|
||||||
|
char *strtrim_whitespace(char *str);
|
||||||
|
|
||||||
#endif /* __MISC_H__ */
|
#endif /* __MISC_H__ */
|
||||||
|
|||||||
@ -15,6 +15,7 @@ noinst_HEADERS = \
|
|||||||
subrelayd_SOURCES = \
|
subrelayd_SOURCES = \
|
||||||
subrelayd.c \
|
subrelayd.c \
|
||||||
splay.c \
|
splay.c \
|
||||||
|
misc.c \
|
||||||
subscribers.c \
|
subscribers.c \
|
||||||
pending.c \
|
pending.c \
|
||||||
configuration.c \
|
configuration.c \
|
||||||
|
|||||||
@ -24,26 +24,15 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <ilib/misc.h>
|
||||||
#include <ilib/configuration.h>
|
#include <ilib/configuration.h>
|
||||||
|
|
||||||
#define LINE_MAXLEN 1024
|
#define LINE_MAXLEN 1024
|
||||||
|
|
||||||
static char *trim(char *str)
|
|
||||||
{
|
|
||||||
while (isspace((unsigned char)*str)) str++;
|
|
||||||
if (*str == 0) return str;
|
|
||||||
|
|
||||||
char *end = str + strlen(str) - 1;
|
|
||||||
while (end > str && isspace((unsigned char)*end)) end--;
|
|
||||||
end[1] = '\0';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Strips quotes if value is wrapped in 'VALUE' or "VALUE" */
|
/* Strips quotes if value is wrapped in 'VALUE' or "VALUE" */
|
||||||
static char *strip_quotes(char *val)
|
static char *strip_quotes(char *val)
|
||||||
{
|
{
|
||||||
val = trim(val);
|
val = strtrim_whitespace(val);
|
||||||
size_t len = strlen(val);
|
size_t len = strlen(val);
|
||||||
|
|
||||||
if (len >= 2 && ((val[0] == '\'' && val[len - 1] == '\'') ||
|
if (len >= 2 && ((val[0] == '\'' && val[len - 1] == '\'') ||
|
||||||
@ -60,7 +49,7 @@ static int text2port(const char *value, int *port)
|
|||||||
errno = 0;
|
errno = 0;
|
||||||
long n = strtol(value, &end, 10);
|
long n = strtol(value, &end, 10);
|
||||||
|
|
||||||
if (value == end || errno == ERANGE || *trim(end) != '\0' ||
|
if (value == end || errno == ERANGE || *strtrim_whitespace(end) != '\0' ||
|
||||||
n < 1 || n > 65535)
|
n < 1 || n > 65535)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -101,7 +90,7 @@ int configuration_load(subrelay_config_t *cfg, const char *filepath)
|
|||||||
char *comment = strchr(line, '#');
|
char *comment = strchr(line, '#');
|
||||||
if (comment) *comment = '\0';
|
if (comment) *comment = '\0';
|
||||||
|
|
||||||
char *trimmed = trim(line);
|
char *trimmed = strtrim_whitespace(line);
|
||||||
if (*trimmed == '\0') continue; /* skip empty/comment lines */
|
if (*trimmed == '\0') continue; /* skip empty/comment lines */
|
||||||
|
|
||||||
/* requirement C004.2: <KEY>: '<VALUE>' */
|
/* requirement C004.2: <KEY>: '<VALUE>' */
|
||||||
@ -112,7 +101,7 @@ int configuration_load(subrelay_config_t *cfg, const char *filepath)
|
|||||||
}
|
}
|
||||||
|
|
||||||
*colon = '\0';
|
*colon = '\0';
|
||||||
char *key = trim(trimmed);
|
char *key = strtrim_whitespace(trimmed);
|
||||||
char *value = strip_quotes(colon + 1);
|
char *value = strip_quotes(colon + 1);
|
||||||
|
|
||||||
if (strcasecmp(key, "MAILLIST_ADDR") == 0) {
|
if (strcasecmp(key, "MAILLIST_ADDR") == 0) {
|
||||||
|
|||||||
16
src/local.c
16
src/local.c
@ -30,27 +30,15 @@
|
|||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <ilib/misc.h>
|
||||||
#include <ilib/local.h>
|
#include <ilib/local.h>
|
||||||
|
|
||||||
#define OUTBUF_MAX 2048
|
#define OUTBUF_MAX 2048
|
||||||
#define EMAILLEN_MAX 512
|
|
||||||
|
|
||||||
static char *trim(char *str)
|
|
||||||
{
|
|
||||||
while (isspace((unsigned char)*str)) str++;
|
|
||||||
if (*str == 0) return str;
|
|
||||||
|
|
||||||
char *end = str + strlen(str) - 1;
|
|
||||||
while (end > str && isspace((unsigned char)*end)) end--;
|
|
||||||
end[1] = '\0';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dispatch requests from local control tool */
|
/* dispatch requests from local control tool */
|
||||||
static void handle_ipc_command(local_ctrl_t *server, int client_fd, char *cmd_line)
|
static void handle_ipc_command(local_ctrl_t *server, int client_fd, char *cmd_line)
|
||||||
{
|
{
|
||||||
char *line = trim(cmd_line);
|
char *line = strtrim_whitespace(cmd_line);
|
||||||
if (*line == '\0') return;
|
if (*line == '\0') return;
|
||||||
|
|
||||||
char *cmd = strtok(line, " \t");
|
char *cmd = strtok(line, " \t");
|
||||||
|
|||||||
@ -1,4 +1,24 @@
|
|||||||
/* mail_processor.c - RFC 822 header parsing & routing engine */
|
/*
|
||||||
|
* this is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* this is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
|
||||||
|
*
|
||||||
|
* (c) Alexander Vdolainen 2007, 2026 <alex@vapaa.xyz>
|
||||||
|
* subrelay: a minimalistic maillist server based on external IMAPs/SMTPs
|
||||||
|
*
|
||||||
|
* mail_processor.c: RFC 822/RFC 5322 header parsing/rewriting and routing
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -6,272 +26,509 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <ilib/misc.h>
|
||||||
#include <ilib/mail_processor.h>
|
#include <ilib/mail_processor.h>
|
||||||
#include <ilib/template.h>
|
#include <ilib/template.h>
|
||||||
|
|
||||||
/* Trims whitespace and trailing \r\n */
|
|
||||||
static char *trim(char *str)
|
|
||||||
{
|
|
||||||
while (isspace((unsigned char)*str)) str++;
|
|
||||||
if (*str == 0) return str;
|
|
||||||
|
|
||||||
char *end = str + strlen(str) - 1;
|
|
||||||
while (end > str && isspace((unsigned char)*end)) end--;
|
|
||||||
end[1] = '\0';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Extract raw address from formats like "Alice <alice@domain.tld>" */
|
/* Extract raw address from formats like "Alice <alice@domain.tld>" */
|
||||||
static void extract_email_address(const char *raw, char *out, size_t out_len)
|
static int extract_email_address(const char *raw, char *out, size_t out_len)
|
||||||
{
|
{
|
||||||
const char *start = strchr(raw, '<');
|
if (!raw || !out || out_len == 0) return -1;
|
||||||
const char *end = strchr(raw, '>');
|
|
||||||
|
|
||||||
if (start && end && end > start) {
|
const char *start = strchr(raw, '<');
|
||||||
size_t len = end - start - 1;
|
const char *end = start ? strchr(start + 1, '>') : NULL;
|
||||||
if (len >= out_len) len = out_len - 1;
|
|
||||||
strncpy(out, start + 1, len);
|
if (start && end) {
|
||||||
|
raw = start + 1;
|
||||||
|
size_t len = (size_t)(end - raw);
|
||||||
|
if (len == 0 || len >= out_len) return -1;
|
||||||
|
|
||||||
|
memcpy(out, raw, len);
|
||||||
out[len] = '\0';
|
out[len] = '\0';
|
||||||
} else {
|
} else {
|
||||||
snprintf(out, out_len, "%s", raw);
|
size_t len = strlen(raw);
|
||||||
|
if (len >= out_len) return -1;
|
||||||
|
|
||||||
|
memcpy(out, raw, len + 1);
|
||||||
}
|
}
|
||||||
trim(out);
|
|
||||||
|
strtrim_whitespace(out);
|
||||||
|
|
||||||
|
if (out[0] == '\0') return -1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Case-insensitive header value extractor supporting multi-line folding */
|
/* Case-insensitive header value extractor supporting multi-line folding */
|
||||||
static char *get_header_value(const char *raw_msg, const char *header_name)
|
static char *get_header_value(const char *raw_msg, const char *header_name)
|
||||||
{
|
{
|
||||||
size_t hlen = strlen(header_name);
|
if (!raw_msg || !header_name || !*header_name) return NULL;
|
||||||
|
|
||||||
|
const size_t name_len = strlen(header_name);
|
||||||
const char *p = raw_msg;
|
const char *p = raw_msg;
|
||||||
|
char *value = NULL;
|
||||||
|
size_t value_len = 0;
|
||||||
|
|
||||||
while (*p) {
|
while (*p) {
|
||||||
/* End of headers block reached (double CRLF/LF) */
|
const char *line = p;
|
||||||
if (p[0] == '\r' && p[1] == '\n' && (p[2] == '\r' || p[2] == '\n')) break;
|
const char *lf = strchr(p, '\n');
|
||||||
if (p[0] == '\n' && p[1] == '\n') break;
|
const char *end = lf ? lf : p + strlen(p);
|
||||||
|
|
||||||
if (strncasecmp(p, header_name, hlen) == 0 && p[hlen] == ':') {
|
if (end > line && end[-1] == '\r') end--;
|
||||||
p += hlen + 1;
|
|
||||||
while (*p == ' ' || *p == '\t') p++;
|
|
||||||
|
|
||||||
const char *val_start = p;
|
/* Empty line marks the end of the header block. */
|
||||||
while (*p && *p != '\r' && *p != '\n') p++;
|
if (end == line) break;
|
||||||
|
|
||||||
size_t val_len = p - val_start;
|
if (*line == ' ' || *line == '\t') {
|
||||||
char *val = malloc(val_len + 1);
|
/* Continuation belongs to the preceding header. */
|
||||||
if (!val) return NULL;
|
if (value) {
|
||||||
|
const char *q = line;
|
||||||
|
while (q < end && (*q == ' ' || *q == '\t')) q++;
|
||||||
|
|
||||||
memcpy(val, val_start, val_len);
|
size_t add = (size_t)(end - q);
|
||||||
val[val_len] = '\0';
|
char *tmp = realloc(value, value_len + 1 + add + 1);
|
||||||
|
if (!tmp) {
|
||||||
|
free(value);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
return trim(val);
|
value = tmp;
|
||||||
|
value[value_len++] = ' ';
|
||||||
|
memcpy(value + value_len, q, add);
|
||||||
|
value_len += add;
|
||||||
|
value[value_len] = '\0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* A new header ends the value we were collecting. */
|
||||||
|
if (value) break;
|
||||||
|
|
||||||
|
const char *colon = memchr(line, ':', (size_t)(end - line));
|
||||||
|
if (colon && (size_t)(colon - line) == name_len &&
|
||||||
|
strncasecmp(line, header_name, name_len) == 0) {
|
||||||
|
const char *v = colon + 1;
|
||||||
|
while (v < end && (*v == ' ' || *v == '\t')) v++;
|
||||||
|
|
||||||
|
value_len = (size_t)(end - v);
|
||||||
|
value = malloc(value_len + 1);
|
||||||
|
if (!value) return NULL;
|
||||||
|
|
||||||
|
memcpy(value, v, value_len);
|
||||||
|
value[value_len] = '\0';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Move to next line */
|
p = lf ? lf + 1 : end;
|
||||||
while (*p && *p != '\n') p++;
|
if (!lf) break;
|
||||||
if (*p == '\n') p++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
if (value) strtrim_whitespace(value);
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checks if a header key should be stripped (E002 & E004) */
|
/* Reject values that could inject additional message headers. */
|
||||||
static int should_strip_header(const char *line)
|
static int header_value_is_safe(const char *s)
|
||||||
{
|
{
|
||||||
/* Advance past any leading CRLF/whitespace */
|
if (!s) return 0;
|
||||||
while (*line == '\r' || *line == '\n' || *line == ' ' || *line == '\t') line++;
|
|
||||||
|
|
||||||
static const char *strip_list[] = {
|
for (; *s; s++) {
|
||||||
"From:",
|
unsigned char c = (unsigned char)*s;
|
||||||
"Resent-From:",
|
if (c == '\r' || c == '\n' || c == 0x7f ||
|
||||||
"X-Original-From:",
|
(c < 0x20 && c != '\t'))
|
||||||
"Envelope-From:",
|
return 0;
|
||||||
"Reply-To:",
|
}
|
||||||
"Sender:",
|
|
||||||
"Precedence:",
|
|
||||||
"Auto-Submitted:",
|
|
||||||
"List-Id:",
|
|
||||||
"List-Unsubscribe:",
|
|
||||||
"List-Post:",
|
|
||||||
"List-Help:",
|
|
||||||
"List-Subscribe:",
|
|
||||||
"List-Archive:",
|
|
||||||
"DKIM-Signature:",
|
|
||||||
"DomainKey-Signature:",
|
|
||||||
"Authentication-Results:",
|
|
||||||
"Received:",
|
|
||||||
"Received-SPF:",
|
|
||||||
"Delivered-To:",
|
|
||||||
"Return-Path:",
|
|
||||||
"X-Received:",
|
|
||||||
"X-Spam-",
|
|
||||||
"X-Google-",
|
|
||||||
"X-Zoho-",
|
|
||||||
"X-Gm-",
|
|
||||||
"ARC-",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; strip_list[i] != NULL; i++) {
|
return 1;
|
||||||
if (strncasecmp(line, strip_list[i], strlen(strip_list[i])) == 0)
|
}
|
||||||
|
|
||||||
|
static int field_name_eq(const char *name, size_t name_len, const char *wanted)
|
||||||
|
{
|
||||||
|
return strlen(wanted) == name_len &&
|
||||||
|
strncasecmp(name, wanted, name_len) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checks if a header key should be stripped */
|
||||||
|
static int should_strip_header(const char *line, size_t line_len)
|
||||||
|
{
|
||||||
|
static const char *const exact[] = {
|
||||||
|
"From", "Resent-From", "X-Original-From", "Envelope-From",
|
||||||
|
"Reply-To", "Sender", "Precedence", "Auto-Submitted",
|
||||||
|
"List-Id", "List-Unsubscribe", "List-Post", "List-Help",
|
||||||
|
"List-Subscribe", "List-Archive", "DKIM-Signature",
|
||||||
|
"DomainKey-Signature", "Authentication-Results", "Received",
|
||||||
|
"Received-SPF", "Delivered-To", "Return-Path", "X-Received",
|
||||||
|
NULL};
|
||||||
|
|
||||||
|
for (size_t i = 0; exact[i]; i++) {
|
||||||
|
if (field_name_eq(line, line_len, exact[i]))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *const prefix[] = {
|
||||||
|
"X-Spam-", "X-Google-", "X-Zoho-", "X-Gm-", "ARC-",
|
||||||
|
NULL};
|
||||||
|
|
||||||
|
for (size_t i = 0; prefix[i]; i++) {
|
||||||
|
size_t n = strlen(prefix[i]);
|
||||||
|
if(line_len >= n && strncasecmp(line, prefix[i], n) == 0)
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int is_header_continuation(const char *line)
|
static int is_header_continuation(const char *line)
|
||||||
{
|
{
|
||||||
return (line[0] == ' ' || line[0] == '\t');
|
return line && (line[0] == ' ' || line[0] == '\t');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rebuilds raw email buffer: strips stale/auth headers, injects E001 headers */
|
/*
|
||||||
static char *rebuild_message_for_broadcast(mail_processor_t *mp, const char *raw_msg, size_t *out_len)
|
* Locate the earliest CRLF-CRLF or LF-LF separator. (RFC 822)
|
||||||
|
* Returns the start of the body and reports the separator length.
|
||||||
|
* If there is no separator, the body is considered empty.
|
||||||
|
*/
|
||||||
|
static const char *find_body_start(const char *msg, size_t *separator_len)
|
||||||
{
|
{
|
||||||
const char *body_start = strstr(raw_msg, "\r\n\r\n");
|
const char *crlf = strstr(msg, "\r\n\r\n");
|
||||||
if (!body_start) body_start = strstr(raw_msg, "\n\n");
|
const char *lf = strstr(msg, "\n\n");
|
||||||
if (body_start) {
|
|
||||||
body_start += (body_start[0] == '\r') ? 4 : 2;
|
if (crlf && (!lf || crlf <= lf)) {
|
||||||
} else {
|
*separator_len = 4;
|
||||||
body_start = raw_msg + strlen(raw_msg);
|
return crlf + 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Extract display name or email from original From: header */
|
if (lf) {
|
||||||
char orig_display[128] = "Subscriber";
|
*separator_len = 2;
|
||||||
char *raw_from = get_header_value(raw_msg, "From");
|
return lf + 2;
|
||||||
if (raw_from) {
|
}
|
||||||
char *bracket = strchr(raw_from, '<');
|
|
||||||
if (bracket && bracket > raw_from) {
|
*separator_len = 0;
|
||||||
size_t dlen = bracket - raw_from;
|
return msg + strlen(msg);
|
||||||
if (dlen >= sizeof(orig_display)) dlen = sizeof(orig_display) - 1;
|
}
|
||||||
strncpy(orig_display, raw_from, dlen);
|
|
||||||
orig_display[dlen] = '\0';
|
/*
|
||||||
trim(orig_display);
|
* Build a safe quoted display name from the original 'From' value.
|
||||||
if (orig_display[0] == '"' || orig_display[0] == '\'') {
|
* This deliberately uses a conservative fallback for malformed or
|
||||||
size_t slen = strlen(orig_display);
|
* control-character-containing values.
|
||||||
if (slen > 1 && (orig_display[slen - 1] == '"' || orig_display[slen - 1] == '\'')) {
|
*/
|
||||||
orig_display[slen - 1] = '\0';
|
static void get_display_name(const char *raw_msg, char *out, size_t out_len)
|
||||||
memmove(orig_display, orig_display + 1, slen - 1);
|
{
|
||||||
|
if (!out || out_len == 0) return;
|
||||||
|
|
||||||
|
snprintf(out, out_len, "%s", "Subscriber");
|
||||||
|
char *from = get_header_value(raw_msg, "From");
|
||||||
|
if (!from) return;
|
||||||
|
|
||||||
|
char *lt = strchr(from, '<');
|
||||||
|
if (lt) {
|
||||||
|
while (lt > from && isspace((unsigned char)lt[-1])) lt--;
|
||||||
|
|
||||||
|
size_t n = (size_t)(lt - from);
|
||||||
|
while (n && isspace((unsigned char)from[n - 1])) n--;
|
||||||
|
|
||||||
|
/* remove surrounding quotes, if any */
|
||||||
|
const char *start = from;
|
||||||
|
if (n >= 2 && ((from[0] == '"' && from[n - 1] == '"') ||
|
||||||
|
(from[0] == '\'' && from[n - 1] == '\''))) {
|
||||||
|
start++;
|
||||||
|
n -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n > 0 && n < out_len) {
|
||||||
|
int safe = 1;
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
unsigned char c = (unsigned char)start[i];
|
||||||
|
if (c < 0x20 || c == 0x7f) {
|
||||||
|
safe = 0;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if (safe) {
|
||||||
extract_email_address(raw_from, orig_display, sizeof(orig_display));
|
memcpy(out, start, n);
|
||||||
|
out[n] = '\0';
|
||||||
|
strtrim_whitespace(out);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
free(raw_from);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Extract local and domain parts */
|
|
||||||
char local_part[128] = {0};
|
|
||||||
char domain_part[128] = {0};
|
|
||||||
const char *at_sign = strchr(mp->cfg->maillist_addr, '@');
|
|
||||||
if (at_sign) {
|
|
||||||
size_t len = at_sign - mp->cfg->maillist_addr;
|
|
||||||
strncpy(local_part, mp->cfg->maillist_addr, len);
|
|
||||||
strcpy(domain_part, at_sign + 1);
|
|
||||||
} else {
|
} else {
|
||||||
strcpy(local_part, "subrelay");
|
/* no display name -- use the parsed address as the display text. */
|
||||||
strcpy(domain_part, "localhost");
|
char addr[EMAILLEN_MAX];
|
||||||
|
if (extract_email_address(from, addr, sizeof(addr)) == 0 &&
|
||||||
|
header_value_is_safe(addr))
|
||||||
|
snprintf(out, out_len, "%s", addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t buf_cap = strlen(raw_msg) + 4096;
|
free(from);
|
||||||
char *out = malloc(buf_cap);
|
|
||||||
if (!out) return NULL;
|
|
||||||
size_t pos = 0;
|
|
||||||
|
|
||||||
/* 1. Inject EXACTLY ONE From header line at the absolute top */
|
if (out[0] == '\0') snprintf(out, out_len, "%s", "Subscriber");
|
||||||
pos += snprintf(out + pos, buf_cap - pos,
|
return;
|
||||||
"From: \"%s via %s\" <%s>\r\n"
|
}
|
||||||
"Reply-To: <%s>\r\n"
|
|
||||||
"Sender: <%s>\r\n"
|
|
||||||
"Precedence: list\r\n"
|
|
||||||
"Auto-Submitted: auto-generated\r\n"
|
|
||||||
"List-Id: <%s>\r\n"
|
|
||||||
"List-Unsubscribe: <mailto:%s+unsubscribe@%s>\r\n"
|
|
||||||
"List-Post: <mailto:%s>\r\n",
|
|
||||||
orig_display, mp->cfg->maillist_name, mp->cfg->maillist_addr,
|
|
||||||
mp->cfg->maillist_addr,
|
|
||||||
mp->cfg->maillist_addr,
|
|
||||||
mp->cfg->maillist_addr,
|
|
||||||
local_part, domain_part,
|
|
||||||
mp->cfg->maillist_addr
|
|
||||||
);
|
|
||||||
|
|
||||||
const char *p = raw_msg;
|
/* append a quoted phrase, escaping quote and backslash */
|
||||||
int stripping_current_header = 0;
|
static int append_quoted_phrase(message_builder_t *b, const char *s)
|
||||||
|
{
|
||||||
|
if (mb_append_cstr(b, "\"") != 0) return -1;
|
||||||
|
|
||||||
|
for (; *s; s++) {
|
||||||
|
if (*s == '"' || *s == '\\') {
|
||||||
|
if (mb_append(b, "\\", 1) != 0) return -1;
|
||||||
|
}
|
||||||
|
if (mb_append(b, s, 1) != 0) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_append_cstr(b, "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Rebuild a message for broadcast.
|
||||||
|
*
|
||||||
|
* Rebuilds raw email buffer: strips stale/auth headers, injects headers
|
||||||
|
* Allocates a buffer in a heap of *out_len length (it sets it too), otherwise nil
|
||||||
|
*/
|
||||||
|
static char *rebuild_message_for_broadcast(mail_processor_t *mp, const char *raw_msg,
|
||||||
|
size_t *out_len)
|
||||||
|
{
|
||||||
|
if (out_len) *out_len = 0;
|
||||||
|
|
||||||
|
if (!mp || !mp->cfg || !raw_msg || !out_len) return NULL;
|
||||||
|
|
||||||
|
const char *list_addr = mp->cfg->maillist_addr;
|
||||||
|
const char *list_name = mp->cfg->maillist_name;
|
||||||
|
|
||||||
|
if (!header_value_is_safe(list_addr) ||
|
||||||
|
!header_value_is_safe(list_name)) return NULL;
|
||||||
|
|
||||||
|
/* Split the configured list address into local part and domain. */
|
||||||
|
const char *at = strchr(list_addr, '@');
|
||||||
|
if (!at || at == list_addr || at[1] == '\0') return NULL;
|
||||||
|
|
||||||
|
size_t local_len = (size_t)(at - list_addr);
|
||||||
|
char *local_part = malloc(local_len + 1);
|
||||||
|
if (!local_part) return NULL;
|
||||||
|
|
||||||
|
memcpy(local_part, list_addr, local_len);
|
||||||
|
local_part[local_len] = '\0';
|
||||||
|
const char *domain_part = at + 1;
|
||||||
|
|
||||||
|
if (!header_value_is_safe(local_part) || !header_value_is_safe(domain_part)) {
|
||||||
|
free(local_part);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char display[EMAILLEN_MAX];
|
||||||
|
get_display_name(raw_msg, display, sizeof(display));
|
||||||
|
|
||||||
|
message_builder_t out = {0};
|
||||||
|
int failed = 0;
|
||||||
|
|
||||||
|
/* Add list-controlled headers first. */
|
||||||
|
if (mb_append_cstr(&out, "From: ") != 0 ||
|
||||||
|
append_quoted_phrase(&out, display) != 0 ||
|
||||||
|
mb_append_cstr(&out, " via ") != 0 ||
|
||||||
|
append_quoted_phrase(&out, list_name) != 0 ||
|
||||||
|
mb_append_cstr(&out, " <") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_addr) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\nReply-To: <") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_addr) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\nSender: <") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_addr) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\n"
|
||||||
|
"Precedence: list\r\n"
|
||||||
|
"Auto-Submitted: auto-generated\r\n"
|
||||||
|
"List-Id: <") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_addr) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\n"
|
||||||
|
"List-Unsubscribe: <mailto:") != 0 ||
|
||||||
|
mb_append_cstr(&out, local_part) != 0 ||
|
||||||
|
mb_append_cstr(&out, "+unsubscribe@") != 0 ||
|
||||||
|
mb_append_cstr(&out, domain_part) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\nList-Post: <mailto:") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_addr) != 0 ||
|
||||||
|
mb_append_cstr(&out, ">\r\n"))
|
||||||
|
failed = 1;
|
||||||
|
|
||||||
|
free(local_part);
|
||||||
|
if (failed) goto error;
|
||||||
|
|
||||||
|
size_t separator_len = 0;
|
||||||
|
const char *body_start = find_body_start(raw_msg, &separator_len);
|
||||||
|
const char *headers_end = body_start - separator_len;
|
||||||
int has_subject = 0;
|
int has_subject = 0;
|
||||||
|
int subject_tagged = 0;
|
||||||
|
|
||||||
/* 2. Line-by-line header processing */
|
/*
|
||||||
while (p < body_start) {
|
* Read and copy complete header fields. A field consists of its initial
|
||||||
const char *line_end = strchr(p, '\n');
|
* line plus all following continuation lines. Malformed header lines
|
||||||
if (!line_end || line_end >= body_start) break;
|
* cause failure rather than being copied into the rebuilt message.
|
||||||
|
*/
|
||||||
|
const char *p = raw_msg;
|
||||||
|
while (p < headers_end) {
|
||||||
|
const char *line_start = p;
|
||||||
|
const char *lf = memchr(p, '\n', (size_t)(headers_end - p));
|
||||||
|
const char *line_end = lf ? lf + 1 : headers_end;
|
||||||
|
|
||||||
size_t line_len = line_end - p + 1;
|
/* Empty line before the detected separator: stop header parsing. */
|
||||||
|
const char *content_end = lf ? lf : line_end;
|
||||||
|
if (content_end > line_start && content_end[-1] == '\r') content_end--;
|
||||||
|
if (content_end == line_start) break;
|
||||||
|
|
||||||
/* Skip leading \r if present at start of line */
|
/* Header field names cannot begin with whitespace. */
|
||||||
const char *check_ptr = p;
|
if (*line_start == ' ' || *line_start == '\t') {
|
||||||
if (*check_ptr == '\r') check_ptr++;
|
failed = 1;
|
||||||
|
goto error;
|
||||||
if (is_header_continuation(check_ptr)) {
|
|
||||||
if (stripping_current_header) {
|
|
||||||
p = line_end + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (should_strip_header(check_ptr)) {
|
|
||||||
stripping_current_header = 1;
|
|
||||||
p = line_end + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
stripping_current_header = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subject Tagging (E003) */
|
const char *colon = memchr(line_start, ':', (size_t)(content_end - line_start));
|
||||||
if (strncasecmp(check_ptr, "Subject:", 8) == 0) {
|
if (!colon || colon == line_start) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t name_len = (size_t)(colon - line_start);
|
||||||
|
for (size_t i = 0; i < name_len; i++) {
|
||||||
|
unsigned char c = (unsigned char)line_start[i];
|
||||||
|
if (!(isalnum(c) || c == '-')) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *field_end = line_end;
|
||||||
|
|
||||||
|
/* Consume continuation lines belonging to this field. */
|
||||||
|
while (field_end < headers_end) {
|
||||||
|
const char *next_lf = memchr(field_end, '\n', (size_t)(headers_end - field_end));
|
||||||
|
const char *next_end = next_lf ? next_lf + 1 : headers_end;
|
||||||
|
const char *next_content_end = next_lf ? next_lf : next_end;
|
||||||
|
|
||||||
|
if (next_content_end > field_end && next_content_end[-1] == '\r')
|
||||||
|
next_content_end--;
|
||||||
|
if (next_content_end == field_end || (*field_end != ' ' && *field_end != '\t'))
|
||||||
|
break;
|
||||||
|
|
||||||
|
field_end = next_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_subject = field_name_eq(line_start, name_len, "Subject");
|
||||||
|
int strip = should_strip_header(line_start, name_len);
|
||||||
|
|
||||||
|
if (is_subject) {
|
||||||
has_subject = 1;
|
has_subject = 1;
|
||||||
char tag[256];
|
|
||||||
snprintf(tag, sizeof(tag), "[%s]", mp->cfg->maillist_name);
|
|
||||||
|
|
||||||
if (!strstr(check_ptr, tag)) {
|
/* unfold the value only for duplicate tag detection. */
|
||||||
pos += snprintf(out + pos, buf_cap - pos, "Subject: %s ", tag);
|
message_builder_t unfolded = {0};
|
||||||
/* Advance check_ptr past "Subject:" */
|
const char *v = colon + 1;
|
||||||
const char *subj_val = check_ptr + 8;
|
while (v < content_end && (*v == ' ' || *v == '\t')) v++;
|
||||||
size_t val_len = (line_end - subj_val + 1);
|
|
||||||
memcpy(out + pos, subj_val, val_len);
|
if (mb_append(&unfolded, v, (size_t)(content_end - v)) != 0) {
|
||||||
pos += val_len;
|
free(unfolded.data);
|
||||||
out[pos] = '\0';
|
failed = 1;
|
||||||
p = line_end + 1;
|
goto error;
|
||||||
continue;
|
}
|
||||||
|
|
||||||
|
const char *q = line_end;
|
||||||
|
while (q < field_end) {
|
||||||
|
const char *next_lf = memchr(q, '\n', (size_t)(field_end - q));
|
||||||
|
const char *next_end = next_lf ? next_lf + 1 : field_end;
|
||||||
|
const char *next_content_end = next_lf ? next_lf : next_end;
|
||||||
|
|
||||||
|
if (next_content_end > q && next_content_end[-1] == '\r')
|
||||||
|
next_content_end--;
|
||||||
|
|
||||||
|
while (q < next_content_end && (*q == ' ' || *q == '\t')) q++;
|
||||||
|
|
||||||
|
if (mb_append(&unfolded, " ", 1) != 0 ||
|
||||||
|
mb_append(&unfolded, q, (size_t)(next_content_end - q)) != 0) {
|
||||||
|
free(unfolded.data);
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
q = next_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* IMPROVE: make subject tagging configurable */
|
||||||
|
char tag[TAGLEN_MAX];
|
||||||
|
int n = snprintf(tag, sizeof(tag), "[%s]", list_name);
|
||||||
|
if (n < 0 || (size_t)n >= sizeof(tag)) {
|
||||||
|
free(unfolded.data);
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unfolded.data && strcasestr(unfolded.data, tag) != NULL)
|
||||||
|
subject_tagged = 1;
|
||||||
|
|
||||||
|
free(unfolded.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strip) {
|
||||||
|
if (is_subject && !subject_tagged) {
|
||||||
|
if (mb_append_cstr(&out, "Subject: [") != 0 ||
|
||||||
|
mb_append_cstr(&out, list_name) != 0 ||
|
||||||
|
mb_append_cstr(&out, "] ") != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Copy the original subject value and its folded lines. */
|
||||||
|
if (mb_append(&out, colon + 1, (size_t)(field_end - (colon + 1))) != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The copied field includes its original newline(s). If the
|
||||||
|
* input used LF, keep that style for this field.
|
||||||
|
*/
|
||||||
|
if (field_end == headers_end || (field_end > raw_msg && field_end[-1] != '\n')) {
|
||||||
|
if (mb_append_cstr(&out, "\r\n") != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (mb_append(&out, line_start, (size_t)(field_end - line_start)) != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field_end == headers_end || (field_end > raw_msg && field_end[-1] != '\n')) {
|
||||||
|
if (mb_append_cstr(&out, "\r\n") != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy clean header */
|
p = field_end;
|
||||||
if (pos + line_len < buf_cap) {
|
|
||||||
memcpy(out + pos, p, line_len);
|
|
||||||
pos += line_len;
|
|
||||||
out[pos] = '\0';
|
|
||||||
}
|
|
||||||
p = line_end + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!has_subject) {
|
if (!has_subject) {
|
||||||
pos += snprintf(out + pos, buf_cap - pos,
|
if (mb_append_cstr(&out, "Subject: [") != 0 ||
|
||||||
"Subject: [%s] (No Subject)\r\n", mp->cfg->maillist_name);
|
mb_append_cstr(&out, list_name) != 0 ||
|
||||||
|
mb_append_cstr(&out, "] (No Subject)\r\n") != 0) {
|
||||||
|
failed = 1;
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. Terminate Header Block with CRLF and append Body */
|
/* End headers, then append the original body unchanged. */
|
||||||
pos += snprintf(out + pos, buf_cap - pos, "\r\n");
|
if (mb_append_cstr(&out, "\r\n") != 0 || mb_append(&out, body_start, strlen(body_start)) != 0) {
|
||||||
size_t body_len = strlen(body_start);
|
failed = 1;
|
||||||
if (pos + body_len < buf_cap) {
|
goto error;
|
||||||
memcpy(out + pos, body_start, body_len);
|
|
||||||
pos += body_len;
|
|
||||||
out[pos] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_len = pos;
|
*out_len = out.len;
|
||||||
return out;
|
return out.data;
|
||||||
|
|
||||||
|
error:
|
||||||
|
free(out.data);
|
||||||
|
*out_len = 0;
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int mail_processor_init(mail_processor_t *mp, subrelay_config_t *cfg, subscriber_pool_t *subscribers, pending_pool_t *pending)
|
int mail_processor_init(mail_processor_t *mp, subrelay_config_t *cfg,
|
||||||
|
subscriber_pool_t *subscribers, pending_pool_t *pending)
|
||||||
{
|
{
|
||||||
if (!mp || !cfg || !subscribers || !pending) return -EINVAL;
|
if (!mp || !cfg || !subscribers || !pending) return -EINVAL;
|
||||||
mp->cfg = cfg;
|
mp->cfg = cfg;
|
||||||
|
|||||||
83
src/misc.c
Normal file
83
src/misc.c
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* this is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* this is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
* See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.";
|
||||||
|
*
|
||||||
|
* (c) Alexander Vdolainen 2007, 2026 <alex@vapaa.xyz>
|
||||||
|
* subrelay: a minimalistic maillist server based on external IMAPs/SMTPs
|
||||||
|
*
|
||||||
|
* misc.c: misc definitions and functions
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <ilib/misc.h>
|
||||||
|
|
||||||
|
/* Trims whitespace and trailing \r\n */
|
||||||
|
char *strtrim_whitespace(char *str)
|
||||||
|
{
|
||||||
|
while (isspace((unsigned char)*str)) str++;
|
||||||
|
if (*str == 0) return str;
|
||||||
|
|
||||||
|
char *end = str + strlen(str) - 1;
|
||||||
|
while (end > str && isspace((unsigned char)*end)) end--;
|
||||||
|
end[1] = '\0';
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MB ops */
|
||||||
|
int mb_reserve(message_builder_t *b, size_t additional)
|
||||||
|
{
|
||||||
|
if (!b || additional > SIZE_MAX - b->len - 1) return -1;
|
||||||
|
|
||||||
|
size_t needed = b->len + additional + 1;
|
||||||
|
if (needed <= b->cap)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t cap = b->cap ? b->cap : 1024;
|
||||||
|
while (cap < needed) {
|
||||||
|
if (cap > SIZE_MAX / 2) {
|
||||||
|
cap = needed;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cap *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *tmp = realloc(b->data, cap);
|
||||||
|
if (!tmp) return -1;
|
||||||
|
|
||||||
|
b->data = tmp;
|
||||||
|
b->cap = cap;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mb_append(message_builder_t *b, const void *data, size_t len)
|
||||||
|
{
|
||||||
|
if ((!data && len) || mb_reserve(b, len) != 0) return -1;
|
||||||
|
|
||||||
|
if (len) memcpy(b->data + b->len, data, len);
|
||||||
|
|
||||||
|
b->len += len;
|
||||||
|
b->data[b->len] = '\0';
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int mb_append_cstr(message_builder_t *b, const char *s)
|
||||||
|
{
|
||||||
|
return s ? mb_append(b, s, strlen(s)) : -1;
|
||||||
|
}
|
||||||
@ -39,18 +39,6 @@ static long pending_cmp(const void *a, const void *b)
|
|||||||
return strcmp(s1, s2);
|
return strcmp(s1, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char *trim_whitespace(char *str)
|
|
||||||
{
|
|
||||||
while (isspace((unsigned char)*str)) str++;
|
|
||||||
if (*str == 0) return str;
|
|
||||||
|
|
||||||
char *end = str + strlen(str) - 1;
|
|
||||||
while (end > str && isspace((unsigned char)*end)) end--;
|
|
||||||
end[1] = '\0';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Generates a secure random hex token via /dev/urandom */
|
/* Generates a secure random hex token via /dev/urandom */
|
||||||
static void generate_token(char *buf, size_t len)
|
static void generate_token(char *buf, size_t len)
|
||||||
{
|
{
|
||||||
@ -188,7 +176,7 @@ int pending_load_file(pending_pool_t *pool)
|
|||||||
time_t now = time(NULL);
|
time_t now = time(NULL);
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
char *str = trim_whitespace(line);
|
char *str = strtrim_whitespace(line);
|
||||||
if (*str == '\0' || *str == '#') continue;
|
if (*str == '\0' || *str == '#') continue;
|
||||||
|
|
||||||
/* File line format: TOKEN TIMESTAMP EMAIL */
|
/* File line format: TOKEN TIMESTAMP EMAIL */
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <ilib/misc.h>
|
||||||
#include <ilib/subscribers.h>
|
#include <ilib/subscribers.h>
|
||||||
|
|
||||||
/* Case-insensitive string comparison for email addresses */
|
/* Case-insensitive string comparison for email addresses */
|
||||||
@ -38,20 +39,6 @@ static long subscriber_cmp(const void *a, const void *b)
|
|||||||
return strcasecmp(s1, s2);
|
return strcasecmp(s1, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Helper to trim leading/trailing whitespace and newlines */
|
|
||||||
static char *trim_whitespace(char *str)
|
|
||||||
{
|
|
||||||
char *end;
|
|
||||||
while (isspace((unsigned char)*str)) str++;
|
|
||||||
if (*str == 0) return str;
|
|
||||||
|
|
||||||
end = str + strlen(str) - 1;
|
|
||||||
while (end > str && isspace((unsigned char)*end)) end--;
|
|
||||||
end[1] = '\0';
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
int subscriber_pool_init(subscriber_pool_t *pool, const char *filepath)
|
int subscriber_pool_init(subscriber_pool_t *pool, const char *filepath)
|
||||||
{
|
{
|
||||||
if (!pool || !filepath) return -EINVAL;
|
if (!pool || !filepath) return -EINVAL;
|
||||||
@ -125,7 +112,7 @@ int subscriber_load_file(subscriber_pool_t *pool)
|
|||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
char *email = trim_whitespace(line);
|
char *email = strtrim_whitespace(line);
|
||||||
/* Ignore empty lines and comment lines starting with '#' */
|
/* Ignore empty lines and comment lines starting with '#' */
|
||||||
if (*email == '\0' || *email == '#') continue;
|
if (*email == '\0' || *email == '#') continue;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user