local interface polishing, clean up;

This commit is contained in:
Alexander Vdolainen 2026-10-12 23:12:20 +03:00
parent 39d8ea845c
commit 747f30df68
4 changed files with 157 additions and 107 deletions

View File

@ -1,29 +0,0 @@
/* ipc.h - UNIX domain socket IPC listener and command processor */
#ifndef __IPC_H__
#define __IPC_H__
#include <ilib/config.h>
#include <ilib/subscribers.h>
#include <ilib/pending.h>
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__ */

47
include/ilib/local.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.";
*
* (c) Alexander Vdolainen 2026 <alex@vapaa.xyz>
* 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 <ilib/config.h>
#include <ilib/subscribers.h>
#include <ilib/pending.h>
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__ */

View File

@ -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 <http://www.gnu.org/licenses/>.";
*
* (c) Alexander Vdolainen 2026 <alex@vapaa.xyz>
* subrelay: a minimalistic maillist server based on external IMAPs/SMTPs
*
* local.c: local interface to maillist (UNIX socket)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -11,7 +30,10 @@
#include <sys/un.h>
#include <sys/stat.h>
#include <ilib/ipc.h>
#include <ilib/local.h>
#define OUTBUF_MAX 2048
#define EMAILLEN_MAX 512
static char *trim(char *str)
{
@ -25,78 +47,56 @@ 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 <email> (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 <email> - 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 <email> (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 <email> - 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) {
} 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) {
} 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,
@ -104,20 +104,21 @@ static void handle_ipc_command(ipc_server_t *server, int client_fd, char *cmd_li
(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));
}

View File

@ -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 <http://www.gnu.org/licenses/>.";
*
* (c) Alexander Vdolainen 2026 <alex@vapaa.xyz>
* 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 <stdio.h>
@ -16,7 +35,7 @@
#include <ilib/pending.h>
#include <ilib/mail_engine.h>
#include <ilib/mail_processor.h>
#include <ilib/ipc.h>
#include <ilib/local.h>
static volatile int running = 1;
@ -62,25 +81,37 @@ static void print_usage(const char *progname)
{
printf("Usage: %s -c <config_file> [-d]\n", progname);
printf(" -c <path> 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 <config_path>)\n");
fprintf(stderr, "error: configuration file required (-c <config_file>)\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);