summaryrefslogtreecommitdiff
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 @@
1*.o 1**/*.o
2tests/smtp
2snac 3snac
diff --git a/Makefile b/Makefile
index 178cfdd..4ba6862 100644
--- a/Makefile
+++ b/Makefile
@@ -8,11 +8,16 @@ snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \
8 activitypub.o html.o utils.o format.o upgrade.o mastoapi.o 8 activitypub.o html.o utils.o format.o upgrade.o mastoapi.o
9 $(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ 9 $(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@
10 10
11test: tests/smtp
12
13tests/smtp: tests/smtp.o
14 $(CC) $(CFLAGS) -L$(PREFIX)/lib $< -lcurl $(LDFLAGS) -o $@
15
11.c.o: 16.c.o:
12 $(CC) $(CFLAGS) $(CPPFLAGS) -I$(PREFIX)/include -c $< 17 $(CC) $(CFLAGS) $(CPPFLAGS) -I$(PREFIX)/include -c $< -o $@
13 18
14clean: 19clean:
15 rm -rf *.o *.core snac makefile.depend 20 rm -rf *.o tests/*.o tests/smtp *.core snac makefile.depend
16 21
17dep: 22dep:
18 $(CC) -I$(PREFIX)/include -MM *.c > makefile.depend 23 $(CC) -I$(PREFIX)/include -MM *.c > makefile.depend
@@ -71,3 +76,4 @@ utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
71 xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h 76 xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h
72webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \ 77webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \
73 http_codes.h 78 http_codes.h
79tests/smtp.o: tests/smtp.c xs.h xs_curl.h
diff --git a/activitypub.c b/activitypub.c
index 104110d..a209abd 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -1017,17 +1017,20 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor,
1017 1017
1018 xs *subject = xs_fmt("snac notify for @%s@%s", 1018 xs *subject = xs_fmt("snac notify for @%s@%s",
1019 xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host")); 1019 xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host"));
1020 xs *from = xs_fmt("snac-daemon <snac-daemon@%s>", xs_dict_get(srv_config, "host")); 1020 xs *from = xs_fmt("<snac-daemon@%s>", xs_dict_get(srv_config, "host"));
1021 xs *header = xs_fmt( 1021 xs *header = xs_fmt(
1022 "From: %s\n" 1022 "From: snac-daemon %s\n"
1023 "To: %s\n" 1023 "To: %s\n"
1024 "Subject: %s\n" 1024 "Subject: %s\n"
1025 "\n", 1025 "\n",
1026 from, email, subject); 1026 from, email, subject);
1027 1027
1028 xs *email_body = xs_fmt("%s%s", header, body); 1028 xs *mailinfo = xs_dict_new();
1029 mailinfo = xs_dict_append(mailinfo, "from", from);
1030 mailinfo = xs_dict_append(mailinfo, "to", email);
1031 mailinfo = xs_dict_append(mailinfo, "body", xs_fmt("%s%s", header, body));
1029 1032
1030 enqueue_email(email_body, 0); 1033 enqueue_email(mailinfo, 0);
1031 } 1034 }
1032 1035
1033 /* telegram */ 1036 /* telegram */
@@ -2524,32 +2527,20 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
2524} 2527}
2525 2528
2526 2529
2527int send_email(const char *msg) 2530int send_email(const xs_dict *mailinfo)
2528/* invoke sendmail with email headers and body in msg */ 2531/* invoke curl */
2529{ 2532{
2530 FILE *f; 2533 const xs_dict *smtp_cfg = xs_dict_get(srv_config, "email_notifications");
2531 int status; 2534 const char
2532 int fds[2]; 2535 *url = xs_dict_get(smtp_cfg, "url"),
2533 pid_t pid; 2536 *user = xs_dict_get(smtp_cfg, "username"),
2534 if (pipe(fds) == -1) return -1; 2537 *pass = xs_dict_get(smtp_cfg, "password"),
2535 pid = vfork(); 2538 *from = xs_dict_get(mailinfo, "from"),
2536 if (pid == -1) return -1; 2539 *to = xs_dict_get(mailinfo, "to"),
2537 else if (pid == 0) { 2540 *body = xs_dict_get(mailinfo, "body");
2538 dup2(fds[0], 0); 2541 int smtp_port = parse_port(url, NULL);
2539 close(fds[0]); 2542
2540 close(fds[1]); 2543 return xs_smtp_request(url, user, pass, from, to, body, smtp_port == 465 || smtp_port == 587);
2541 execl("/usr/sbin/sendmail", "sendmail", "-t", (char *) NULL);
2542 _exit(1);
2543 }
2544 close(fds[0]);
2545 if ((f = fdopen(fds[1], "w")) == NULL) {
2546 close(fds[1]);
2547 return -1;
2548 }
2549 fprintf(f, "%s\n", msg);
2550 fclose(f);
2551 if (waitpid(pid, &status, 0) == -1) return -1;
2552 return status;
2553} 2544}
2554 2545
2555 2546
@@ -2820,7 +2811,7 @@ void process_queue_item(xs_dict *q_item)
2820 else 2811 else
2821 if (strcmp(type, "email") == 0) { 2812 if (strcmp(type, "email") == 0) {
2822 /* send this email */ 2813 /* send this email */
2823 const xs_str *msg = xs_dict_get(q_item, "message"); 2814 const xs_dict *msg = xs_dict_get(q_item, "message");
2824 int retries = xs_number_get(xs_dict_get(q_item, "retries")); 2815 int retries = xs_number_get(xs_dict_get(q_item, "retries"));
2825 2816
2826 if (!send_email(msg)) 2817 if (!send_email(msg))
diff --git a/data.c b/data.c
index ce040dd..ae85aaf 100644
--- a/data.c
+++ b/data.c
@@ -3276,7 +3276,7 @@ void enqueue_output_by_actor(snac *snac, const xs_dict *msg,
3276} 3276}
3277 3277
3278 3278
3279void enqueue_email(const xs_str *msg, int retries) 3279void enqueue_email(const xs_dict *msg, int retries)
3280/* enqueues an email message to be sent */ 3280/* enqueues an email message to be sent */
3281{ 3281{
3282 xs *qmsg = _new_qmsg("email", msg, retries); 3282 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)
8{ 8{
9 const char *address = xs_dict_get(srv_config, "address"); 9 const char *address = xs_dict_get(srv_config, "address");
10 10
11 int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications"));
12
13 if (xs_is_true(xs_dict_get(srv_config, "disable_openbsd_security"))) { 11 if (xs_is_true(xs_dict_get(srv_config, "disable_openbsd_security"))) {
14 srv_log(xs_dup("OpenBSD security disabled by admin")); 12 srv_log(xs_dup("OpenBSD security disabled by admin"));
15 return; 13 return;
@@ -24,9 +22,6 @@ void sbox_enter(const char *basedir)
24 unveil("/etc/ssl/cert.pem", "r"); 22 unveil("/etc/ssl/cert.pem", "r");
25 unveil("/usr/share/zoneinfo", "r"); 23 unveil("/usr/share/zoneinfo", "r");
26 24
27 if (smail)
28 unveil("/usr/sbin/sendmail", "x");
29
30 if (*address == '/') 25 if (*address == '/')
31 unveil(address, "rwc"); 26 unveil(address, "rwc");
32 27
@@ -36,9 +31,6 @@ void sbox_enter(const char *basedir)
36 31
37 xs *p = xs_str_new("stdio rpath wpath cpath flock inet proc dns fattr"); 32 xs *p = xs_str_new("stdio rpath wpath cpath flock inet proc dns fattr");
38 33
39 if (smail)
40 p = xs_str_cat(p, " exec");
41
42 if (*address == '/') 34 if (*address == '/')
43 p = xs_str_cat(p, " unix"); 35 p = xs_str_cat(p, " unix");
44 36
@@ -55,7 +47,7 @@ void sbox_enter(const char *basedir)
55#include "landloc.h" 47#include "landloc.h"
56 48
57static 49static
58LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail) { 50LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smtp_port) {
59 51
60 const unsigned long long 52 const unsigned long long
61 rd = LANDLOCK_ACCESS_FS_READ_DIR, 53 rd = LANDLOCK_ACCESS_FS_READ_DIR,
@@ -101,9 +93,6 @@ LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail)
101 LL_PATH(sdir, s); 93 LL_PATH(sdir, s);
102 } 94 }
103 95
104 if (smail && mtime("/usr/sbin/sendmail") > 0)
105 LL_PATH("/usr/sbin/sendmail", x);
106
107 if (*address != '/') { 96 if (*address != '/') {
108 unsigned short listen_port = xs_number_get(xs_dict_get(srv_config, "port")); 97 unsigned short listen_port = xs_number_get(xs_dict_get(srv_config, "port"));
109 LL_PORT(listen_port, LANDLOCK_ACCESS_NET_BIND_TCP_COMPAT); 98 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)
111 100
112 LL_PORT(80, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT); 101 LL_PORT(80, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
113 LL_PORT(443, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT); 102 LL_PORT(443, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
103 if (smtp_port > 0)
104 LL_PORT((unsigned short)smtp_port, LANDLOCK_ACCESS_NET_CONNECT_TCP_COMPAT);
114 105
115} LL_END 106} LL_END
116 107
117void sbox_enter(const char *basedir) 108void sbox_enter(const char *basedir)
118{ 109{
110 const xs_val *v;
111 const char *errstr;
119 const char *address = xs_dict_get(srv_config, "address"); 112 const char *address = xs_dict_get(srv_config, "address");
120 113 int smtp_port = -1;
121 int smail = !xs_is_true(xs_dict_get(srv_config, "disable_email_notifications"));
122 114
123 if (xs_is_true(xs_dict_get(srv_config, "disable_sandbox"))) { 115 if (xs_is_true(xs_dict_get(srv_config, "disable_sandbox"))) {
124 srv_debug(1, xs_dup("Linux sandbox disabled by admin")); 116 srv_debug(1, xs_dup("Linux sandbox disabled by admin"));
125 return; 117 return;
126 } 118 }
127 119
128 if (sbox_enter_linux_(basedir, address, smail) == 0) 120 if ((v = xs_dict_get(srv_config, "email_notifications")) &&
121 (v = xs_dict_get(v, "url"))) {
122 smtp_port = parse_port((const char *)v, &errstr);
123 if (errstr)
124 srv_debug(0, xs_fmt("Couldn't determine port from '%s': %s", (const char *)v, errstr));
125 }
126
127 if (sbox_enter_linux_(basedir, address, smtp_port) == 0)
129 srv_debug(1, xs_dup("Linux sandbox enabled")); 128 srv_debug(1, xs_dup("Linux sandbox enabled"));
130 else 129 else
131 srv_debug(1, xs_dup("Linux sandbox failed")); 130 srv_debug(0, xs_dup("Linux sandbox failed"));
132} 131}
133 132
134#else /* defined(WITH_LINUX_SANDBOX) */ 133#else /* defined(WITH_LINUX_SANDBOX) */
diff --git a/snac.h b/snac.h
index ac1abcd..92ffb3f 100644
--- a/snac.h
+++ b/snac.h
@@ -427,6 +427,7 @@ void import_blocked_accounts_csv(snac *user, const char *fn);
427void import_following_accounts_csv(snac *user, const char *fn); 427void import_following_accounts_csv(snac *user, const char *fn);
428void import_list_csv(snac *user, const char *fn); 428void import_list_csv(snac *user, const char *fn);
429void import_csv(snac *user); 429void import_csv(snac *user);
430int parse_port(const char *url, const char **errstr);
430 431
431typedef enum { 432typedef enum {
432#define HTTP_STATUS(code, name, text) HTTP_STATUS_ ## name = code, 433#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 @@
1/* snac - A simple, minimalistic ActivityPub instance */
2/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */
3
4#define XS_IMPLEMENTATION
5#include "../xs.h"
6#include "../xs_curl.h"
7
8#define FROM "<snac-smtp-test@locahost>"
9
10int main(void) {
11 xs *to = xs_fmt("<%s@localhost>", getenv("USER")),
12 *body = xs_fmt(""
13 "To: %s \r\n"
14 "From: " FROM "\r\n"
15 "Subject: snac smtp test\r\n"
16 "\r\n"
17 "If you read this as an email, it probably worked!\r\n",
18 to);
19
20 return xs_smtp_request("smtp://localhost", NULL, NULL,
21 FROM,
22 to,
23 body, 0);
24} \ 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)
911 else 911 else
912 snac_log(user, xs_fmt("Cannot open file %s", fn)); 912 snac_log(user, xs_fmt("Cannot open file %s", fn));
913} 913}
914
915static const struct {
916 const char *proto;
917 unsigned short default_port;
918} FALLBACK_PORTS[] = {
919 /* caution: https > http, smpts > smtp */
920 {"https", 443},
921 {"http", 80},
922 {"smtps", 465},
923 {"smtp", 25}
924};
925
926int parse_port(const char *url, const char **errstr)
927{
928 const char *col, *rcol;
929 int tmp, ret = -1;
930
931 if (errstr)
932 *errstr = NULL;
933
934 if (!(col = strchr(url, ':'))) {
935 if (errstr)
936 *errstr = "bad url";
937
938 return -1;
939 }
940
941 for (size_t i = 0; i < sizeof(FALLBACK_PORTS) / sizeof(*FALLBACK_PORTS); ++i) {
942 if (memcmp(url, FALLBACK_PORTS[i].proto, strlen(FALLBACK_PORTS[i].proto)) == 0) {
943 ret = FALLBACK_PORTS[i].default_port;
944 break;
945 }
946 }
947
948 if (!(rcol = strchr(col + 1, ':')))
949 rcol = col;
950
951 if (rcol) {
952 tmp = atoi(rcol + 1);
953 if (tmp == 0) {
954 if (ret != -1)
955 return ret;
956
957 if (errstr)
958 *errstr = strerror(errno);
959
960 return -1;
961 }
962
963 return tmp;
964 }
965
966 if (errstr)
967 *errstr = "unknown protocol";
968
969 return -1;
970}
diff --git a/xs_curl.h b/xs_curl.h
index 657644a..0609a08 100644
--- a/xs_curl.h
+++ b/xs_curl.h
@@ -9,6 +9,10 @@ xs_dict *xs_http_request(const char *method, const char *url,
9 const xs_str *body, int b_size, int *status, 9 const xs_str *body, int b_size, int *status,
10 xs_str **payload, int *p_size, int timeout); 10 xs_str **payload, int *p_size, int timeout);
11 11
12int xs_smtp_request(const char *url, const char *user, const char *pass,
13 const char *from, const char *to, const xs_str *body,
14 int use_ssl);
15
12const char *xs_curl_strerr(int errnum); 16const char *xs_curl_strerr(int errnum);
13 17
14#ifdef XS_IMPLEMENTATION 18#ifdef XS_IMPLEMENTATION
@@ -196,6 +200,48 @@ xs_dict *xs_http_request(const char *method, const char *url,
196 return response; 200 return response;
197} 201}
198 202
203int xs_smtp_request(const char *url, const char *user, const char *pass,
204 const char *from, const char *to, const xs_str *body,
205 int use_ssl)
206{
207 CURL *curl;
208 CURLcode res = CURLE_OK;
209 struct curl_slist *rcpt = NULL;
210 struct _payload_data pd = {
211 .data = (char *)body,
212 .size = xs_size(body),
213 .offset = 0
214 };
215
216 curl = curl_easy_init();
217
218 curl_easy_setopt(curl, CURLOPT_URL, url);
219 if (user && pass) {
220 /* allow authless connections, to, e.g. localhost */
221 curl_easy_setopt(curl, CURLOPT_USERNAME, user);
222 curl_easy_setopt(curl, CURLOPT_PASSWORD, pass);
223 }
224
225 if (use_ssl)
226 curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL);
227
228 curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from);
229
230 rcpt = curl_slist_append(rcpt, to);
231 curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, rcpt);
232
233 curl_easy_setopt(curl, CURLOPT_READDATA, &pd);
234 curl_easy_setopt(curl, CURLOPT_READFUNCTION, _post_callback);
235 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
236
237 res = curl_easy_perform(curl);
238
239 curl_easy_cleanup(curl);
240 curl_slist_free_all(rcpt);
241
242 return (int)res;
243}
244
199 245
200const char *xs_curl_strerr(int errnum) 246const char *xs_curl_strerr(int errnum)
201{ 247{