misc: update gmime dependency 2.6 -> 3.0
[rb-79.git] / rb79-server.c
blobd289a61f17941d73b502cc803ddde6609ec59383
1 /*
2 * Copyright (c) 2017-2020, De Rais <derais@cock.li>
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all
7 * copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
18 #include <limits.h>
19 #include <locale.h>
20 #include <stdint.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <strings.h>
25 #include <time.h>
26 #include <unistd.h>
28 #include <fcgiapp.h>
30 #include "macros.h"
31 #include "rb79.h"
33 #include "config.h"
35 const char *program_name = "rb79-server";
37 /* Print out a page saying the request was malformed (400) */
38 static int
39 report_bad_request(FCGX_Request *r, const char *reason)
41 if (!reason) {
42 reason = "That's not a real request. That's all we know.";
45 FCGX_FPrintF(r->out, BAD_REQUEST_FMT, reason);
47 return 0;
50 /* Print out a page saying they failed the CAPTCHA (403) */
51 static int
52 report_bad_challenge(FCGX_Request *r)
54 FCGX_FPrintF(r->out, BAD_CHALLENGE_FMT);
56 return 0;
59 /* Print out a BANNED page (403) */
60 static int
61 report_ban(FCGX_Request *r, char *ban_until, char *ban_reason)
63 FCGX_FPrintF(r->out, BAN_FMT, UBSAFES(ban_until), UBSAFES(ban_reason));
65 return 0;
68 /* Print out a BAD METHOD page (405) */
69 static int
70 report_bad_method(FCGX_Request *r)
72 FCGX_FPrintF(r->out, BAD_METHOD_FMT);
74 return 0;
77 /* Print out a FILE TOO LARGE page (413) */
78 static int
79 report_too_large(FCGX_Request *r, const char *large_thing)
81 FCGX_FPrintF(r->out, TOO_LARGE_FMT, UBSAFES(large_thing));
83 return 0;
86 /* Print out a page saying they're posting too fast (429) */
87 static int
88 report_cooldown(FCGX_Request *r, char *cooldown_length)
90 FCGX_FPrintF(r->out, COOLDOWN_FMT, UBSAFES(cooldown_length));
92 return 0;
95 /* Print out an INTERNAL ERROR page (500) */
96 static int
97 report_internal_error(FCGX_Request *r)
99 FCGX_FPrintF(r->out, INTERNAL_ERROR_FMT);
101 return 0;
104 /* Print out a POST SUCCESSFUL page (200) */
105 static int
106 report_post_successful(FCGX_Request *r, const char *buf)
108 FCGX_FPrintF(r->out, POST_SUCCESSFUL_FMT, buf);
110 return 0;
113 /* Normal POST SUCCESSFUL page, with a redirect back to the given board */
114 static int
115 report_post_successful_with_redir(FCGX_Request *r, const char *board_name)
117 char *buf = 0;
118 size_t len = snprintf(0, 0, "/%s", board_name);
119 int ret = -1;
121 if (len + 1 < len) {
122 ERROR_MESSAGE("overflow");
123 report_internal_error(r);
124 goto done;
127 if (!(buf = malloc(len + 1))) {
128 PERROR_MESSAGE("malloc");
129 report_internal_error(r);
130 goto done;
133 sprintf(buf, "/%s", board_name);
134 ret = report_post_successful(r, buf);
135 done:
136 free(buf);
138 return ret;
141 /* Normal POST SUCCESSFUL page, with redirect back to the given thread */
142 static int
143 report_post_successful_no_redir(FCGX_Request *r, const char *board_name, const
144 char *thread)
146 char *buf = 0;
147 size_t len = snprintf(0, 0, "/%s/res/%s", board_name, thread);
148 int ret = -1;
150 if (len + 1 < len) {
151 ERROR_MESSAGE("overflow");
152 report_internal_error(r);
153 goto done;
156 if (!(buf = malloc(len + 1))) {
157 PERROR_MESSAGE("malloc");
158 report_internal_error(r);
159 goto done;
162 sprintf(buf, "/%s/res/%s", board_name, thread);
163 ret = report_post_successful(r, buf);
164 done:
165 free(buf);
167 return ret;
170 /* Make sure every board has a page (really only for brand-new boards) */
171 static int
172 board_pages_init(struct configuration *conf)
174 int ret = -1;
175 uintmax_t *thread_ids = 0;
176 size_t thread_ids_num = 0;
177 size_t board_pages_num = 0;
179 for (size_t j = 0; j < conf->boards_num; ++j) {
180 free(thread_ids);
181 thread_ids = 0;
183 if (lock_acquire(j) < 0) {
184 goto done;
187 if (db_cull_and_report_threads(j, &thread_ids, &thread_ids_num,
188 &board_pages_num) < 0) {
189 goto done;
192 if (wt_write_board(j, thread_ids, thread_ids_num,
193 board_pages_num) < 0) {
194 goto done;
197 lock_release(j);
200 ret = 0;
201 done:
202 free(thread_ids);
204 return ret;
207 /* Free what needs to be freed */
208 static void
209 clean_post_cmd(struct post_cmd *p)
211 if (!p) {
212 return;
215 free(p->raw.action);
216 free(p->raw.board);
217 free(p->raw.thread);
218 free(p->raw.post);
219 free(p->raw.name);
220 free(p->raw.email);
221 free(p->raw.tripcode);
222 free(p->raw.subject);
223 free(p->raw.comment);
224 free(p->raw.file_name);
225 free(p->raw.file_contents);
226 free(p->raw.challenge_id);
227 free(p->raw.challenge_response);
228 free(p->prepared.name);
229 free(p->prepared.email);
230 free(p->prepared.tripcode);
231 free(p->prepared.subject);
232 free(p->prepared.comment);
233 free(p->prepared.ext);
234 free(p->prepared.file_name);
235 free(p->prepared.system_full_path);
236 free(p->prepared.system_thumb_path);
237 free(p->prepared.file_info);
238 free(p->scannable_comment);
239 free(p->scannable_name);
240 free(p->scannable_email);
241 free(p->scannable_subject);
242 free(p->scannable_filename);
243 free(p->comment_position_map);
244 free(p->name_position_map);
245 free(p->email_position_map);
246 free(p->subject_position_map);
247 free(p->filename_position_map);
248 *p = (struct post_cmd) { 0 };
251 /* The bulk of work for processing a post */
252 static void
253 handle_op_or_reply(struct configuration *conf, FCGX_Request *r, struct
254 post_cmd *pc, const char *ip, size_t parent_thread)
256 char *buf = 0;
257 char *abs_file_path = 0;
258 int our_fault = 0;
259 uintmax_t real_thread = 0;
260 int cooldown = 0;
261 int thread_dne = 0;
262 int thread_full = 0;
263 int thread_closed = 0;
264 const struct filetype *f;
265 size_t board_pages_num = 0;
266 uintmax_t *thread_ids = 0;
267 size_t thread_ids_num = 0;
268 uint_fast8_t need_to_unlock = 0;
270 if (!parent_thread &&
271 (!pc->raw.file_contents ||
272 !pc->raw.file_contents_len)) {
273 LOG("New thread, yet no file (400)");
274 report_bad_request(r, "New threads must have a file");
275 goto done;
278 /* pc comes in with a bunch of these lens set not-as-desired */
279 if (pc->raw.file_name_len > conf->max_text_len) {
280 LOG("File name length (%zu) larger than max (%zu) (413)",
281 pc->raw.file_name_len, conf->max_text_len);
282 report_too_large(r, "Filename");
283 goto done;
286 if (pc->raw.subject_len > conf->max_text_len) {
287 LOG("Subject length (%zu) larger than max (%zu) (413)",
288 pc->raw.subject_len, conf->max_text_len);
289 report_too_large(r, "Subject text");
290 goto done;
293 if (pc->raw.email_len > conf->max_text_len) {
294 LOG("Email length (%zu) larger than max (%zu) (413)",
295 pc->raw.email_len, conf->max_text_len);
296 report_too_large(r, "Email address");
297 goto done;
300 if (pc->raw.comment_len > conf->max_text_len) {
301 LOG("Comment length (%zu) larger than max (%zu) (413)",
302 pc->raw.comment_len, conf->max_text_len);
303 report_too_large(r, "Comment text");
304 goto done;
307 if (pc->raw.file_contents_len > conf->max_file_size) {
308 LOG("File size (%zu) larger than max (%zu) (413)",
309 pc->raw.file_contents_len, conf->max_file_size);
310 report_too_large(r, "File size");
311 goto done;
314 if (sf_check_mime_type(pc->raw.file_contents, pc->raw.file_contents_len,
315 &f) < 0) {
316 LOG("Bad MIME check (400)");
317 report_bad_request(r, "Unsupported file type");
318 goto done;
321 /* Calculate tripcodes before HTML-escaping everything */
322 if (tripcodes_calculate(pc) < 0) {
323 LOG("Error in tripcodes_calculate (500)");
324 report_internal_error(r);
325 goto done;
328 /* HTML-escape, wordfilter, linkify */
329 uint_fast8_t is_forbidden = 0;
330 int ban_duration = 0;
331 const char *ban_reason = 0;
333 if (st_sanitize_text(pc, &our_fault, &is_forbidden, &ban_duration,
334 &ban_reason) < 0) {
335 if (our_fault) {
336 LOG("Error in st_sanitize_text (500)");
337 report_internal_error(r);
338 goto done;
341 if (is_forbidden) {
342 LOG("Bad text (400)");
343 LOG("Comment was \"%s\"", UBSAFES(pc->raw.comment));
345 if (ban_duration) {
346 time_t start = time(0);
347 time_t end = start + ban_duration;
348 int is_secret = !(ban_reason);
350 if (db_insert_ban(1, 0, ip, ip, ban_reason,
351 is_secret, start, end) < 0) {
352 LOG("Error in db_insert_ban (500)");
353 report_internal_error(r);
354 goto done;
358 report_bad_request(r, "Disallowed text");
359 goto done;
360 } else {
361 LOG("Unknown error (400)");
362 LOG("Comment was \"%s\"", UBSAFES(pc->raw.comment));
363 report_bad_request(r, "Disallowed text");
364 goto done;
368 cooldown = pc->prepared.comment_len ?
369 conf->boards[pc->board_idx].text_cooldown :
370 conf->boards[pc->board_idx].blank_cooldown;
373 * From now on, everything must be under lock, since we
374 * could be touching the filesystem. Strictly, we don't
375 * need to worry about locking for db-only operations, so
376 * this could be delayed a bit.
378 if (lock_acquire(pc->board_idx) < 0) {
379 LOG("Error in lock_acquire (500)");
380 report_internal_error(r);
381 goto done;
384 need_to_unlock = 1;
386 if (db_insert_post(ip, parent_thread, cooldown, pc, &thread_dne,
387 &thread_closed, &thread_full, &pc->prepared.id) <
388 0) {
389 LOG("Error in insert_post (500)");
390 report_internal_error(r);
391 goto done;
394 LOG("Post %ju on board /%s/", pc->prepared.id,
395 conf->boards[pc->board_idx].name);
397 if (thread_dne) {
398 LOG("Thread %zu does not exist (400)", (size_t) 0);
399 report_bad_request(r, "Thread does not exist");
400 goto done;
403 if (thread_full) {
404 LOG("Thread %zu is full (400)", (size_t) 0);
405 report_bad_request(r, "Thread is full");
406 goto done;
409 if (thread_closed) {
410 LOG("Thread %zu is closed (400)", (size_t) 0);
411 report_bad_request(r, "Thread is closed");
412 goto done;
415 /* Make thumbnails and insert them */
416 if (f) {
417 if (sf_install_files(pc->board_idx, pc->raw.file_contents,
418 pc->raw.file_contents_len,
419 &pc->prepared.now, f, &abs_file_path,
420 &pc->prepared.system_full_path,
421 &pc->prepared.system_full_path_len,
422 &pc->prepared.system_thumb_path,
423 &pc->prepared.system_thumb_path_len,
424 &our_fault) < 0) {
425 if (our_fault) {
426 LOG("Error in sf_install_files (500)");
427 report_internal_error(r);
428 goto done;
431 LOG("Couldn't install files (400)");
432 report_bad_request(r, "Bad file upload");
433 goto done;
436 /* ... and now that they're inserted, describe them ... */
437 if (sf_describe_file(f->mime_type, abs_file_path,
438 &pc->prepared.file_info,
439 &pc->prepared.file_info_len) < 0) {
440 LOG("Error in sf_describe_file (500)");
441 report_internal_error(r);
442 goto done;
445 /* ... and alert the db about that description. */
446 if (db_update_file_info(pc->board_idx, pc->prepared.id,
447 pc->prepared.file_info,
448 pc->prepared.file_info_len,
449 pc->prepared.system_full_path,
450 pc->prepared.system_full_path_len,
451 pc->prepared.system_thumb_path,
452 pc->prepared.system_thumb_path_len) <
453 0) {
454 LOG("Error in db_update_post_description (500)");
455 report_internal_error(r);
456 goto done;
461 * We're about ready to write out the threads, boards, etc.
462 * Therefore, we must now check for thread culling, and
463 * also calculate how many board pages we need.
465 if (db_cull_and_report_threads(pc->board_idx, &thread_ids,
466 &thread_ids_num, &board_pages_num) < 0) {
467 LOG("Error in db_cull_and_report_threads (500)");
468 report_internal_error(r);
469 goto done;
472 real_thread = parent_thread ? parent_thread : pc->prepared.id;
474 if (wt_write_thread(pc->board_idx, real_thread) < 0) {
475 LOG("Error in wt_write_thread (500)");
476 report_internal_error(r);
477 goto done;
480 if (wt_write_board(pc->board_idx, thread_ids, thread_ids_num,
481 board_pages_num) < 0) {
482 LOG("Error in wt_write_board (500)");
483 report_internal_error(r);
484 goto done;
487 if (pc->raw.email &&
488 !strcmp(pc->raw.email, "noko")) {
489 report_post_successful_no_redir(r, pc->raw.board,
490 pc->raw.thread);
491 } else {
492 report_post_successful_with_redir(r, pc->raw.board);
495 done:
497 if (need_to_unlock) {
498 lock_release(pc->board_idx);
501 free(buf);
502 free(abs_file_path);
503 free(thread_ids);
506 /* Rebuild every thread and every board */
507 static void
508 handle_rebuild (struct configuration *conf, FCGX_Request *r)
510 uint_fast8_t had_errors = util_rebuild(conf);
512 FCGX_FPrintF(r->out, "Status: 200\r\nContent-type: text/plain\r\n\r\n"
513 "Rebuild complete%s\n", had_errors ?
514 " with errors" : "");
516 return;
519 /* Figure out what they want us to do */
520 static void
521 handle(struct configuration *conf, FCGX_Request *r)
523 char *p = 0;
524 char *content_type = 0;
525 char *content_len_str = 0;
526 size_t content_len = 0;
527 char *buf_main = 0;
528 size_t buf_main_len = 0;
529 const char *content_type_prefix = "Content-Type: ";
530 struct post_cmd post_cmd = { 0 };
531 const char *ip_raw = FCGX_GetParam("REMOTE_ADDR", r->envp);
532 char *ip = 0;
533 char *ban_reason = 0;
534 char *ban_until = 0;
535 char *cooldown_length = 0;
536 uint_fast8_t found_idx = 0;
538 /* In case someone is trying for a time GET, prioritize that */
539 time(&post_cmd.prepared.now);
540 LOG("-----------------------------------------");
541 LOG("Handling post at %zu from %s", (size_t) post_cmd.prepared.now,
542 UBSAFES(ip_raw));
544 if (!ip_raw) {
545 LOG("Couldn't get REMOTE_ADDR (500)");
546 report_internal_error(r);
547 goto done;
550 if (util_normalize_ip(ip_raw, &ip) < 0) {
551 LOG("Couldn't normalize ip (500)");
552 report_internal_error(r);
553 goto done;
556 /* You can only POST to /action */
557 if (!(p = FCGX_GetParam("REQUEST_METHOD", r->envp))) {
558 LOG("Couldn't get request method (500)");
559 report_internal_error(r);
560 goto done;
563 if (strcmp(p, "POST")) {
564 LOG("request method was not POST (405)");
565 report_bad_method(r);
566 goto done;
569 /* We have to somehow feed this into multipart */
570 if (!(content_type = FCGX_GetParam("CONTENT_TYPE", r->envp))) {
571 LOG("Can't get CONTENT_TYPE (500)");
572 report_internal_error(r);
573 goto done;
576 if (!(content_len_str = FCGX_GetParam("CONTENT_LENGTH", r->envp))) {
577 LOG("Can't get CONTENT_LENGTH (500)");
578 report_internal_error(r);
579 goto done;
582 content_len = (size_t) strtoll(content_len_str, 0, 0);
584 if (content_len > max_form_data_size) {
585 LOG("Buffer would have exceeded %zuB (413)",
586 max_form_data_size);
587 report_too_large(r, "Total POST");
588 goto done;
591 buf_main_len = strlen(content_type_prefix) + strlen(content_type) +
592 strlen("\r\n\r\n") + content_len;
594 if (buf_main_len + 1 < buf_main_len) {
595 ERROR_MESSAGE("overflow");
596 goto done;
599 if (!(buf_main = malloc(buf_main_len + 1))) {
600 PERROR_MESSAGE("malloc");
601 goto done;
604 size_t offset = sprintf(buf_main, "%s%s\r\n\r\n", content_type_prefix,
605 content_type);
607 /* Try and swallow this thing into a buffer */
608 FCGX_GetStr(buf_main + offset, content_len, r->in);
610 /* Okay, we've got it in the buffer */
611 if (multipart_decompose(buf_main, buf_main_len, &post_cmd) < 0) {
612 LOG("Decoding message failed, returning (400)");
613 report_bad_request(r, "Invalid multipart/form-data");
614 goto done;
617 /* Now we can check what they actually wanted us to DO */
618 if (!post_cmd.raw.action) {
619 LOG("No action specified (400)");
620 report_bad_request(r, "You have to give action=something");
621 goto done;
622 } else if (!(strcmp(post_cmd.raw.action, "reply"))) {
623 post_cmd.action_id = REPLY;
624 } else if (!(strcmp(post_cmd.raw.action, "newthread"))) {
625 post_cmd.action_id = NEWTHREAD;
626 } else if (!(strcmp(post_cmd.raw.action, "rebuild"))) {
627 post_cmd.action_id = REBUILD;
630 if (post_cmd.raw.thread) {
631 post_cmd.thread_id = strtoll(post_cmd.raw.thread, 0, 0);
634 if (post_cmd.action_id == NONE) {
635 LOG("Invalid action \"%s\" (400)", post_cmd.raw.action);
636 report_bad_request(r, "That's not a valid action");
637 goto done;
641 * XXX: the idea is to only accept REBUILD commmands from
642 * the local machine. Is this necessary and sufficient in
643 * the world of ipv6?
645 if (post_cmd.action_id == REBUILD) {
646 /* Note that the IP is normalized so we can sort it */
647 if (strcmp(ip, "127.000.000.001") &&
648 strcmp(ip, "000.000.000.000") &&
649 strcmp(ip, "0000:0000:0000:0000:0000:0000:0000:0001")) {
650 LOG("REBUILD requested from invalid ip %s", ip);
651 report_bad_request(r, "You can(not) rebuild");
652 goto done;
655 goto take_action;
658 /* And we can find where they wanted to do it */
659 found_idx = 0;
661 if (!post_cmd.raw.board) {
662 LOG("No board specified (400)");
663 report_bad_request(r, "You have to give board=something");
664 goto done;
667 if (post_cmd.action_id == REPLY &&
668 !post_cmd.thread_id) {
669 LOG("Reply, yet no thread (400)");
670 report_bad_request(r, "You have to give thread=something");
671 goto done;
674 for (size_t j = 0; j < conf->boards_num; ++j) {
675 if (!strcmp(post_cmd.raw.board, conf->boards[j].name)) {
676 post_cmd.board_idx = j;
677 found_idx = 1;
678 break;
682 if (!found_idx) {
683 LOG("Invalid board \"%s\" (400)", post_cmd.raw.board);
684 report_bad_request(r, "That's not a valid board");
685 goto done;
688 int is_banned = 0;
689 int is_secret = 0;
691 if (db_check_bans(ip, post_cmd.board_idx, post_cmd.prepared.now,
692 &is_banned, &ban_until, &is_secret, &ban_reason) <
693 0) {
694 LOG("Couldn't determine ban status (500)");
695 report_internal_error(r);
696 goto done;
699 if (is_banned) {
700 if (is_secret) {
701 LOG("Ban[s] (until=\"%s\", reason=\"%s\") (200)",
702 ban_until, UBSAFES(ban_reason));
703 report_post_successful_with_redir(r,
704 post_cmd.raw.board);
705 goto done;
706 } else {
707 /* This should give HTTP 403 */
708 LOG("Ban (until=\"%s\", reason=\"%s\") (403)",
709 ban_until, UBSAFES(ban_reason));
710 report_ban(r, ban_until, ban_reason);
711 goto done;
715 if (post_cmd.action_id == REPLY ||
716 post_cmd.action_id == NEWTHREAD) {
717 int is_cooled = 0;
719 if (db_check_cooldowns(ip, post_cmd.board_idx,
720 post_cmd.prepared.now, &is_cooled,
721 &cooldown_length) < 0) {
722 LOG("Couldn't determine cooldown status (500)");
723 report_internal_error(r);
724 goto done;
727 if (is_cooled) {
728 /* This should give HTTP 429 */
729 LOG("Cooldown triggered (length=\"%s\") (429)",
730 cooldown_length);
731 report_cooldown(r, cooldown_length);
732 goto done;
735 int correct_challenge = 0;
737 if (!post_cmd.raw.challenge_id) {
738 LOG("No challenge id given (403)");
739 report_bad_challenge(r);
740 goto done;
743 char *e = 0;
744 size_t challenge_idx = (size_t) strtoll(
745 post_cmd.raw.challenge_id, &e, 0);
747 if (e &&
748 *e) {
749 challenge_idx = conf->challenges_num;
752 if (challenge_idx >= conf->challenges_num) {
753 LOG("Bad challenge id \"%s\" given (403)",
754 post_cmd.raw.challenge_id);
755 report_bad_challenge(r);
756 goto done;
759 if (!post_cmd.raw.challenge_response) {
760 LOG("No challenge response given (403)");
761 report_bad_challenge(r);
762 goto done;
765 for (size_t j = 0; j < NUM_CHALLENGE_ANSWERS; ++j) {
766 if (!conf->challenges[challenge_idx].answers[j]) {
767 continue;
770 if (!strcasecmp(post_cmd.raw.challenge_response,
771 conf->challenges[challenge_idx].answers[
772 j])) {
773 correct_challenge = 1;
777 if (!correct_challenge) {
778 LOG("Incorrect response \"%s\" to challenge %s (403)",
779 post_cmd.raw.challenge_response,
780 post_cmd.raw.challenge_id);
781 LOG("Comment was \"%s\"", UBSAFES(
782 post_cmd.raw.comment));
783 report_bad_challenge(r);
784 goto done;
788 take_action:
790 /* Now we split into specific actions */
791 switch (post_cmd.action_id) {
792 case REPLY:
793 LOG("reply to /%s/%ju", UBSAFES(post_cmd.raw.board),
794 post_cmd.thread_id);
795 handle_op_or_reply(conf, r, &post_cmd, ip, post_cmd.thread_id);
796 break;
797 case NEWTHREAD:
798 LOG("newthread on /%s/", UBSAFES(post_cmd.raw.board));
799 handle_op_or_reply(conf, r, &post_cmd, ip, 0);
800 break;
801 case REBUILD:
802 LOG("rebuild");
803 handle_rebuild(conf, r);
804 break;
805 case NONE:
806 ERROR_MESSAGE("Impossible");
807 report_internal_error(r);
808 break;
811 done:
812 clean_post_cmd(&post_cmd);
813 free(buf_main);
814 free(ban_reason);
815 free(ban_until);
816 free(cooldown_length);
817 free(ip);
820 /* Do the thing */
822 main(void)
824 int ret = 1;
825 FCGX_Request r = { 0 };
826 struct configuration conf = { 0 };
828 setlocale(LC_ALL, "");
830 /* tedu@ is probably laughing at me right now. Hi! */
831 srand(time(0));
832 conf = (struct configuration) {
833 /* */
834 .static_www_folder = static_www_folder, /* */
835 .work_path = work_path, /* */
836 .temp_dir_template = temp_dir_template, /* */
837 .trip_salt = trip_salt, /* */
838 .trip_salt_len = strlen(trip_salt), /* */
839 .boards = boards, /* */
840 .boards_num = NUM_OF(boards), /* */
841 .max_form_data_size = max_form_data_size, /* */
842 .max_file_size = max_file_size, /* */
843 .max_text_len = max_text_len, /* */
844 .filetypes = filetypes, /* */
845 .filetypes_num = NUM_OF(filetypes), /* */
846 .file_description_prog = file_description_prog, /* */
847 .headers = headers, /* */
848 .headers_num = NUM_OF(headers), /* */
849 .challenges = challenges, /* */
850 .challenges_num = NUM_OF(challenges), /* */
851 .wordfilter_inputs = wordfilter_inputs, /* */
852 .wordfilter_inputs_num = NUM_OF(wordfilter_inputs), /* */
853 .forbidden_inputs = forbidden_inputs, /* */
854 .forbidden_inputs_num = NUM_OF(forbidden_inputs), /* */
857 if (preconditions_check(&conf) < 0) {
858 goto done;
861 if (board_pages_init(&conf) < 0) {
862 goto done;
865 FCGX_Init();
866 FCGX_InitRequest(&r, 0, 0);
868 while (FCGX_Accept_r(&r) == 0) {
869 handle(&conf, &r);
870 FCGX_Finish_r(&r);
873 ret = 0;
874 done:
875 clean_dbs();
876 clean_locks();
877 clean_multipart();
878 clean_sanitize_comment();
879 clean_sanitize_file();
880 clean_tripcodes();
881 clean_write_thread();
883 return ret;