aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--Makefile10
-rw-r--r--activitypub.c51
-rw-r--r--data.c2
-rw-r--r--sandbox.c31
-rw-r--r--snac.h1
-rw-r--r--tests/smtp.c24
-rw-r--r--utils.c57
-rw-r--r--xs_curl.h46
9 files changed, 175 insertions, 50 deletions
diff --git a/.gitignore b/.gitignore
index 7ead966..6ebb150 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
-*.o
+**/*.o
+tests/smtp
snac
diff --git a/Makefile b/Makefile
index 75d9562..764456d 100644
--- a/Makefile
+++ b/Makefile
@@ -8,11 +8,16 @@ snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \
activitypub.o html.o utils.o format.o upgrade.o mastoapi.o
$(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@
+test: tests/smtp
+
+tests/smtp: tests/smtp.o
+ $(CC) $(CFLAGS) -L$(PREFIX)/lib $< -lcurl $(LDFLAGS) -o $@
+
.c.o:
- $(CC) $(CFLAGS) $(CPPFLAGS) -I$(PREFIX)/include -c $<
+ $(CC) $(CFLAGS) $(CPPFLAGS) -I$(PREFIX)/include -c $< -o $@
clean:
- rm -rf *.o *.core snac makefile.depend
+ rm -rf *.o tests/*.o tests/smtp *.core snac makefile.depend
dep:
$(CC) -I$(PREFIX)/include -MM *.c > makefile.depend
@@ -64,3 +69,4 @@ utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h
webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \
http_codes.h
+tests/smtp.o: tests/smtp.c xs.h xs_curl.h
diff --git a/activitypub.c b/activitypub.c
index bcb733a..643baba 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -1017,17 +1017,20 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
xs *subject = xs_fmt("snac notify for @%s@%s",
xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host"));
- xs *from = xs_fmt("snac-daemon <snac-daemon@%s>", xs_dict_get(srv_config, "host"));
+ xs *from = xs_fmt("<snac-daemon@%s>", xs_dict_get(srv_config, "host"));
xs *header = xs_fmt(
- "From: %s\n"
+ "From: snac-daemon %s\n"
"To: %s\n"
"Subject: %s\n"
"\n",
from, email, subject);
- xs *email_body = xs_fmt("%s%s", header, body);
+ xs *mailinfo = xs_dict_new();
+ xs_dict_append(mailinfo, "from", from);
+ xs_dict_append(mailinfo, "to", email);
+ xs_dict_append(mailinfo, "body", xs_fmt("%s%s", header, body));
- enqueue_email(email_body, 0);
+ enqueue_email(mailinfo, 0);
}
/* telegram */
@@ -2524,32 +2527,20 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
}
-int send_email(const char *msg)
-/* invoke sendmail with email headers and body in msg */
+int send_email(const xs_dict *mailinfo)
+/* invoke curl */
{
- FILE *f;
- int status;
- int fds[2];
- pid_t pid;
- if (pipe(fds) == -1) return -1;
- pid = vfork();
- if (pid == -1) return -1;
- else if (pid == 0) {
- dup2(fds[0], 0);
- close(fds[0]);
- close(fds[1]);
- execl("/usr/sbin/sendmail", "sendmail", "-t", (char *) NULL);
- _exit(1);
- }
- close(fds[0]);
- if ((f = fdopen(fds[1], "w")) == NULL) {
- close(fds[1]);
- return -1;
- }
- fprintf(f, "%s\n", msg);
- fclose(f);
- if (waitpid(pid, &status, 0) == -1) return -1;
- return status;
+ const xs_dict *smtp_cfg = xs_dict_get(srv_config, "email_notifications");
+ const char
+ *url = xs_dict_get(smtp_cfg, "url"),
+ *user = xs_dict_get(smtp_cfg, "username"),
+ *pass = xs_dict_get(smtp_cfg, "password"),
+ *from = xs_dict_get(mailinfo, "from"),
+ *to = xs_dict_get(mailinfo, "to"),
+ *body = xs_dict_get(mailinfo, "body");
+ int smtp_port = parse_port(url, NULL);
+
+ return xs_smtp_request(url, user, pass, from, to, body, smtp_port == 465 || smtp_port == 587);
}
@@ -2812,7 +2803,7 @@ void process_queue_item(xs_dict *q_item)
else
if (strcmp(type, "email") == 0) {
/* send this email */
- const xs_str *msg = xs_dict_get(q_item, "message");
+ const xs_dict *msg = xs_dict_get(q_item, "message");
int retries = xs_number_get(xs_dict_get(q_item, "retries"));
if (!send_email(msg))
diff --git a/data.c b/data.c
index 9fc54a0..6995611 100644
--- a/data.c
+++ b/data.c
@@ -3249,7 +3249,7 @@ void enqueue_output_by_actor(snac *snac, const xs_dict *msg,
}
-void enqueue_email(const xs_str *msg, int retries)
+void enqueue_email(const xs_dict *msg, int retries)
/* enqueues an email message to be sent */
{
xs *qmsg = _new_qmsg("email", msg, retries);
diff --git a/sandbox.c b/sandbox.c
index 0fc48ad..5046104 100644
--- a/sandbox.c
+++ b/sandbox.c
@@ -8,8 +8,6 @@ void sbox_enter(const char *basedir)
{
const char *address = xs_dict_get(srv_config, "address");
- int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications"));
-
if (xs_is_true(xs_dict_get(srv_config, "disable_openbsd_security"))) {
srv_log(xs_dup("OpenBSD security disabled by admin"));
return;
@@ -24,9 +22,6 @@ void sbox_enter(const char *basedir)
unveil("/etc/ssl/cert.pem", "r");
unveil("/usr/share/zoneinfo", "r");
- if (smail)
- unveil("/usr/sbin/sendmail", "x");
-
if (*address == '/')
unveil(address, "rwc");
@@ -36,9 +31,6 @@ void sbox_enter(const char *basedir)
xs *p = xs_str_new("stdio rpath wpath cpath flock inet proc dns fattr");
- if (smail)
- p = xs_str_cat(p, " exec");
-
if (*address == '/')
p = xs_str_cat(p, " unix");
@@ -55,7 +47,7 @@ void sbox_enter(const char *basedir)
#include "landloc.h"
static
-LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail) {
+LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smtp_port) {
const unsigned long long
rd = LANDLOCK_ACCESS_FS_READ_DIR,
@@ -101,9 +93,6 @@ LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail)
LL_PATH(sdir, s);
}
- if (smail && mtime("/usr/sbin/sendmail") > 0)
- LL_PATH("/usr/sbin/sendmail", x);
-
if (*address != '/') {
unsigned short listen_port = xs_number_get(xs_dict_get(srv_config, "port"));
LL_PORT(listen_port, LANDLOCK_ACCESS_NET_BIND_TCP_COMPAT);
@@ -111,24 +100,34 @@ LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail)
LL_PORT(80, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
LL_PORT(443, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
+ if (smtp_port > 0)
+ LL_PORT((unsigned short)smtp_port, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
} LL_END
void sbox_enter(const char *basedir)
{
+ const xs_val *v;
+ const char *errstr;
const char *address = xs_dict_get(srv_config, "address");
-
- int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications"));
+ int smtp_port = -1;
if (xs_is_true(xs_dict_get(srv_config, "disable_sandbox"))) {
srv_debug(1, xs_dup("Linux sandbox disabled by admin"));
return;
}
- if (sbox_enter_linux_(basedir, address, smail) == 0)
+ if ((v = xs_dict_get(srv_config, "email_notifications")) &&
+ (v = xs_dict_get(v, "url"))) {
+ smtp_port = parse_port((const char *)v, &errstr);
+ if (errstr)
+ srv_debug(0, xs_fmt("Couldn't determine port from '%s': %s", (const char *)v, errstr));
+ }
+
+ if (sbox_enter_linux_(basedir, address, smtp_port) == 0)
srv_debug(1, xs_dup("Linux sandbox enabled"));
else
- srv_debug(1, xs_dup("Linux sandbox failed"));
+ srv_debug(0, xs_dup("Linux sandbox failed"));
}
#else /* defined(WITH_LINUX_SANDBOX) */
diff --git a/snac.h b/snac.h
index 2ff3235..5769b9b 100644
--- a/snac.h
+++ b/snac.h
@@ -421,6 +421,7 @@ void import_blocked_accounts_csv(snac *user, const char *fn);
void import_following_accounts_csv(snac *user, const char *fn);
void import_list_csv(snac *user, const char *fn);
void import_csv(snac *user);
+int parse_port(const char *url, const char **errstr);
typedef enum {
#define HTTP_STATUS(code, name, text) HTTP_STATUS_ ## name = code,
diff --git a/tests/smtp.c b/tests/smtp.c
new file mode 100644
index 0000000..1100a9d
--- /dev/null
+++ b/tests/smtp.c
@@ -0,0 +1,24 @@
+/* snac - A simple, minimalistic ActivityPub instance */
+/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */
+
+#define XS_IMPLEMENTATION
+#include "../xs.h"
+#include "../xs_curl.h"
+
+#define FROM "<snac-smtp-test@locahost>"
+
+int main(void) {
+ xs *to = xs_fmt("<%s@localhost>", getenv("USER")),
+ *body = xs_fmt(""
+ "To: %s \r\n"
+ "From: " FROM "\r\n"
+ "Subject: snac smtp test\r\n"
+ "\r\n"
+ "If you read this as an email, it probably worked!\r\n",
+ to);
+
+ return xs_smtp_request("smtp://localhost", NULL, NULL,
+ FROM,
+ to,
+ body, 0);
+} \ No newline at end of file
diff --git a/utils.c b/utils.c
index b799813..37281e2 100644
--- a/utils.c
+++ b/utils.c
@@ -911,3 +911,60 @@ void import_csv(snac *user)
else
snac_log(user, xs_fmt("Cannot open file %s", fn));
}
+
+static const struct {
+ const char *proto;
+ unsigned short default_port;
+} FALLBACK_PORTS[] = {
+ /* caution: https > http, smpts > smtp */
+ {"https", 443},
+ {"http", 80},
+ {"smtps", 465},
+ {"smtp", 25}
+};
+
+int parse_port(const char *url, const char **errstr)
+{
+ const char *col, *rcol;
+ int tmp, ret = -1;
+
+ if (errstr)
+ *errstr = NULL;
+
+ if (!(col = strchr(url, ':'))) {
+ if (errstr)
+ *errstr = "bad url";
+
+ return -1;
+ }
+
+ for (size_t i = 0; i < sizeof(FALLBACK_PORTS) / sizeof(*FALLBACK_PORTS); ++i) {
+ if (memcmp(url, FALLBACK_PORTS[i].proto, strlen(FALLBACK_PORTS[i].proto)) == 0) {
+ ret = FALLBACK_PORTS[i].default_port;
+ break;
+ }
+ }
+
+ if (!(rcol = strchr(col + 1, ':')))
+ rcol = col;
+
+ if (rcol) {
+ tmp = atoi(rcol + 1);
+ if (tmp == 0) {
+ if (ret != -1)
+ return ret;
+
+ if (errstr)
+ *errstr = strerror(errno);
+
+ return -1;
+ }
+
+ return tmp;
+ }
+
+ if (errstr)
+ *errstr = "unknown protocol";
+
+ return -1;
+}
diff --git a/xs_curl.h b/xs_curl.h
index f0cfd98..d98ac4c 100644
--- a/xs_curl.h
+++ b/xs_curl.h
@@ -9,6 +9,10 @@ xs_dict *xs_http_request(const char *method, const char *url,
const xs_str *body, int b_size, int *status,
xs_str **payload, int *p_size, int timeout);
+int xs_smtp_request(const char *url, const char *user, const char *pass,
+ const char *from, const char *to, const xs_str *body,
+ int use_ssl);
+
#ifdef XS_IMPLEMENTATION
#include <curl/curl.h>
@@ -194,6 +198,48 @@ xs_dict *xs_http_request(const char *method, const char *url,
return response;
}
+int xs_smtp_request(const char *url, const char *user, const char *pass,
+ const char *from, const char *to, const xs_str *body,
+ int use_ssl)
+{
+ CURL *curl;
+ CURLcode res = CURLE_OK;
+ struct curl_slist *rcpt = NULL;
+ struct _payload_data pd = {
+ .data = (char *)body,
+ .size = xs_size(body),
+ .offset = 0
+ };
+
+ curl = curl_easy_init();
+
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ if (user && pass) {
+ /* allow authless connections, to, e.g. localhost */
+ curl_easy_setopt(curl, CURLOPT_USERNAME, user);
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
+ }
+
+ if (use_ssl)
+ curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
+
+ curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from);
+
+ rcpt = curl_slist_append(rcpt, to);
+ curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpt);
+
+ curl_easy_setopt(curl, CURLOPT_READDATA, &pd);
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, _post_callback);
+ curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
+
+ res = curl_easy_perform(curl);
+
+ curl_easy_cleanup(curl);
+ curl_slist_free_all(rcpt);
+
+ return (int)res;
+}
+
#endif /* XS_IMPLEMENTATION */
#endif /* _XS_CURL_H */