summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar default2023-10-17 20:02:08 +0200
committerGravatar default2023-10-17 20:02:08 +0200
commit6cd3e768908e99c9bfb5d4f896b9765a477e4214 (patch)
treebbb5aef160cd5169a029da95c5f16fa5f80bac31
parentVersion 2.42 RELEASED. (diff)
downloadsnac2-6cd3e768908e99c9bfb5d4f896b9765a477e4214.tar.gz
snac2-6cd3e768908e99c9bfb5d4f896b9765a477e4214.tar.xz
snac2-6cd3e768908e99c9bfb5d4f896b9765a477e4214.zip
Added FastCGI support.
Diffstat (limited to '')
-rw-r--r--Makefile4
-rw-r--r--httpd.c22
-rw-r--r--snac.c1
-rw-r--r--utils.c3
-rw-r--r--xs_fcgi.h365
-rw-r--r--xs_version.h2
6 files changed, 389 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index 9cbdabc..2090e7c 100644
--- a/Makefile
+++ b/Makefile
@@ -43,13 +43,13 @@ html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \
43http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \ 43http.o: http.c xs.h xs_io.h xs_openssl.h xs_curl.h xs_time.h xs_json.h \
44 snac.h 44 snac.h
45httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \ 45httpd.o: httpd.c xs.h xs_io.h xs_json.h xs_socket.h xs_httpd.h xs_mime.h \
46 xs_time.h xs_openssl.h snac.h 46 xs_time.h xs_openssl.h xs_fcgi.h snac.h
47main.o: main.c xs.h xs_io.h xs_json.h snac.h 47main.o: main.c xs.h xs_io.h xs_json.h snac.h
48mastoapi.o: mastoapi.c xs.h xs_openssl.h xs_json.h xs_io.h xs_time.h \ 48mastoapi.o: mastoapi.c xs.h xs_openssl.h xs_json.h xs_io.h xs_time.h \
49 xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h snac.h 49 xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h snac.h
50snac.o: snac.c xs.h xs_io.h xs_unicode.h xs_json.h xs_curl.h xs_openssl.h \ 50snac.o: snac.c xs.h xs_io.h xs_unicode.h xs_json.h xs_curl.h xs_openssl.h \
51 xs_socket.h xs_url.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h \ 51 xs_socket.h xs_url.h xs_httpd.h xs_mime.h xs_regex.h xs_set.h xs_time.h \
52 xs_glob.h xs_random.h xs_match.h snac.h 52 xs_glob.h xs_random.h xs_match.h xs_fcgi.h snac.h
53upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h 53upgrade.o: upgrade.c xs.h xs_io.h xs_json.h xs_glob.h snac.h
54utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \ 54utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \
55 xs_random.h snac.h 55 xs_random.h snac.h
diff --git a/httpd.c b/httpd.c
index 213f751..aed15dc 100644
--- a/httpd.c
+++ b/httpd.c
@@ -9,6 +9,7 @@
9#include "xs_mime.h" 9#include "xs_mime.h"
10#include "xs_time.h" 10#include "xs_time.h"
11#include "xs_openssl.h" 11#include "xs_openssl.h"
12#include "xs_fcgi.h"
12 13
13#include "snac.h" 14#include "snac.h"
14 15
@@ -24,6 +25,8 @@
24#include <poll.h> 25#include <poll.h>
25#endif 26#endif
26 27
28int use_fcgi = 0;
29
27int srv_running = 0; 30int srv_running = 0;
28 31
29/* nodeinfo 2.0 template */ 32/* nodeinfo 2.0 template */
@@ -199,8 +202,12 @@ void httpd_connection(FILE *f)
199 xs *etag = NULL; 202 xs *etag = NULL;
200 int p_size = 0; 203 int p_size = 0;
201 char *p; 204 char *p;
205 int fcgi_id;
202 206
203 req = xs_httpd_request(f, &payload, &p_size); 207 if (use_fcgi)
208 req = xs_fcgi_request(f, &payload, &p_size, &fcgi_id);
209 else
210 req = xs_httpd_request(f, &payload, &p_size);
204 211
205 if (req == NULL) { 212 if (req == NULL) {
206 /* probably because a timeout */ 213 /* probably because a timeout */
@@ -330,7 +337,10 @@ void httpd_connection(FILE *f)
330 headers = xs_dict_append(headers, "access-control-allow-origin", "*"); 337 headers = xs_dict_append(headers, "access-control-allow-origin", "*");
331 headers = xs_dict_append(headers, "access-control-allow-headers", "*"); 338 headers = xs_dict_append(headers, "access-control-allow-headers", "*");
332 339
333 xs_httpd_response(f, status, headers, body, b_size); 340 if (use_fcgi)
341 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
342 else
343 xs_httpd_response(f, status, headers, body, b_size);
334 344
335 fclose(f); 345 fclose(f);
336 346
@@ -550,6 +560,8 @@ void httpd(void)
550 char sem_name[24]; 560 char sem_name[24];
551 sem_t anon_job_sem; 561 sem_t anon_job_sem;
552 562
563 use_fcgi = xs_type(xs_dict_get(srv_config, "fastcgi")) == XSTYPE_TRUE;
564
553 address = xs_dict_get(srv_config, "address"); 565 address = xs_dict_get(srv_config, "address");
554 port = xs_number_str(xs_dict_get(srv_config, "port")); 566 port = xs_number_str(xs_dict_get(srv_config, "port"));
555 567
@@ -564,7 +576,8 @@ void httpd(void)
564 signal(SIGTERM, term_handler); 576 signal(SIGTERM, term_handler);
565 signal(SIGINT, term_handler); 577 signal(SIGINT, term_handler);
566 578
567 srv_log(xs_fmt("httpd start %s:%s %s", address, port, USER_AGENT)); 579 srv_log(xs_fmt("httpd%s start %s:%s %s", use_fcgi ? " (FastCGI)" : "",
580 address, port, USER_AGENT));
568 581
569 /* show the number of usable file descriptors */ 582 /* show the number of usable file descriptors */
570 struct rlimit r; 583 struct rlimit r;
@@ -651,5 +664,6 @@ void httpd(void)
651 664
652 xs *uptime = xs_str_time_diff(time(NULL) - start_time); 665 xs *uptime = xs_str_time_diff(time(NULL) - start_time);
653 666
654 srv_log(xs_fmt("httpd stop %s:%s (run time: %s)", address, port, uptime)); 667 srv_log(xs_fmt("httpd%s stop %s:%s (run time: %s)", use_fcgi ? " (FastCGI)" : "",
668 address, port, uptime));
655} 669}
diff --git a/snac.c b/snac.c
index 1ea61ff..ac371c5 100644
--- a/snac.c
+++ b/snac.c
@@ -19,6 +19,7 @@
19#include "xs_glob.h" 19#include "xs_glob.h"
20#include "xs_random.h" 20#include "xs_random.h"
21#include "xs_match.h" 21#include "xs_match.h"
22#include "xs_fcgi.h"
22 23
23#include "snac.h" 24#include "snac.h"
24 25
diff --git a/utils.c b/utils.c
index dd09be6..21922d1 100644
--- a/utils.c
+++ b/utils.c
@@ -29,7 +29,8 @@ static const char *default_srv_config = "{"
29 "\"admin_email\": \"\"," 29 "\"admin_email\": \"\","
30 "\"admin_account\": \"\"," 30 "\"admin_account\": \"\","
31 "\"title\": \"\"," 31 "\"title\": \"\","
32 "\"short_description\": \"\"" 32 "\"short_description\": \"\","
33 "\"fastcgi\": false"
33 "}"; 34 "}";
34 35
35static const char *default_css = 36static const char *default_css =
diff --git a/xs_fcgi.h b/xs_fcgi.h
new file mode 100644
index 0000000..97f14d5
--- /dev/null
+++ b/xs_fcgi.h
@@ -0,0 +1,365 @@
1/* copyright (c) 2022 - 2023 grunfink et al. / MIT license */
2
3/*
4 This is an intentionally-dead-simple FastCGI implementation;
5 only FCGI_RESPONDER type with *no* FCGI_KEEP_CON flag is supported.
6 This means only one simultaneous connection and no multiplexing.
7 It seems it's enough for nginx and OpenBSD's httpd, so here it goes.
8 Almost fully compatible with xs_httpd.h
9*/
10
11#ifndef _XS_FCGI_H
12
13#define _XS_FCGI_H
14
15 xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *id);
16 void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int id);
17
18
19#ifdef XS_IMPLEMENTATION
20
21struct fcgi_record_header {
22 unsigned char version;
23 unsigned char type;
24 unsigned short id;
25 unsigned short content_len;
26 unsigned char padding_len;
27 unsigned char reserved;
28} __attribute__((packed));
29
30/* version */
31
32#define FCGI_VERSION_1 1
33
34/* types */
35
36#define FCGI_BEGIN_REQUEST 1
37#define FCGI_ABORT_REQUEST 2
38#define FCGI_END_REQUEST 3
39#define FCGI_PARAMS 4
40#define FCGI_STDIN 5
41#define FCGI_STDOUT 6
42#define FCGI_STDERR 7
43#define FCGI_DATA 8
44#define FCGI_GET_VALUES 9
45#define FCGI_GET_VALUES_RESULT 10
46#define FCGI_UNKNOWN_TYPE 11
47#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
48
49struct fcgi_begin_request {
50 unsigned short role;
51 unsigned char flags;
52 unsigned char reserved[5];
53} __attribute__((packed));
54
55/* roles */
56
57#define FCGI_RESPONDER 1
58#define FCGI_AUTHORIZER 2
59#define FCGI_FILTER 3
60
61/* flags */
62
63#define FCGI_KEEP_CONN 1
64
65struct fcgi_end_request {
66 unsigned int app_status;
67 unsigned char protocol_status;
68 unsigned char reserved[3];
69} __attribute__((packed));
70
71/* protocol statuses */
72
73#define FCGI_REQUEST_COMPLETE 0
74#define FCGI_CANT_MPX_CONN 1
75#define FCGI_OVERLOADED 2
76#define FCGI_UNKNOWN_ROLE 3
77
78
79xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *fcgi_id)
80/* keeps receiving FCGI packets until a complete request is finished */
81{
82 unsigned char p_buf[100000];
83 struct fcgi_record_header hdr;
84 struct fcgi_begin_request *breq = (struct fcgi_begin_request *)&p_buf;
85 unsigned char *buf = NULL;
86 int b_size = 0;
87 xs_dict *req = NULL;
88 unsigned char p_status = FCGI_REQUEST_COMPLETE;
89 xs *q_vars = NULL;
90 xs *p_vars = NULL;
91
92 *fcgi_id = -1;
93
94 for (;;) {
95 int sz, psz;
96
97 /* read the packet header */
98 if (fread(&hdr, sizeof(hdr), 1, f) != 1)
99 break;
100
101 /* read the packet body */
102 if ((psz = ntohs(hdr.content_len)) > 0) {
103 if ((sz = fread(p_buf, 1, psz, f)) != psz)
104 break;
105 }
106
107 /* read (and drop) the padding */
108 if (hdr.padding_len > 0)
109 fread(p_buf + sz, 1, hdr.padding_len, f);
110
111 switch (hdr.type) {
112 case FCGI_BEGIN_REQUEST:
113 /* fail on unsupported roles */
114 if (ntohs(breq->role) != FCGI_RESPONDER) {
115 p_status = FCGI_UNKNOWN_ROLE;
116 goto end;
117 }
118
119 /* fail on unsupported flags */
120 if (breq->flags & FCGI_KEEP_CONN) {
121 p_status = FCGI_CANT_MPX_CONN;
122 goto end;
123 }
124
125 /* store the id for later */
126 *fcgi_id = (int) hdr.id;
127
128 break;
129
130 case FCGI_PARAMS:
131 /* unknown id? fail */
132 if (hdr.id != *fcgi_id) {
133 p_status = FCGI_CANT_MPX_CONN;
134 goto end;
135 }
136
137 if (psz) {
138 /* add to the buffer */
139 buf = xs_realloc(buf, b_size + psz);
140 memcpy(buf + b_size, p_buf, psz);
141 b_size += psz;
142 }
143 else {
144 /* no size, so the packet is complete; process it */
145 xs *cgi_vars = xs_dict_new();
146
147 req = xs_dict_new();
148
149 int offset = 0;
150 while (offset < b_size) {
151 unsigned int ksz = buf[offset++];
152
153 if (ksz & 0x80) {
154 ksz &= 0x7f;
155 ksz = (ksz << 8) | buf[offset++];
156 ksz = (ksz << 8) | buf[offset++];
157 ksz = (ksz << 8) | buf[offset++];
158 }
159
160 unsigned int vsz = buf[offset++];
161 if (vsz & 0x80) {
162 vsz &= 0x7f;
163 vsz = (vsz << 8) | buf[offset++];
164 vsz = (vsz << 8) | buf[offset++];
165 vsz = (vsz << 8) | buf[offset++];
166 }
167
168 /* get the key */
169 xs *k = xs_str_new_sz((char *)&buf[offset], ksz);
170 offset += ksz;
171
172 /* get the value */
173 xs *v = xs_str_new_sz((char *)&buf[offset], vsz);
174 offset += vsz;
175
176 cgi_vars = xs_dict_append(cgi_vars, k, v);
177
178 if (strcmp(k, "REQUEST_METHOD") == 0)
179 req = xs_dict_append(req, "method", v);
180 else
181 if (strcmp(k, "REQUEST_URI") == 0) {
182 xs *udp = xs_url_dec(v);
183 xs *pnv = xs_split_n(udp, "?", 1);
184
185 /* store the path */
186 req = xs_dict_append(req, "path", xs_list_get(pnv, 0));
187
188 /* get the variables */
189 q_vars = xs_url_vars(xs_list_get(pnv, 1));
190 }
191 else
192 if (xs_match(k, "CONTENT_TYPE|CONTENT_LENGTH|HTTP_*")) {
193 if (xs_startswith(k, "HTTP_"))
194 k = xs_crop_i(k, 5, 0);
195
196 k = xs_tolower_i(k);
197 k = xs_replace_i(k, "_", "-");
198
199 req = xs_dict_append(req, k, v);
200 }
201 }
202
203 req = xs_dict_append(req, "cgi_vars", cgi_vars);
204
205 buf = xs_free(buf);
206 b_size = 0;
207 }
208
209 break;
210
211 case FCGI_STDIN:
212 /* unknown id? fail */
213 if (hdr.id != *fcgi_id) {
214 p_status = FCGI_CANT_MPX_CONN;
215 goto end;
216 }
217
218 if (psz) {
219 /* add to the buffer */
220 buf = xs_realloc(buf, b_size + psz);
221 memcpy(buf + b_size, p_buf, psz);
222 b_size += psz;
223 }
224 else {
225 /* the packet is complete; fill the payload info and finish */
226 *payload = (xs_str *)buf;
227 *p_size = b_size;
228
229 const char *ct = xs_dict_get(req, "content-type");
230
231 if (*payload && ct && strcmp(ct, "application/x-www-form-urlencoded") == 0) {
232 xs *upl = xs_url_dec(*payload);
233 p_vars = xs_url_vars(upl);
234 }
235 else
236 if (*payload && ct && xs_startswith(ct, "multipart/form-data")) {
237 p_vars = xs_multipart_form_data(*payload, *p_size, ct);
238 }
239 else
240 p_vars = xs_dict_new();
241
242 if (q_vars == NULL)
243 q_vars = xs_dict_new();
244
245 req = xs_dict_append(req, "q_vars", q_vars);
246 req = xs_dict_append(req, "p_vars", p_vars);
247
248 /* disconnect the payload from the buf variable */
249 buf = NULL;
250
251 goto end;
252 }
253
254 break;
255 }
256 }
257
258end:
259 /* any kind of error? notify and cleanup */
260 if (p_status != FCGI_REQUEST_COMPLETE) {
261 struct fcgi_end_request ereq = {0};
262
263 /* complete the connection */
264 ereq.app_status = 0;
265 ereq.protocol_status = p_status;
266
267 /* reuse header */
268 hdr.type = FCGI_ABORT_REQUEST;
269 hdr.content_len = htons(sizeof(ereq));
270
271 fwrite(&hdr, sizeof(hdr), 1, f);
272 fwrite(&ereq, sizeof(ereq), 1, f);
273
274 /* session closed */
275 *fcgi_id = -1;
276
277 /* request dict is not valid */
278 req = xs_free(req);
279 }
280
281 xs_free(buf);
282 return req;
283}
284
285
286void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int fcgi_id)
287/* writes an FCGI response */
288{
289 struct fcgi_record_header hdr = {0};
290 struct fcgi_end_request ereq = {0};
291 xs *out = xs_str_new(NULL);
292 xs_dict *p;
293 xs_str *k;
294 xs_str *v;
295
296 /* no previous id? it's an error */
297 if (fcgi_id == -1)
298 return;
299
300 /* create the headers */
301 {
302 xs *s1 = xs_fmt("status: %d\r\n", status);
303 out = xs_str_cat(out, s1);
304 }
305
306 p = headers;
307 while (xs_dict_iter(&p, &k, &v)) {
308 xs *s1 = xs_fmt("%s: %s\r\n", k, v);
309 out = xs_str_cat(out, s1);
310 }
311
312 if (b_size > 0) {
313 xs *s1 = xs_fmt("content-length: %d\r\n", b_size);
314 out = xs_str_cat(out, s1);
315 }
316
317 out = xs_str_cat(out, "\r\n");
318
319 /* everything is text by now */
320 int size = strlen(out);
321
322 /* add the body */
323 if (body != NULL && b_size > 0)
324 out = xs_append_m(out, body, b_size);
325
326 /* now send all the STDOUT in packets */
327 hdr.version = FCGI_VERSION_1;
328 hdr.type = FCGI_STDOUT;
329 hdr.id = fcgi_id;
330
331 size += b_size;
332 int offset = 0;
333
334 while (offset < size) {
335 int sz = size - offset;
336 if (sz > 0xffff)
337 sz = 0xffff;
338
339 hdr.content_len = htons(sz);
340
341 fwrite(&hdr, sizeof(hdr), 1, f);
342 fwrite(out + offset, 1, sz, f);
343
344 offset += sz;
345 }
346
347 /* final STDOUT packet with 0 size */
348 hdr.content_len = 0;
349 fwrite(&hdr, sizeof(hdr), 1, f);
350
351 /* complete the connection */
352 ereq.app_status = 0;
353 ereq.protocol_status = FCGI_REQUEST_COMPLETE;
354
355 hdr.type = FCGI_END_REQUEST;
356 hdr.content_len = htons(sizeof(ereq));
357
358 fwrite(&hdr, sizeof(hdr), 1, f);
359 fwrite(&ereq, sizeof(ereq), 1, f);
360}
361
362
363#endif /* XS_IMPLEMENTATION */
364
365#endif /* XS_URL_H */
diff --git a/xs_version.h b/xs_version.h
index a30fb2d..5fa20dd 100644
--- a/xs_version.h
+++ b/xs_version.h
@@ -1 +1 @@
/* a9cd3893c427bbcc478c5680245d435e415fd58a */ /* b109eea00ddc0765929e36ed1ca6f3f697262bb2 */