// webchat.c - A basic TCP server with INET and Unix socket support // Compile: gcc -std=c99 -o webchat webchat.c // Usage: ./webchat -p 8080 or ./webchat -u /tmp/mysocket #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "util/defs.h" #include "util/arenastr.c" #include "util/strmanip.c" #define DEFAULT_DB "fanr.sqlite" enum{ listen_backlog=10, bwt_expiry_time_secs = 60 * 60, quieter_logs = true, }; static int srv_close_and_fail(int sock){ close(sock); return -1; } static int create_inet_socket(int port) { int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return -1; } int opt = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { perror("setsockopt"); return srv_close_and_fail(sock); } struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return srv_close_and_fail(sock); } if (listen(sock, listen_backlog) < 0) { perror("listen"); return srv_close_and_fail(sock); } log("Listening on TCP port %d (fd:%d)\n", port, sock); return sock; } static int create_unix_socket(str path) { MAKE_ARENA(a, 256); char *path_cstr = str_to_cstr(&a, path); if (!path_cstr) { fprintf(stderr, "Failed to convert path to C string\n"); return -1; } // Unlink existing socket file unlink(path_cstr); int sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return -1; } struct sockaddr_un addr = {0}; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, path_cstr, sizeof(addr.sun_path) - 1); if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); return srv_close_and_fail(sock); } // Set permissions if (chmod(path_cstr, 0777) < 0) { perror("chmod"); return srv_close_and_fail(sock); } if (listen(sock, listen_backlog) < 0) { perror("listen"); return srv_close_and_fail(sock); } log("Listening on Unix socket: %s (fd:%d)\n", path_cstr, sock); return sock; } static void print_usage(const char *prog) { fprintf(stderr, "fanr webchat server\n" "usage: %s [options]\n" "options:\n" " -p TCP port to listen on\n" " -u Unix socket path (overrides -p)\n" " -d SQLite database path (default: fanr.sqlite)\n" " -h, --help Show this help\n" "examples:\n" " %s -p 12345\n" " %s -u /tmp/webchat.sock\n" " %s -p 8080 -d /var/lib/webchat/chat.db\n", prog, prog, prog, prog); } #include "util/rw.c" #include "util/monotime.c" #include "util/blisp_parser.c" #include "util/b64.c" #include "util/passwordhash.c" #include "util/bwt.c" #include "util/ulid.c" #include "util/dbapi.c" #include "util/httpmulti.c" #include "wssys.c" #include "secret.key" #include "client.c" #include "http.c" #include "netloop.c" typedef struct cliargs cliargs; struct cliargs{ char const *port_str; char const *usock_path; char const *db_name; }; static cliargs parse_argv(int argc, char *argv[]){ cliargs a = {.db_name=DEFAULT_DB}; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) { a.port_str = argv[++i]; } else if (strcmp(argv[i], "-u") == 0 && i + 1 < argc) { a.usock_path = argv[++i]; } else if (strcmp(argv[i], "-d") == 0 && i + 1 < argc) { a.db_name = argv[++i]; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { print_usage(argv[0]); exit(0); } else { fprintf(stderr, "Unknown option: %s\n", argv[i]); print_usage(argv[0]); exit(1); } } if (!a.port_str && !a.usock_path) { fprintf(stderr, "Error: Either -p or -u is required\n"); print_usage(argv[0]); exit(1); } return a; } int main(int argc, char *argv[]) { signal(SIGPIPE, SIG_IGN); cliargs args = parse_argv(argc, argv); log("Web Chat server starting. (database: %s)\n", args.db_name); init_realtime_monotonic_offset(); int listen_fd = -1; if (args.usock_path) { str path = str_from_cstr(args.usock_path); if (path.len == 0) { fprintf(stderr, "Invalid path\n"); return 1; } listen_fd = create_unix_socket(path); } else if (args.port_str) { int port = atoi(args.port_str); if (port <= 0 || port > 65535) { fprintf(stderr, "Invalid port: %s\n", args.port_str); return 1; } listen_fd = create_inet_socket(port); } if (listen_fd < 0) diesys("failed to create listening socket"); unless(db_init(args.db_name)) diesys("Failed to initialise database [%s] [err: %s]", args.db_name, db_errmsg()); event_loop(listen_fd); close(listen_fd); db_close(); return 0; }