diff options
| author | 2025-04-27 03:11:57 +0000 | |
|---|---|---|
| committer | 2025-04-27 03:11:57 +0000 | |
| commit | 169c5c8c070c527e7462e6ac86b916e94649c1ac (patch) | |
| tree | f1ce03b950a278a1985526cb58dc4e879c3ab046 | |
| parent | Updated RELEASE_NOTES. (diff) | |
| parent | default to `smtp://localhost` (diff) | |
| download | penes-snac2-169c5c8c070c527e7462e6ac86b916e94649c1ac.tar.gz penes-snac2-169c5c8c070c527e7462e6ac86b916e94649c1ac.tar.xz penes-snac2-169c5c8c070c527e7462e6ac86b916e94649c1ac.zip | |
Merge pull request 'do email notifications with CURL' (#283) from shtrophic/snac2:curl-smtp into master
Reviewed-on: https://codeberg.org/grunfink/snac2/pulls/283
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | Makefile | 10 | ||||
| -rw-r--r-- | activitypub.c | 51 | ||||
| -rw-r--r-- | data.c | 2 | ||||
| -rw-r--r-- | sandbox.c | 31 | ||||
| -rw-r--r-- | snac.h | 1 | ||||
| -rw-r--r-- | tests/smtp.c | 24 | ||||
| -rw-r--r-- | utils.c | 57 | ||||
| -rw-r--r-- | xs_curl.h | 46 |
9 files changed, 175 insertions, 50 deletions
| @@ -1,2 +1,3 @@ | |||
| 1 | *.o | 1 | **/*.o |
| 2 | tests/smtp | ||
| 2 | snac | 3 | snac |
| @@ -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 | ||
| 11 | test: tests/smtp | ||
| 12 | |||
| 13 | tests/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 | ||
| 14 | clean: | 19 | clean: |
| 15 | rm -rf *.o *.core snac makefile.depend | 20 | rm -rf *.o tests/*.o tests/smtp *.core snac makefile.depend |
| 16 | 21 | ||
| 17 | dep: | 22 | dep: |
| 18 | $(CC) -I$(PREFIX)/include -MM *.c > makefile.depend | 23 | $(CC) -I$(PREFIX)/include -MM *.c > makefile.depend |
| @@ -72,3 +77,4 @@ utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \ | |||
| 72 | xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h | 77 | xs_random.h xs_glob.h xs_curl.h xs_regex.h snac.h http_codes.h |
| 73 | webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \ | 78 | webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h snac.h \ |
| 74 | http_codes.h | 79 | http_codes.h |
| 80 | tests/smtp.o: tests/smtp.c xs.h xs_curl.h | ||
diff --git a/activitypub.c b/activitypub.c index 4c22c25..df68151 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -1044,17 +1044,20 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor, | |||
| 1044 | 1044 | ||
| 1045 | xs *subject = xs_fmt("snac notify for @%s@%s", | 1045 | xs *subject = xs_fmt("snac notify for @%s@%s", |
| 1046 | xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host")); | 1046 | xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host")); |
| 1047 | xs *from = xs_fmt("snac-daemon <snac-daemon@%s>", xs_dict_get(srv_config, "host")); | 1047 | xs *from = xs_fmt("<snac-daemon@%s>", xs_dict_get(srv_config, "host")); |
| 1048 | xs *header = xs_fmt( | 1048 | xs *header = xs_fmt( |
| 1049 | "From: %s\n" | 1049 | "From: snac-daemon %s\n" |
| 1050 | "To: %s\n" | 1050 | "To: %s\n" |
| 1051 | "Subject: %s\n" | 1051 | "Subject: %s\n" |
| 1052 | "\n", | 1052 | "\n", |
| 1053 | from, email, subject); | 1053 | from, email, subject); |
| 1054 | 1054 | ||
| 1055 | xs *email_body = xs_fmt("%s%s", header, body); | 1055 | xs *mailinfo = xs_dict_new(); |
| 1056 | mailinfo = xs_dict_append(mailinfo, "from", from); | ||
| 1057 | mailinfo = xs_dict_append(mailinfo, "to", email); | ||
| 1058 | mailinfo = xs_dict_append(mailinfo, "body", xs_fmt("%s%s", header, body)); | ||
| 1056 | 1059 | ||
| 1057 | enqueue_email(email_body, 0); | 1060 | enqueue_email(mailinfo, 0); |
| 1058 | } | 1061 | } |
| 1059 | 1062 | ||
| 1060 | /* telegram */ | 1063 | /* telegram */ |
| @@ -2563,32 +2566,20 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2563 | } | 2566 | } |
| 2564 | 2567 | ||
| 2565 | 2568 | ||
| 2566 | int send_email(const char *msg) | 2569 | int send_email(const xs_dict *mailinfo) |
| 2567 | /* invoke sendmail with email headers and body in msg */ | 2570 | /* invoke curl */ |
| 2568 | { | 2571 | { |
| 2569 | FILE *f; | 2572 | const xs_dict *smtp_cfg = xs_dict_get(srv_config, "email_notifications"); |
| 2570 | int status; | 2573 | const char |
| 2571 | int fds[2]; | 2574 | *url = xs_dict_get_def(smtp_cfg, "url", "smtp://localhost"), |
| 2572 | pid_t pid; | 2575 | *user = xs_dict_get(smtp_cfg, "username"), |
| 2573 | if (pipe(fds) == -1) return -1; | 2576 | *pass = xs_dict_get(smtp_cfg, "password"), |
| 2574 | pid = vfork(); | 2577 | *from = xs_dict_get(mailinfo, "from"), |
| 2575 | if (pid == -1) return -1; | 2578 | *to = xs_dict_get(mailinfo, "to"), |
| 2576 | else if (pid == 0) { | 2579 | *body = xs_dict_get(mailinfo, "body"); |
| 2577 | dup2(fds[0], 0); | 2580 | int smtp_port = parse_port(url, NULL); |
| 2578 | close(fds[0]); | 2581 | |
| 2579 | close(fds[1]); | 2582 | return xs_smtp_request(url, user, pass, from, to, body, smtp_port == 465 || smtp_port == 587); |
| 2580 | execl("/usr/sbin/sendmail", "sendmail", "-t", (char *) NULL); | ||
| 2581 | _exit(1); | ||
| 2582 | } | ||
| 2583 | close(fds[0]); | ||
| 2584 | if ((f = fdopen(fds[1], "w")) == NULL) { | ||
| 2585 | close(fds[1]); | ||
| 2586 | return -1; | ||
| 2587 | } | ||
| 2588 | fprintf(f, "%s\n", msg); | ||
| 2589 | fclose(f); | ||
| 2590 | if (waitpid(pid, &status, 0) == -1) return -1; | ||
| 2591 | return status; | ||
| 2592 | } | 2583 | } |
| 2593 | 2584 | ||
| 2594 | 2585 | ||
| @@ -2861,7 +2852,7 @@ void process_queue_item(xs_dict *q_item) | |||
| 2861 | else | 2852 | else |
| 2862 | if (strcmp(type, "email") == 0) { | 2853 | if (strcmp(type, "email") == 0) { |
| 2863 | /* send this email */ | 2854 | /* send this email */ |
| 2864 | const xs_str *msg = xs_dict_get(q_item, "message"); | 2855 | const xs_dict *msg = xs_dict_get(q_item, "message"); |
| 2865 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); | 2856 | int retries = xs_number_get(xs_dict_get(q_item, "retries")); |
| 2866 | 2857 | ||
| 2867 | if (!send_email(msg)) | 2858 | if (!send_email(msg)) |
| @@ -3357,7 +3357,7 @@ void enqueue_output_by_actor(snac *snac, const xs_dict *msg, | |||
| 3357 | } | 3357 | } |
| 3358 | 3358 | ||
| 3359 | 3359 | ||
| 3360 | void enqueue_email(const xs_str *msg, int retries) | 3360 | void enqueue_email(const xs_dict *msg, int retries) |
| 3361 | /* enqueues an email message to be sent */ | 3361 | /* enqueues an email message to be sent */ |
| 3362 | { | 3362 | { |
| 3363 | xs *qmsg = _new_qmsg("email", msg, retries); | 3363 | xs *qmsg = _new_qmsg("email", msg, retries); |
| @@ -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 | ||
| 57 | static | 49 | static |
| 58 | LL_BEGIN(sbox_enter_linux_, const char* basedir, const char *address, int smail) { | 50 | LL_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 | ||
| 117 | void sbox_enter(const char *basedir) | 108 | void 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) */ |
| @@ -434,6 +434,7 @@ void import_blocked_accounts_csv(snac *user, const char *fn); | |||
| 434 | void import_following_accounts_csv(snac *user, const char *fn); | 434 | void import_following_accounts_csv(snac *user, const char *fn); |
| 435 | void import_list_csv(snac *user, const char *fn); | 435 | void import_list_csv(snac *user, const char *fn); |
| 436 | void import_csv(snac *user); | 436 | void import_csv(snac *user); |
| 437 | int parse_port(const char *url, const char **errstr); | ||
| 437 | 438 | ||
| 438 | typedef enum { | 439 | typedef enum { |
| 439 | #define HTTP_STATUS(code, name, text) HTTP_STATUS_ ## name = code, | 440 | #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 | |||
| 10 | int 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 | ||
| @@ -912,3 +912,60 @@ void import_csv(snac *user) | |||
| 912 | else | 912 | else |
| 913 | snac_log(user, xs_fmt("Cannot open file %s", fn)); | 913 | snac_log(user, xs_fmt("Cannot open file %s", fn)); |
| 914 | } | 914 | } |
| 915 | |||
| 916 | static const struct { | ||
| 917 | const char *proto; | ||
| 918 | unsigned short default_port; | ||
| 919 | } FALLBACK_PORTS[] = { | ||
| 920 | /* caution: https > http, smpts > smtp */ | ||
| 921 | {"https", 443}, | ||
| 922 | {"http", 80}, | ||
| 923 | {"smtps", 465}, | ||
| 924 | {"smtp", 25} | ||
| 925 | }; | ||
| 926 | |||
| 927 | int parse_port(const char *url, const char **errstr) | ||
| 928 | { | ||
| 929 | const char *col, *rcol; | ||
| 930 | int tmp, ret = -1; | ||
| 931 | |||
| 932 | if (errstr) | ||
| 933 | *errstr = NULL; | ||
| 934 | |||
| 935 | if (!(col = strchr(url, ':'))) { | ||
| 936 | if (errstr) | ||
| 937 | *errstr = "bad url"; | ||
| 938 | |||
| 939 | return -1; | ||
| 940 | } | ||
| 941 | |||
| 942 | for (size_t i = 0; i < sizeof(FALLBACK_PORTS) / sizeof(*FALLBACK_PORTS); ++i) { | ||
| 943 | if (memcmp(url, FALLBACK_PORTS[i].proto, strlen(FALLBACK_PORTS[i].proto)) == 0) { | ||
| 944 | ret = FALLBACK_PORTS[i].default_port; | ||
| 945 | break; | ||
| 946 | } | ||
| 947 | } | ||
| 948 | |||
| 949 | if (!(rcol = strchr(col + 1, ':'))) | ||
| 950 | rcol = col; | ||
| 951 | |||
| 952 | if (rcol) { | ||
| 953 | tmp = atoi(rcol + 1); | ||
| 954 | if (tmp == 0) { | ||
| 955 | if (ret != -1) | ||
| 956 | return ret; | ||
| 957 | |||
| 958 | if (errstr) | ||
| 959 | *errstr = strerror(errno); | ||
| 960 | |||
| 961 | return -1; | ||
| 962 | } | ||
| 963 | |||
| 964 | return tmp; | ||
| 965 | } | ||
| 966 | |||
| 967 | if (errstr) | ||
| 968 | *errstr = "unknown protocol"; | ||
| 969 | |||
| 970 | return -1; | ||
| 971 | } | ||
| @@ -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 | ||
| 12 | int 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 | |||
| 12 | const char *xs_curl_strerr(int errnum); | 16 | const 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 | ||
| 203 | int 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 | ||
| 200 | const char *xs_curl_strerr(int errnum) | 246 | const char *xs_curl_strerr(int errnum) |
| 201 | { | 247 | { |