diff --git a/include/ilib/ipc.h b/include/ilib/ipc.h deleted file mode 100644 index d09df55..0000000 --- a/include/ilib/ipc.h +++ /dev/null @@ -1,29 +0,0 @@ -/* ipc.h - UNIX domain socket IPC listener and command processor */ -#ifndef __IPC_H__ -#define __IPC_H__ - -#include -#include -#include - -typedef struct ipc_server { - int server_fd; - char *socket_path; - subrelay_config_t *cfg; - subscriber_pool_t *subscribers; - pending_pool_t *pending; -} ipc_server_t; - -/* Initialize non-blocking UNIX domain socket server */ -int ipc_server_init(ipc_server_t *server, - subrelay_config_t *cfg, - subscriber_pool_t *subscribers, - pending_pool_t *pending); - -/* Non-blocking check for incoming client commands from subrelay-shell */ -int ipc_server_poll(ipc_server_t *server); - -/* Cleanup socket file and close descriptors */ -void ipc_server_destroy(ipc_server_t *server); - -#endif /* __IPC_H__ */ diff --git a/include/ilib/local.h b/include/ilib/local.h new file mode 100644 index 0000000..d37766d --- /dev/null +++ b/include/ilib/local.h @@ -0,0 +1,47 @@ +/* + * this is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * this is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see ."; + * + * (c) Alexander Vdolainen 2026 + * subrelay: a minimalistic maillist server based on external IMAPs/SMTPs + * + * local.h: local interface to maillist (UNIX socket) + */ +#ifndef __LOCAL_H__ +#define __LOCAL_H__ + +#include +#include +#include + +typedef struct local_ctrl { + int server_fd; + char *socket_path; + subrelay_config_t *cfg; + subscriber_pool_t *subscribers; + pending_pool_t *pending; +} local_ctrl_t; + +/* Initialize non-blocking UNIX domain socket server */ +int local_ctrl_init(local_ctrl_t *server, + subrelay_config_t *cfg, + subscriber_pool_t *subscribers, + pending_pool_t *pending); + +/* Non-blocking check for incoming client commands from subrelay-shell */ +int local_ctrl_poll(local_ctrl_t *server); + +/* Cleanup socket file and close descriptors */ +void local_ctrl_destroy(local_ctrl_t *server); + +#endif /* __LOCAL_H__ */ diff --git a/src/ipc.c b/src/local.c similarity index 53% rename from src/ipc.c rename to src/local.c index 8bfe96f..bd81878 100644 --- a/src/ipc.c +++ b/src/local.c @@ -1,4 +1,23 @@ -/* ipc.c - UNIX domain socket ASCII command listener */ +/* + * this is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * this is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see ."; + * + * (c) Alexander Vdolainen 2026 + * subrelay: a minimalistic maillist server based on external IMAPs/SMTPs + * + * local.c: local interface to maillist (UNIX socket) + */ + #include #include #include @@ -11,7 +30,10 @@ #include #include -#include +#include + +#define OUTBUF_MAX 2048 +#define EMAILLEN_MAX 512 static char *trim(char *str) { @@ -25,99 +47,78 @@ static char *trim(char *str) return str; } -/* Parse and dispatch ASCII commands from subrelay-shell */ -static void handle_ipc_command(ipc_server_t *server, int client_fd, char *cmd_line) +/* dispatch requests from local control tool */ +static void handle_ipc_command(local_ctrl_t *server, int client_fd, char *cmd_line) { char *line = trim(cmd_line); if (*line == '\0') return; char *cmd = strtok(line, " \t"); char *arg = strtok(NULL, " \t"); - - char response[2048]; + ssize_t wrt = 0; + char response[OUTBUF_MAX]; + int rc; response[0] = '\0'; - if (!cmd) { - snprintf(response, sizeof(response), "ERR invalid_command\n"); - } - /* ---------------------------------------------------------------------- */ - /* Command: ADD (Directly subscribe address, bypasses opt-in) */ - /* ---------------------------------------------------------------------- */ - else if (strcasecmp(cmd, "ADD") == 0) { - if (!arg) { - snprintf(response, sizeof(response), "ERR missing_email_argument\n"); - } else { - int rc = subscriber_add(server->subscribers, arg); + if (!cmd) snprintf(response, sizeof(response), "ERR invalid command\n"); + else if (strcasecmp(cmd, "ADD") == 0) { /* ADD - add email to subscribers list */ + if (!arg) snprintf(response, sizeof(response), "ERR missing argument\n"); + else { + rc = subscriber_add(server->subscribers, arg); if (rc == 0) { subscriber_save_file(server->subscribers); - snprintf(response, sizeof(response), "OK subscriber_added %s\n", arg); - } else if (rc == -EEXIST) { - snprintf(response, sizeof(response), "ERR subscriber_already_exists\n"); - } else { - snprintf(response, sizeof(response), "ERR failed_to_add_subscriber\n"); - } + snprintf(response, sizeof(response), "OK subscription added for: %s\n", arg); + } else if (rc == -EEXIST) snprintf(response, + sizeof(response), "ERR subscriber already exists\n"); + else snprintf(response, sizeof(response), "ERR failed to add subscriber\n"); } - } - /* ---------------------------------------------------------------------- */ - /* Command: DEL (Directly unsubscribe address) */ - /* ---------------------------------------------------------------------- */ - else if (strcasecmp(cmd, "DEL") == 0) { - if (!arg) { - snprintf(response, sizeof(response), "ERR missing_email_argument\n"); - } else { - int rc = subscriber_remove(server->subscribers, arg); + } else if (strcasecmp(cmd, "DEL") == 0) { /* DEL - remove email from list */ + if (!arg) snprintf(response, sizeof(response), "ERR missing argument\n"); + else { + rc = subscriber_remove(server->subscribers, arg); if (rc == 0) { subscriber_save_file(server->subscribers); - snprintf(response, sizeof(response), "OK subscriber_removed %s\n", arg); - } else { - snprintf(response, sizeof(response), "ERR subscriber_not_found\n"); - } + snprintf(response, sizeof(response), "OK subscription removed for %s\n", arg); + } else snprintf(response, sizeof(response), "ERR subscriber not found\n"); } - } - /* ---------------------------------------------------------------------- */ - /* Command: LIST (Output current subscribers) */ - /* ---------------------------------------------------------------------- */ - else if (strcasecmp(cmd, "LIST") == 0) { - snprintf(response, sizeof(response), "OK SUBSCRIBERS %lu\n", + } else if (strcasecmp(cmd, "LIST") == 0) { /* LIST - print list of current subscribers */ + snprintf(response, sizeof(response), "OK SUBSCRIBERS %lu\n", (unsigned long)server->subscribers->tree.nodecount); - write(client_fd, response, strlen(response)); + wrt = write(client_fd, response, strlen(response)); response[0] = '\0'; splay_node_t *curr = splay_first(&server->subscribers->tree); while (curr != NULL) { subscriber_entry_t *entry = (subscriber_entry_t *)curr; - char item[512]; + char item[EMAILLEN_MAX]; snprintf(item, sizeof(item), "%s\n", entry->email); - write(client_fd, item, strlen(item)); + wrt = write(client_fd, item, strlen(item)); curr = splay_next(&server->subscribers->tree, curr); } snprintf(response, sizeof(response), "END\n"); - } - /* ---------------------------------------------------------------------- */ - /* Command: STATUS (Output system stats & daemon info) */ - /* ---------------------------------------------------------------------- */ - else if (strcasecmp(cmd, "STATUS") == 0) { - snprintf(response, sizeof(response), + } else if (strcasecmp(cmd, "STATUS") == 0) { /* STATUS - output current stats */ + snprintf(response, sizeof(response), "OK LIST_NAME='%s' ADDRESS='%s' SUBSCRIBERS=%lu PENDING=%lu\n", server->cfg->maillist_name, server->cfg->maillist_addr, (unsigned long)server->subscribers->tree.nodecount, (unsigned long)server->pending->tree.nodecount); - } - else { - snprintf(response, sizeof(response), "ERR unknown_command\n"); } + else snprintf(response, sizeof(response), "ERR unknown command\n"); + (void)wrt; /* don't care about how much was written to local socket - not critical */ if (strlen(response) > 0) { - write(client_fd, response, strlen(response)); + wrt = write(client_fd, response, strlen(response)); } + return; } -int ipc_server_init(ipc_server_t *server, subrelay_config_t *cfg, subscriber_pool_t *subscribers, pending_pool_t *pending) +int local_ctrl_init(local_ctrl_t *server, subrelay_config_t *cfg, + subscriber_pool_t *subscribers, pending_pool_t *pending) { if (!server || !cfg || !cfg->socket_path) return -EINVAL; - memset(server, 0, sizeof(ipc_server_t)); + memset(server, 0, sizeof(local_ctrl_t)); server->cfg = cfg; server->subscribers = subscribers; server->pending = pending; @@ -155,7 +156,7 @@ int ipc_server_init(ipc_server_t *server, subrelay_config_t *cfg, subscriber_poo return 0; } -int ipc_server_poll(ipc_server_t *server) +int local_ctrl_poll(local_ctrl_t *server) { if (!server || server->server_fd < 0) return -EINVAL; @@ -167,7 +168,7 @@ int ipc_server_poll(ipc_server_t *server) return -errno; } - char buf[1024]; + char buf[OUTBUF_MAX]; ssize_t bytes = read(client_fd, buf, sizeof(buf) - 1); if (bytes > 0) { buf[bytes] = '\0'; @@ -178,7 +179,7 @@ int ipc_server_poll(ipc_server_t *server) return 0; } -void ipc_server_destroy(ipc_server_t *server) +void local_ctrl_destroy(local_ctrl_t *server) { if (!server) return; @@ -191,5 +192,5 @@ void ipc_server_destroy(ipc_server_t *server) free(server->socket_path); } - memset(server, 0, sizeof(ipc_server_t)); + memset(server, 0, sizeof(local_ctrl_t)); } diff --git a/src/subrelayd.c b/src/subrelayd.c index 9644588..4afeb04 100644 --- a/src/subrelayd.c +++ b/src/subrelayd.c @@ -1,4 +1,23 @@ -/* src/subrelayd/main.c - Main daemon process */ +/* + * this is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published + * by the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * this is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see ."; + * + * (c) Alexander Vdolainen 2026 + * subrelay: a minimalistic maillist server based on external IMAPs/SMTPs + * + * subrelayd.c: maillist daemon - main entry + */ + #define _POSIX_C_SOURCE 200809L #define _BSD_SOURCE #include @@ -16,7 +35,7 @@ #include #include #include -#include +#include static volatile int running = 1; @@ -62,25 +81,37 @@ static void print_usage(const char *progname) { printf("Usage: %s -c [-d]\n", progname); printf(" -c Path to subrelay configuration file\n"); - printf(" -d Run in foreground/debug mode\n"); + printf(" -f Run in foreground mode\n"); } int main(int argc, char **argv) { char *config_path = NULL; - int debug_mode = 0; - + int foreground_mode = 0; int opt; - while ((opt = getopt(argc, argv, "c:d")) != -1) { - switch (opt) { + + while(1) { + int option_index = 0; + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"config-file", required_argument, NULL, 'c'}, + {"foreground", no_argument, NULL, 'f'}, + {NULL, 0, NULL, 0}, + }; + + if((opt = getopt_long(argc, argv, "hc:f", long_options, &option_index)) == -1) + break; + + switch(opt) { + case 'h': print_usage(argv[0]); return 0; break; case 'c': config_path = optarg; break; - case 'd': debug_mode = 1; break; - default: print_usage(argv[0]); return EXIT_FAILURE; + case 'f': foreground_mode = 1; break; + default: print_usage(argv[0]); return EXIT_FAILURE; break; } } if (!config_path) { - fprintf(stderr, "error: configuration file required (-c )\n"); + fprintf(stderr, "error: configuration file required (-c )\n"); print_usage(argv[0]); return EXIT_FAILURE; } @@ -121,8 +152,8 @@ int main(int argc, char **argv) pending_load_file(&pending); /* 3. Initialize IPC Socket Server */ - ipc_server_t ipc; - if (ipc_server_init(&ipc, &cfg, &subscribers, &pending) != 0) { + local_ctrl_t ipc; + if (local_ctrl_init(&ipc, &cfg, &subscribers, &pending) != 0) { fprintf(stderr, "error: failed to bind IPC socket '%s'\n", cfg.socket_path); pending_pool_destroy(&pending); subscriber_pool_destroy(&subscribers); @@ -133,7 +164,7 @@ int main(int argc, char **argv) /* 4. Drop Root Privileges */ if (drop_privileges(cfg.run_user, cfg.run_group) != 0) { fprintf(stderr, "error: failed to drop privileges\n"); - ipc_server_destroy(&ipc); + local_ctrl_destroy(&ipc); pending_pool_destroy(&pending); subscriber_pool_destroy(&subscribers); config_free(&cfg); @@ -143,7 +174,7 @@ int main(int argc, char **argv) /* 5. Initialize Network Engine */ if (mail_engine_global_init() != 0) { fprintf(stderr, "error: failed to initialize libcurl global context\n"); - ipc_server_destroy(&ipc); + local_ctrl_destroy(&ipc); pending_pool_destroy(&pending); subscriber_pool_destroy(&subscribers); config_free(&cfg); @@ -153,7 +184,7 @@ int main(int argc, char **argv) mail_processor_t processor; mail_processor_init(&processor, &cfg, &subscribers, &pending); - if (!debug_mode) { + if (!foreground_mode) { if (daemon(0, 0) != 0) { fprintf(stderr, "error: failed to daemonize\n"); return EXIT_FAILURE; @@ -165,7 +196,7 @@ int main(int argc, char **argv) /* 6. Main Operational Daemon Loop */ while (running) { /* Check local UNIX socket for subrelay-shell IPC commands */ - ipc_server_poll(&ipc); + local_ctrl_poll(&ipc); /* Poll remote IMAP server for unseen messages */ mail_list_results_t unseen; @@ -195,7 +226,7 @@ int main(int argc, char **argv) pending_save_file(&pending); mail_engine_global_cleanup(); - ipc_server_destroy(&ipc); + local_ctrl_destroy(&ipc); pending_pool_destroy(&pending); subscriber_pool_destroy(&subscribers); config_free(&cfg);