Browse Source

Merge branch 'refactor-csocket' (closes #8)

master
Alex 'AdUser' Z 3 years ago
parent
commit
235737409f
  1. 2
      CMakeLists.txt
  2. 9
      src/CMakeLists.txt
  3. 1
      src/appconfig.c
  4. 1
      src/appconfig.h
  5. 6
      src/backends/CMakeLists.txt
  6. 217
      src/backends/mcast.c
  7. 88
      src/buf.c
  8. 16
      src/buf.h
  9. 167
      src/client.c
  10. 42
      src/cmsg.c
  11. 68
      src/cmsg.h
  12. 257
      src/commands.c
  13. 77
      src/commands.h
  14. 20
      src/common.h
  15. 42
      src/csocket-test.c
  16. 418
      src/csocket.c
  17. 61
      src/csocket.h
  18. 135
      src/daemon.c
  19. 2
      src/filter.h
  20. 2
      src/jail.h
  21. 6
      src/sources/CMakeLists.txt
  22. 271
      src/sources/mcast.c
  23. 6
      t/CMakeLists.txt
  24. 68
      t/t_buf.c
  25. 61
      t/t_cmd.c
  26. 32
      t/t_cmsg.c

2
CMakeLists.txt

@ -12,7 +12,6 @@ option(WITH_HARDENING "Enable hardening options" ON)
option(WITH_READLINE "Use readline library for client" ON)
option(WITH_PCRE "Build pcre-compatible filter" ON)
option(WITH_REDIS "Build redis source/backend" OFF)
option(WITH_MCAST "Build mcast source/backend" OFF)
option(WITH_IPSET "Build native ipset backend" OFF)
if (NOT DEFINED CMAKE_INSTALL_PREFIX)
@ -56,7 +55,6 @@ message(STATUS "- WITH_CSOCKET : ${WITH_CSOCKET}")
message(STATUS "- WITH_HARDENING : ${WITH_HARDENING}")
message(STATUS "- WITH_PCRE : ${WITH_PCRE}")
message(STATUS "- WITH_REDIS : ${WITH_REDIS}")
message(STATUS "- WITH_MCAST : ${WITH_MCAST}")
message(STATUS "- WITH_IPSET : ${WITH_IPSET}")
message(STATUS "- WITH_READLINE : ${WITH_READLINE}")
message(STATUS "Components:")

9
src/CMakeLists.txt

@ -1,10 +1,10 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SOURCES "daemon.c" "strlcpy.c" "config.c" "log.c" "matches.c" "ipaddr.c"
set(SOURCES "daemon.c" "strlcpy.c" "config.c" "buf.c" "log.c" "matches.c" "ipaddr.c"
"appconfig.c" "statefile.c" "source.c" "filter.c" "backend.c" "jail.c")
if (WITH_CSOCKET)
list(APPEND SOURCES "commands.c" "csocket.c" "cmsg.c")
list(APPEND SOURCES "commands.c" "csocket.c")
add_definitions("-DWITH_CSOCKET")
endif ()
@ -12,7 +12,7 @@ add_executable("f2b" ${SOURCES})
install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
if (WITH_CLIENT)
set(SOURCES "strlcpy.c" "log.c" "client.c" "cmsg.c" "commands.c" "csocket.c")
set(SOURCES "strlcpy.c" "client.c")
add_executable("f2bc" ${SOURCES})
if (WITH_READLINE)
add_definitions("-DWITH_READLINE")
@ -30,6 +30,9 @@ add_executable("f2b-filter-test" ${SOURCES})
set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c")
add_executable("f2b-backend-test" ${SOURCES})
set(SOURCES "strlcpy.c" "csocket-test.c" "log.c" "buf.c" "commands.c" "csocket.c")
add_executable("f2b-csocket-test" ${SOURCES})
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries("f2b" "dl")
target_link_libraries("f2b-source-test" "dl")

1
src/appconfig.c

@ -11,7 +11,6 @@
f2b_appconfig_t appconfig = {
.daemon = false,
.csock = -1,
.uid = 0,
.gid = 0,
.logdest = "file",

1
src/appconfig.h

@ -14,7 +14,6 @@ typedef struct f2b_appconfig_t {
bool daemon;
uid_t uid;
gid_t gid;
int csock;
char logdest[CONFIG_KEY_MAX];
char config_path[PATH_MAX];
char logfile_path[PATH_MAX];

6
src/backends/CMakeLists.txt

@ -5,12 +5,6 @@ set(BACKENDS "")
add_library("b_exec" MODULE "exec.c" "../strlcpy.c")
list(APPEND BACKENDS "exec")
if (WITH_MCAST)
add_library("b_mcast" MODULE "mcast.c" "../strlcpy.c"
"../commands.c" "../cmsg.c" "../csocket.c")
list(APPEND BACKENDS "mcast")
endif ()
find_library(REDIS_FOUND "hiredis")
if (WITH_REDIS AND REDIS_FOUND)
add_library("b_redis" MODULE "redis.c" "../strlcpy.c")

217
src/backends/mcast.c

@ -1,217 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include "../strlcpy.h"
#include "../commands.h"
#include "../cmsg.h"
#include "../csocket.h"
#include "backend.h"
#include "shared.c"
#define DEFAULT_MCAST_ADDR "239.255.186.1"
#define DEFAULT_MCAST_PORT "3370"
struct _config {
char name[ID_MAX + 1];
char error[256];
bool shared;
char maddr[INET_ADDRSTRLEN]; /**< multicast address */
char mport[6]; /**< multicast port */
char iface[IF_NAMESIZE]; /**< bind interface */
int sock;
struct sockaddr_storage sa;
socklen_t sa_len;
};
cfg_t *
create(const char *id) {
cfg_t *cfg = NULL;
assert(id != NULL);
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL;
strlcpy(cfg->name, id, sizeof(cfg->name));
strlcpy(cfg->maddr, DEFAULT_MCAST_ADDR, sizeof(cfg->maddr));
strlcpy(cfg->mport, DEFAULT_MCAST_PORT, sizeof(cfg->mport));
return cfg;
}
bool
config(cfg_t *cfg, const char *key, const char *value) {
assert(cfg != NULL);
assert(key != NULL);
assert(value != NULL);
if (strcmp(key, "group") == 0) {
if (strncmp(value, "239.255.", 8) != 0) {
strlcpy(cfg->error, "mcast group address should be inside 239.255.0.0/16 block", sizeof(cfg->error));
return false;
}
strlcpy(cfg->maddr, value, sizeof(cfg->maddr));
return true;
}
if (strcmp(key, "port") == 0) {
strlcpy(cfg->mport, value, sizeof(cfg->mport));
return true;
}
if (strcmp(key, "iface") == 0) {
strlcpy(cfg->iface, value, sizeof(cfg->iface));
return true;
}
return false;
}
bool
ready(cfg_t *cfg) {
assert(cfg != NULL);
if (cfg->maddr[0] && cfg->mport[0])
return true;
return false;
}
char *
error(cfg_t *cfg) {
assert(cfg != NULL);
return cfg->error;
}
bool
start(cfg_t *cfg) {
struct addrinfo hints;
struct addrinfo *result;
assert(cfg != NULL);
int ret, sock = -1;
if (cfg->shared && usage_inc(cfg->name) > 1)
return true;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
if ((ret = getaddrinfo(cfg->maddr, cfg->mport, &hints, &result)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't create socket: %s", gai_strerror(ret));
return false;
}
for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) {
if (sock >= 0) {
close(sock); /* from prev iteration */
sock = -1;
}
if ((sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't create socket: %s", strerror(errno));
continue;
}
memcpy(&cfg->sa, rp->ai_addr, rp->ai_addrlen);
cfg->sa_len = rp->ai_addrlen;
break;
}
freeaddrinfo(result);
if (sock < 0)
return false;
cfg->sock = sock;
return true;
return true;
}
bool
stop(cfg_t *cfg) {
assert(cfg != NULL);
if (cfg->shared && usage_dec(cfg->name) > 0)
return true;
close(cfg->sock);
cfg->sock = -1;
return true;
}
bool
ban(cfg_t *cfg, const char *ip) {
f2b_cmsg_t cmsg;
int ret;
assert(cfg != NULL);
memset(&cmsg, 0x0, sizeof(cmsg));
strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic));
cmsg.version = F2B_PROTO_VER;
cmsg.type = CMD_JAIL_IP_BAN;
f2b_cmd_append_arg(cmsg.data, sizeof(cmsg.data), cfg->name);
f2b_cmd_append_arg(cmsg.data, sizeof(cmsg.data), ip);
cmsg.size = strlen(cmsg.data);
cmsg.data[cmsg.size] = '\0';
f2b_cmsg_convert_args(&cmsg);
ret = f2b_csocket_send(cfg->sock, &cmsg, &cfg->sa, &cfg->sa_len);
if (ret <= 0) {
strlcpy(cfg->error, f2b_csocket_error(ret), sizeof(cfg->error));
return false;
}
return true;
}
bool
unban(cfg_t *cfg, const char *ip) {
assert(cfg != NULL);
(void)(cfg); /* suppress warning for unused variable 'cfg' */
(void)(ip); /* suppress warning for unused variable 'ip' */
return true;
}
bool
check(cfg_t *cfg, const char *ip) {
assert(cfg != NULL);
(void)(cfg); /* suppress warning for unused variable 'cfg' */
(void)(ip); /* suppress warning for unused variable 'ip' */
return false;
}
bool
ping(cfg_t *cfg) {
assert(cfg != NULL);
(void)(cfg); /* silence warning about unused variable */
return true;
}
void
destroy(cfg_t *cfg) {
assert(cfg != NULL);
free(cfg);
}

88
src/buf.c

@ -0,0 +1,88 @@
#include "common.h"
#include "buf.h"
bool
f2b_buf_alloc(f2b_buf_t *buf, size_t size) {
assert(buf != NULL);
assert(size > 0);
memset(buf, 0x0, sizeof(f2b_buf_t));
if ((buf->data = malloc(size)) == NULL)
return false; /* can't allocate memory */
buf->size = size;
return true;
}
void
f2b_buf_free(f2b_buf_t *buf) {
assert(buf != NULL);
free(buf->data);
memset(buf, 0x0, sizeof(f2b_buf_t));
}
size_t
f2b_buf_append(f2b_buf_t *buf, const char *str, size_t len) {
assert(buf != NULL);
assert(str != NULL);
if (len == 0)
len = strlen(str);
if ((buf->used + len) > buf->size) {
/* not enough space, append as much as possible */
len = buf->size - buf->used;
}
memcpy(&buf->data[buf->used], str, len);
buf->used += len;
buf->data[buf->used] = '\0';
return len;
}
/**
* @brief Extracts line terminated by delimiter
* @return Pointer to extracted string on success or NULL otherwise
* @note Use only with 'read' buffer type
*/
char *
f2b_buf_extract(f2b_buf_t *buf, const char *end) {
char *s = NULL;
size_t len = 0;
assert(buf != NULL);
assert(end != NULL);
if (buf->data == NULL || buf->used == 0)
return NULL; /* no data */
if ((s = strstr(buf->data, end)) == NULL)
return NULL; /* not found */
/* copy the data before modifying buffer */
len = s - buf->data;
if ((s = strndup(buf->data, len)) == NULL)
return NULL; /* malloc error */
/* shift data inside buffer */
len += strlen(end);
f2b_buf_splice(buf, len);
return s;
}
size_t
f2b_buf_splice(f2b_buf_t *buf, size_t len) {
assert(buf != NULL);
if (len == 0)
return len;
if (buf->used <= len)
len = buf->used;
buf->used -= len,
memmove(buf->data, &buf->data[len], buf->used);
buf->data[buf->used] = '\0';
return len;
}

16
src/buf.h

@ -0,0 +1,16 @@
#ifndef F2B_BUF_H_
#define F2B_BUF_H_
typedef struct f2b_buf_t {
size_t used; /**< bytes used in buffer */
size_t size; /**< available size in data */
char *data; /**< allocated buffer */
} f2b_buf_t;
bool f2b_buf_alloc(f2b_buf_t *buf, size_t max);
void f2b_buf_free(f2b_buf_t *buf);
size_t f2b_buf_append(f2b_buf_t *buf, const char *str, size_t size);
char * f2b_buf_extract(f2b_buf_t *buf, const char *end);
size_t f2b_buf_splice(f2b_buf_t *buf, size_t len);
#endif /* F2B_BUF_H_ */

167
src/client.c

@ -4,27 +4,23 @@
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include "common.h"
#include "cmsg.h"
#include "commands.h"
#include "csocket.h"
#include "client.h"
#include "log.h"
#include <signal.h>
struct {
enum { interactive = 0, oneshot } mode;
int csocket;
float timeout;
char csocket_spath[PATH_MAX];
char csocket_cpath[PATH_MAX];
char spath[PATH_MAX];
} opts = {
interactive, -1, 5.0,
interactive,
DEFAULT_CSOCKET_PATH,
DEFAULT_CSOCKET_CPATH, /* template */
};
static int csock = -1;
void usage(int exitcode) {
fputs("Usage: f2bc [-h] [-s <path>] [-c <command>]\n", stdout);
fputs("\t-h Show this message\n", stdout);
@ -34,60 +30,61 @@ void usage(int exitcode) {
}
int
handle_cmd(const char *line) {
f2b_cmsg_t cmsg;
struct sockaddr_storage addr;
socklen_t addrlen = 0;
handle_recv() {
char buf[WBUF_SIZE] = ""; /* our "read" is server "write" */
int ret;
memset(&addr, 0x0, sizeof(addr));
memset(&cmsg, 0x0, sizeof(cmsg));
cmsg.type = f2b_cmd_parse(&cmsg.data[0], sizeof(cmsg.data), line);
if (cmsg.type == CMD_HELP) {
f2b_cmd_help();
return EXIT_SUCCESS;
} else if (cmsg.type == CMD_NONE) {
printf("! unable to parse command line\n");
return EXIT_FAILURE;
}
/* fill other fields */
strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic));
cmsg.version = F2B_PROTO_VER;
cmsg.size = strlen(cmsg.data);
cmsg.data[cmsg.size] = '\0';
f2b_cmsg_convert_args(&cmsg);
cmsg.flags |= CMSG_FLAG_NEED_REPLY;
if ((ret = f2b_csocket_send(opts.csocket, &cmsg, NULL, &addrlen)) < 0)
return EXIT_FAILURE;
memset(&cmsg, 0x0, sizeof(cmsg));
addrlen = sizeof(addr);
if ((ret = f2b_csocket_recv(opts.csocket, &cmsg, &addr, &addrlen)) < 0) {
printf("! control sicket: %s\n", f2b_csocket_error(ret));
return EXIT_FAILURE;
if (csock < 0)
return 0; /* not connected */
ret = recv(csock, &buf, sizeof(buf), MSG_DONTWAIT);
if (ret > 0) {
write(fileno(stdout), buf, ret);
} else if (ret == 0) {
puts("connection closed");
exit(EXIT_SUCCESS); /* received EOF */
} else if (ret < 0 && errno == EAGAIN) {
return 0;
} else /* ret < 0 */ {
perror("recv()");
exit(EXIT_FAILURE);
}
return 0;
}
if (cmsg.type != CMD_RESP) {
printf("! recieved message not a 'response' type\n");
return EXIT_FAILURE;
int
handle_cmd(const char *line) {
const char *p = NULL;
char buf[WBUF_SIZE] = ""; /* our "read" is server "write" */
int ret; int len;
assert(line != NULL);
snprintf(buf, sizeof(buf), "%s\n", line);
p = buf;
while (p && *p != '\0') {
len = strlen(p);
ret = send(csock, p, strlen(p), 0);
if (ret < 0 && errno == EAGAIN) {
continue; /* try again */
} else if (ret < 0) {
perror("send()");
exit(EXIT_FAILURE);
} else if (ret == len){
break; /* all data sent */
} else /* ret > 0 */ {
p += ret;
}
}
fputs(cmsg.data, stdout);
fputc('\n', stdout);
return EXIT_SUCCESS;
}
void cleanup() {
f2b_csocket_disconnect(opts.csocket, opts.csocket_cpath);
unlink(opts.csocket_cpath);
return 0;
}
void
signal_handler(int signum) {
handle_signal(int signum) {
switch (signum) {
case SIGINT:
case SIGTERM:
cleanup();
exit(EXIT_SUCCESS);
break;
default:
@ -95,25 +92,53 @@ signal_handler(int signum) {
}
}
void
setup_sigaction(int signum) {
struct sigaction act;
memset(&act, 0x0, sizeof(act));
act.sa_handler = &handle_signal;
if (sigaction(signum, &act, NULL) != 0) {
perror("sigaction()");
exit(EXIT_FAILURE);
}
}
void
setup_socket() {
struct sockaddr_un saddr;
if ((csock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket()");
exit(EXIT_FAILURE);
}
memset(&saddr, 0x0, sizeof(saddr));
saddr.sun_family = AF_UNIX;
strlcpy(saddr.sun_path, opts.spath, sizeof(saddr.sun_path) - 1);
if (connect(csock, (struct sockaddr *) &saddr, sizeof(struct sockaddr_un)) < 0) {
perror("connect()");
exit(EXIT_FAILURE);
}
}
#ifdef WITH_READLINE
#include <readline/readline.h>
#include <readline/history.h>
rl_hook_func_t *rl_event_hook = &handle_recv;
#else
char *
readline(const char *prompt) {
char line[INPUT_LINE_MAX];
char line[RBUF_SIZE+1];
char *p;
while (1) {
line[0] = '\0';
p = &line[0];
handle_recv();
fputs(prompt, stdout);
if (!fgets(line, sizeof(line) - 1, stdin)) {
if (feof(stdin)) {
fputc('\n', stdout);
} else {
if (!feof(stdin))
fputs("read error\n", stdout);
}
return NULL;
}
while (isspace(*p)) p++;
@ -129,7 +154,6 @@ readline(const char *prompt) {
#endif
int main(int argc, char *argv[]) {
struct sigaction act;
char *line = NULL;
char opt = '\0';
int ret;
@ -138,10 +162,10 @@ int main(int argc, char *argv[]) {
switch (opt) {
case 'c':
opts.mode = oneshot;
line = strndup(optarg, INPUT_LINE_MAX);
line = strndup(optarg, RBUF_SIZE);
break;
case 's':
strlcpy(opts.csocket_spath, optarg, sizeof(opts.csocket_spath));
strlcpy(opts.spath, optarg, sizeof(opts.spath));
break;
case 'h':
usage(EXIT_SUCCESS);
@ -152,23 +176,14 @@ int main(int argc, char *argv[]) {
}
}
SA_REGISTER(SIGTERM, &signal_handler);
SA_REGISTER(SIGINT, &signal_handler);
setup_sigaction(SIGTERM);
setup_sigaction(SIGINT);
/* prepare client side of socket */
ret = mkstemp(opts.csocket_cpath);
if (ret >= 0)
close(ret); /* suppress compiler warning */
unlink(opts.csocket_cpath); /* remove regular file created by mkstemp() */
if ((opts.csocket = f2b_csocket_connect(opts.csocket_spath, opts.csocket_cpath)) <= 0)
exit(EXIT_FAILURE);
f2b_csocket_rtimeout(opts.csocket, opts.timeout);
setup_socket();
if (opts.mode == oneshot) {
ret = handle_cmd(line);
f2b_csocket_disconnect(opts.csocket, opts.csocket_cpath);
shutdown(csock, SHUT_RDWR);
exit(ret);
}
@ -183,7 +198,5 @@ int main(int argc, char *argv[]) {
}
putc('\n', stdout);
cleanup();
return EXIT_SUCCESS;
}

42
src/cmsg.c

@ -1,42 +0,0 @@
#include "common.h"
#include "commands.h"
#include "cmsg.h"
#include <sys/uio.h>
void
f2b_cmsg_convert_args(f2b_cmsg_t *msg) {
assert(msg != NULL);
for (size_t i = 0; i < msg->size && i < sizeof(msg->data); i++) {
if (msg->data[i] == '\n')
msg->data[i] = '\0';
}
}
int
f2b_cmsg_extract_args(const f2b_cmsg_t *msg, const char **argv) {
char prev = '\0';
size_t argc = 0;
assert(msg != NULL);
assert(argv != NULL);
if (msg->size == 0)
return 0; /* no args */
if (msg->data[msg->size - 1] != '\0')
return -1; /* message data not null-terminated */
for (size_t i = 0; i < msg->size; i++) {
if (prev == '\0' && msg->data[i] != '\0')
argv[argc] = &msg->data[i], argc++;
if (argc >= DATA_ARGS_MAX) {
argc = -1;
break;
}
prev = msg->data[i];
}
return argc;
}

68
src/cmsg.h

@ -1,68 +0,0 @@
#ifndef F2B_CMSG_H_
#define F2B_CMSG_H_
/**
* @file
* This header contains definitions of control messages format and routines
*/
/**
* @def DATA_LEN_MAX
* Maximum length of data in packet
*/
#define DATA_LEN_MAX 1476 /* 1500 - (16 bytes of cmsg header + 8 bytes of udp) */
/**
* @def DATA_ARGS_MAX
* Maximum count of data pieces in packet
*/
#define DATA_ARGS_MAX 6 /* number of args in data */
/**
* @def F2B_PROTO_VER
* Protocol version of control message
*/
#define F2B_PROTO_VER 1
/**
* @def CMSG_FLAG_NEED_REPLY
* Server should reply to this control message
*/
#define CMSG_FLAG_NEED_REPLY 0x01
/**
* @def CMSG_FLAG_AUTH_PASS
* This control message contains password
*/
#define CMSG_FLAG_AUTH_PASS 0x02
/**
* f2b control message
* @note Use sendmsg/recvmsg and iovec structs to pack/unpack
*/
typedef struct f2b_cmsg_t {
char magic[3]; /**< magic string "F2B" */
uint8_t version; /**< protocol version */
/* 4 bytes */
uint8_t type; /**< command type, cast from enum f2b_cmd_type */
uint8_t flags; /**< CMSG_FLAG_* */
uint16_t size; /**< payload length */
/* 8 bytes */
char pass[8]; /**< auth data */
/* 16 bytes */
/* end of header */
char data[DATA_LEN_MAX]; /**< set of "\n"-terminated strings */
/* end of data */
} f2b_cmsg_t;
/**
* @brief Convert every '\n' in data to '\0'
* @param msg Pointer to control message
*/
void f2b_cmsg_convert_args(f2b_cmsg_t *msg);
/**
* @brief Fill @a argv array with pointers to found data pieces
* @param msg Pointer to control message
* @param argv Array of pointers
* @returns Number of found args
*/
int f2b_cmsg_extract_args(const f2b_cmsg_t *msg, const char **argv);
#endif /* F2B_CMSG_H_ */

257
src/commands.c

@ -5,211 +5,208 @@
* published by the Free Software Foundation.
*/
#include "common.h"
#include "buf.h"
#include "commands.h"
struct f2b_cmd_t {
struct cmd_desc {
enum f2b_command_type type;
const short int argc;
const short int tokenc;
const char *help;
const char *tokens[CMD_TOKENS_MAX];
} commands[CMD_MAX_NUMBER] = {
[CMD_NONE] = {
.argc = 0, .tokenc = 0,
.tokens = { NULL },
.help = "Unspecified command"
},
[CMD_RESP] = {
.argc = 1, .tokenc = 0,
.tokens = { NULL },
.help = "Command response, used internally",
},
[CMD_HELP] = {
const char *tokens[CMD_TOKENS_MAXCOUNT];
} commands[] = {
{
.type = CMD_HELP,
.argc = 0, .tokenc = 1,
.tokens = { "help", NULL },
.help = "Show available commands",
},
[CMD_PING] = {
.argc = 0, .tokenc = 1,
.tokens = { "ping", NULL },
.help = "Check the connection",
},
[CMD_STATUS] = {
}, {
.type = CMD_STATUS,
.argc = 0, .tokenc = 1,
.tokens = { "status", NULL },
.help = "Show general stats and jails list",
},
[CMD_LOG_ROTATE] = {
.argc = 0, .tokenc = 2,
.tokens = { "log", "rotate", NULL },
.help = "Reopen daemon's own log file",
},
[CMD_RELOAD] = {
}, {
.type = CMD_RELOAD,
.argc = 0, .tokenc = 1,
.tokens = { "reload", NULL },
.help = "Reload own config, all jails will be reset.",
},
[CMD_SHUTDOWN] = {
}, {
.type = CMD_SHUTDOWN,
.argc = 0, .tokenc = 1,
.tokens = { "shutdown", NULL },
.help = "Gracefully terminate f2b daemon",
},
[CMD_LOG_LEVEL] = {
}, {
.type = CMD_LOG_ROTATE,
.argc = 0, .tokenc = 2,
.tokens = { "log", "rotate", NULL },
.help = "Reopen daemon's own log file",
}, {
.type = CMD_LOG_LEVEL,
.argc = 1, .tokenc = 3,
.tokens = { "log", "level", "<level>", NULL },
.help = "Change maximum level of logged messages",
},
[CMD_JAIL_STATUS] = {
}, {
.type = CMD_JAIL_STATUS,
.argc = 1, .tokenc = 3,
.tokens = { "jail", "<jailname>", "status", NULL },
.help = "Show status and stats of given jail",
},
[CMD_JAIL_SET] = {
}, {
.type = CMD_JAIL_SET,
.argc = 3, .tokenc = 5,
.tokens = { "jail", "<jailname>", "set", "<param>", "<value>", NULL },
.help = "Set parameter of given jail",
},
[CMD_JAIL_IP_STATUS] = {
}, {
.type = CMD_JAIL_IP_STATUS,
.argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "status", "<ip>", NULL },
.help = "Show ip status in given jail",
},
[CMD_JAIL_IP_BAN] = {
}, {
.type = CMD_JAIL_IP_BAN,
.argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "ban", "<ip>", NULL },
.help = "Forcefully ban some ip in given jail",
},
[CMD_JAIL_IP_RELEASE] = {
}, {
.type = CMD_JAIL_IP_RELEASE,
.argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "release", "<ip>", NULL },
.help = "Forcefully release some ip in given jail",
},
[CMD_JAIL_FILTER_STATS] = {
}, {
.type = CMD_JAIL_FILTER_STATS,
.argc = 1, .tokenc = 4,
.tokens = { "jail", "<jailname>", "filter", "stats", NULL },
.help = "Show matches stats for jail regexps",
},
[CMD_JAIL_FILTER_RELOAD] = {
}, {
.type = CMD_JAIL_FILTER_RELOAD,
.argc = 1, .tokenc = 4,
.tokens = { "jail", "<jailname>", "filter", "reload", NULL },
.help = "Reload regexps for given jail",
},
}, {
.type = CMD_UNKNOWN,
.argc = 0, .tokenc = 0,
.tokens = { NULL },
.help = "",
}
};
void
static char *help = NULL;
const char *
f2b_cmd_help() {
struct f2b_cmd_t *cmd = NULL;
f2b_buf_t buf;
const char **p = NULL;
fputs("Available commands:\n\n", stdout);
for (size_t i = CMD_PING; i < CMD_MAX_NUMBER; i++) {
cmd = &commands[i];
if (cmd->tokens[0] == NULL)
continue;
for (p = cmd->tokens; *p != NULL; p++)
fprintf(stdout, "%s ", *p);
fprintf(stdout, "\n\t%s\n\n", cmd->help);
if (help)
return help;
if (!f2b_buf_alloc(&buf, 8192))
return "internal error: can't allocate memory\n";
f2b_buf_append(&buf, "Available commands:\n\n", 0);
for (struct cmd_desc *cmd = commands; cmd->type != CMD_UNKNOWN; cmd++) {
for (p = cmd->tokens; *p != NULL; p++) {
f2b_buf_append(&buf, *p, 0);
f2b_buf_append(&buf, " ", 1);
}
f2b_buf_append(&buf, "\n\t", 2);
f2b_buf_append(&buf, cmd->help, 0);
f2b_buf_append(&buf, "\n\n", 2);
}
return;
help = strndup(buf.data, buf.used);
f2b_buf_free(&buf);
return help;
}
f2b_cmd_t *
f2b_cmd_create(const char *line) {
f2b_cmd_t *cmd = NULL;
assert(line != NULL);
if ((cmd = calloc(1, sizeof(f2b_cmd_t))) == NULL)
return NULL;
if (f2b_buf_alloc(&cmd->data, strlen(line))) {
if (f2b_cmd_parse(cmd, line))
return cmd;
free(cmd);
cmd = NULL;
}
return cmd;
}
void
f2b_cmd_append_arg(char *buf, size_t bufsize, const char *arg) {
assert(buf != NULL);
assert(arg != NULL);
strlcat(buf, arg, bufsize);
strlcat(buf, "\n", bufsize);
f2b_cmd_destroy(f2b_cmd_t *cmd) {
f2b_buf_free(&cmd->data);
free(cmd);
}
/**
* @brief Parse command from line
* @param buf Buffer for command parameters
* @param bufsize SSize of buffer above
* @param src Line taken from user input
* @return Type of parsed command or CMD_NONE if no matches
*/
enum f2b_cmd_type
f2b_cmd_parse(char *buf, size_t bufsize, const char *src) {
size_t tokenc = 0; /* tokens count */
char *tokens[CMD_TOKENS_MAX] = { NULL };
char line[INPUT_LINE_MAX];
char *p;
bool
f2b_cmd_parse(f2b_cmd_t *cmd, const char *src) {
char *p = NULL;
assert(cmd != NULL);
assert(src != NULL);
assert(line != NULL);
/* strip leading spaces */
while (isblank(*line))
while (isblank(*src))
src++;
/* strip trailing spaces, newlines, etc */
strlcpy(line, src, sizeof(line));
for (size_t l = strlen(line); l >= 1 && isspace(line[l - 1]); l--)
line[l - 1] = '\0';
if (line[0] == '\0')
return CMD_NONE; /* empty string */
f2b_buf_alloc(&cmd->data, strlen(src) + 1);
f2b_buf_append(&cmd->data, src, 0);
/* simple commands without args */
if (strcmp(line, "ping") == 0) { return CMD_PING; }
else if (strcmp(line, "help") == 0) { return CMD_HELP; }
else if (strcmp(line, "status") == 0) { return CMD_STATUS; }
else if (strcmp(line, "reload") == 0) { return CMD_RELOAD; }
else if (strcmp(line, "shutdown") == 0) { return CMD_SHUTDOWN; }
cmd->argc = 1; /* we has at least one arg */
cmd->args[0] = cmd->data.data;
if (strcmp(src, "help") == 0) { cmd->type = CMD_HELP; return true; }
else if (strcmp(src, "status") == 0) { cmd->type = CMD_STATUS; return true; }
else if (strcmp(src, "reload") == 0) { cmd->type = CMD_RELOAD; return true; }
else if (strcmp(src, "shutdown") == 0) { cmd->type = CMD_SHUTDOWN; return true; }
/* split string to tokens */
tokenc = 1; /* we has at least one token */
tokens[0] = strtok_r(line, " \t", &p);
for (size_t i = 1; i < CMD_TOKENS_MAX; i++) {
tokens[i] = strtok_r(NULL, " \t", &p);
if (tokens[i] == NULL)
p = cmd->data.data;
cmd->args[0] = strtok_r(cmd->data.data, " \t", &p);
for (size_t i = 1; i < CMD_TOKENS_MAXCOUNT; i++) {
cmd->args[i] = strtok_r(NULL, " \t", &p);
if (cmd->args[i] == NULL)
break;
tokenc++;
cmd->argc++;
}
buf[0] = '\0';
if (strcmp(line, "jail") == 0 && tokenc > 1) {
if (strcmp(cmd->args[0], "jail") == 0 && cmd->argc > 1) {
/* commands for jail */
f2b_cmd_append_arg(buf, bufsize, tokens[1]);
if (tokenc == 3 && strcmp(tokens[2], "status") == 0) {
return CMD_JAIL_STATUS;
if (cmd->argc == 3 && strcmp(cmd->args[2], "status") == 0) {
cmd->type = CMD_JAIL_STATUS; return true;
}
if (tokenc == 5 && strcmp(tokens[2], "set") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[3]);
f2b_cmd_append_arg(buf, bufsize, tokens[4]);
return CMD_JAIL_SET;
if (cmd->argc == 5 && strcmp(cmd->args[2], "set") == 0) {
cmd->type = CMD_JAIL_SET; return true;
}
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "status") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]);
return CMD_JAIL_IP_STATUS;
if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "status") == 0) {
cmd->type = CMD_JAIL_IP_STATUS; return true;
}
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "ban") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]);
return CMD_JAIL_IP_BAN;
if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "ban") == 0) {
cmd->type = CMD_JAIL_IP_BAN; return true;
}
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "release") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]);
return CMD_JAIL_IP_RELEASE;
if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "release") == 0) {
cmd->type = CMD_JAIL_IP_RELEASE; return true;
}
if (tokenc == 4 && strcmp(tokens[2], "filter") == 0 && strcmp(tokens[3], "stats") == 0) {
return CMD_JAIL_FILTER_STATS;
if (cmd->argc == 4 && strcmp(cmd->args[2], "filter") == 0 && strcmp(cmd->args[3], "stats") == 0) {
cmd->type = CMD_JAIL_FILTER_STATS; return true;
}
if (tokenc == 4 && strcmp(tokens[2], "filter") == 0 && strcmp(tokens[3], "reload") == 0) {
return CMD_JAIL_FILTER_RELOAD;
if (cmd->argc == 4 && strcmp(cmd->args[2], "filter") == 0 && strcmp(cmd->args[3], "reload") == 0) {
cmd->type = CMD_JAIL_FILTER_RELOAD; return true;
}
} else if (strcmp(line, "log") == 0 && tokenc > 1) {
if (tokenc == 2 && strcmp(tokens[1], "rotate") == 0) {
return CMD_LOG_ROTATE;
} else if (strcmp(cmd->args[0], "log") == 0 && cmd->argc > 1) {
if (cmd->argc == 2 && strcmp(cmd->args[1], "rotate") == 0) {
cmd->type = CMD_LOG_ROTATE; return true;
}
if (tokenc == 3 && strcmp(tokens[1], "level") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[2]);
return CMD_LOG_LEVEL;
if (cmd->argc == 3 && strcmp(cmd->args[1], "level") == 0) {
cmd->type = CMD_LOG_LEVEL; return true;
}
}
cmd->type = CMD_UNKNOWN;
f2b_buf_free(&cmd->data);
return CMD_NONE;
}
bool
f2b_cmd_check_argc(enum f2b_cmd_type type, int argc) {
if (commands[type].argc == argc)
return true;
return false;
}

77
src/commands.h

@ -10,30 +10,23 @@
/**
* @file
* This header contains definition of control commands and routines
* for work with data buffer of control message
* for parsing user input
*/
/**
* Maximum length of input line in client
* @note yes, i know about LINE_MAX
*/
#define INPUT_LINE_MAX 256
/** Maximum count of data pieces in control message data buf */
#define CMD_TOKENS_MAX 6
#define CMD_TOKENS_MAXCOUNT 6 /**< Maximum count of data pieces in control message data buf */
#define CMD_TOKENS_MAXSIZE 260 /**< parsed tokens */
/** control command type */
enum f2b_cmd_type {
CMD_NONE = 0, /**< unset */
CMD_RESP, /**< response of command */
CMD_HELP, /**< show help for commands (used internally by client) */
CMD_PING = 8, /**< check connection */
CMD_STATUS, /**< show general status of f2b daemon */
CMD_LOG_ROTATE,/**< reopen logfile. works only if set `logdest = file` */
CMD_RELOAD, /**< reload all jails */
CMD_SHUTDOWN, /**< gracefull shutdown */
CMD_LOG_LEVEL, /**< change maximum level of logged messages */
enum f2b_command_type {
CMD_UNKNOWN = 0, /**< unset */
CMD_HELP, /**< show help for commands */
CMD_STATUS, /**< show general status of f2b daemon */
CMD_RELOAD, /**< reload all jails */
CMD_SHUTDOWN, /**< gracefull shutdown daemon */
/* logging */
CMD_LOG_ROTATE, /**< reopen logfile. (only for `logdest = file`) */
CMD_LOG_LEVEL, /**< change maximum level of logged messages */
/* jail commands */
CMD_JAIL_STATUS = 16, /**< show status of given jail */
CMD_JAIL_STATUS, /**< show status of given jail */
CMD_JAIL_SET, /**< set parameter of given jail */
CMD_JAIL_IP_STATUS, /**< show status of given ip */
CMD_JAIL_IP_BAN, /**< force ban given ip */
@ -43,36 +36,42 @@ enum f2b_cmd_type {
CMD_MAX_NUMBER, /**< placeholder */
};
/** control command type */
typedef struct f2b_cmd_t {
enum f2b_command_type type;
int argc;
char *args[CMD_TOKENS_MAXCOUNT+1];
f2b_buf_t data;
} f2b_cmd_t;
/**
* @brief Print to stdout help for defined commands
* @brief Returns multiline string with commands list and it's brief descriptions
* @returns constant multiline string
*/
void f2b_cmd_help();
const char *
f2b_cmd_help();
/**
* @brief Try to parse user input
* @param buf Buffer of control message for storing parsed args
* @param bufsize Size of buffer size above
* @param src Line with user input
* @returns @a CMD_NONE if parsing fails, or cmd type less than @a CMD_MAX_NUMBER on success
* @brief Creates new struct from user input
* @param line User input string
* @returns pointer to newly allocated struct or NULL on malloc()/parse error
*/
enum f2b_cmd_type
f2b_cmd_parse(char *buf, size_t bufsize, const char *src);
f2b_cmd_t *
f2b_cmd_create(const char *line);
/**
* @brief Append data piece to data buffer of control message
* @param buf Buffer of control message for storing parsed args
* @param bufsize Size of buffer size above
* @param arg Piece to append
* @brief Frees memory
*/
void
f2b_cmd_append_arg(char *buf, size_t bufsize, const char *arg);
f2b_cmd_destroy(f2b_cmd_t *cmd);
/**
* @brief Checks is args count match given command type
* @param type Command type
* @param argc Args count
* @returns true if matches, false if not
* @brief Try to parse user input
* @param cmd pointer to preallocated f2b_cmd_t struct
* @param src Line with user input
* @returns true on success, false otherwise
*/
bool
f2b_cmd_check_argc(enum f2b_cmd_type type, int argc);
f2b_cmd_parse(f2b_cmd_t *cmd, const char *src);
#endif /* F2B_COMMANDS_H_ */

20
src/common.h

@ -39,17 +39,13 @@
*/
#define DEFAULT_PIDFILE_PATH "/var/run/f2b.pid"
/**
* Default path of unix control socket (server endpoint)
* Default path of unix control socket
*/
#define DEFAULT_CSOCKET_PATH "/var/run/f2b.sock"
/**
* Default path of directory to store ip states for jails
*/
#define DEFAULT_STATEDIR_PATH "/var/db/f2b"
/**
* Template for making path for client side of connection to control socket
*/
#define DEFAULT_CSOCKET_CPATH "/tmp/f2bc-sock-XXXXXX"
/**
* @def UNUSED
@ -57,16 +53,8 @@
*/
#define UNUSED(x) (void)(x)
/**
* @def SA_REGISTER
* Register signal handler
*/
#define SA_REGISTER(SIGNUM, HANDLER) \
memset(&act, 0x0, sizeof(act)); \
act.sa_handler = HANDLER; \
if (sigaction(SIGNUM, &act, NULL) != 0) { \
f2b_log_msg(log_fatal, "can't register handler for " #SIGNUM); \
return EXIT_FAILURE; \
}
/* default size of buffers */
#define RBUF_SIZE 256
#define WBUF_SIZE 32768 /* 32Kb */
#endif /* F2B_COMMON_H_ */

42
src/csocket-test.c

@ -0,0 +1,42 @@
#include "common.h"
#include "buf.h"
#include "log.h"
#include "commands.h"
#include "csocket.h"
static int run = 1;
void
cmd_handler(const f2b_cmd_t *cmd, f2b_buf_t *res) {
fprintf(stdout, "[handler] received cmd with type %d and %d args:\n", cmd->type, cmd->argc);
for (int i = 0; i < cmd->argc; i++) {
fprintf(stdout, "[handler] arg %d : %s\n", i + 1, cmd->args[i]);
}
UNUSED(res);
return;
}
int main(int argc, const char **argv) {
f2b_csock_t *csock = NULL;
if (argc < 2) {
puts("Usage: csocket-test <path>");
exit(EXIT_FAILURE);
}
f2b_log_set_level("debug");
f2b_log_to_stderr();
if ((csock = f2b_csocket_create(argv[1])) == NULL) {
perror("f2b_csocket_create()");
exit(EXIT_FAILURE);
}
while (run) {
f2b_csocket_poll(csock, cmd_handler);
sleep(1);
}
f2b_csocket_destroy(csock);
return 0;
}

418
src/csocket.c

@ -5,237 +5,269 @@
* published by the Free Software Foundation.
*/
#include "common.h"
#include "buf.h"
#include "log.h"
#include "commands.h"
#include "cmsg.h"
#include "csocket.h"
#include "log.h"
int
f2b_csocket_create(const char *path) {
struct sockaddr_un addr;
int csock = -1;
int flags = -1;
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/select.h>
assert(path != NULL);
typedef struct f2b_conn_t {
f2b_buf_t recv;
f2b_buf_t send;
int sock;
} f2b_conn_t;
if ((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno));
return -1;
}
struct f2b_csock_t {
f2b_conn_t *clients[MAXCONNS];
const char *path;
int sock;
};
memset(&addr, 0x0, sizeof(addr));
addr.sun_family = AF_UNIX;
strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
/* helpers */
if ((flags = fcntl(csock, F_GETFL, 0)) < 0)
return -1;
if (fcntl(csock, F_SETFL, flags | O_NONBLOCK) < 0) {
f2b_log_msg(log_error, "can't set non-blocking mode on socket: %s", strerror(errno));
return -1;
}
static inline int
max(int a, int b) {
return a > b ? a : b;
}
unlink(path);
if (bind(csock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) {
f2b_log_msg(log_error, "bind() on socket failed: %s", strerror(errno));
return -1;
/* connection-related functions */
f2b_conn_t *
f2b_conn_create(size_t rbuf, size_t wbuf) {
f2b_conn_t *conn = NULL;
if ((conn = calloc(1, sizeof(f2b_conn_t))) != NULL) {
if (f2b_buf_alloc(&conn->recv, rbuf)) {
if (f2b_buf_alloc(&conn->send, wbuf)) {
return conn;
}
f2b_buf_free(&conn->recv);
}
free(conn);
}
return csock;
return NULL;
}
void
f2b_csocket_destroy(int sock, const char *path) {
assert(path != NULL);
if (sock >= 0)
close(sock);
unlink(path);
return;
f2b_conn_destroy(f2b_conn_t *conn) {
f2b_buf_free(&conn->recv);
f2b_buf_free(&conn->send);
free(conn);
}
int
f2b_csocket_connect(const char *spath, const char *cpath) {
struct sockaddr_un caddr, saddr;
int csock = -1;
f2b_conn_process(f2b_conn_t *conn, bool in, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) {
f2b_cmd_t *cmd = NULL;
int retval;
/* handle incoming data */
if (in) {
char tmp[RBUF_SIZE] = "";
char *line = NULL;
ssize_t read = 0;
size_t avail = conn->recv.size - conn->recv.used;
read = recv(conn->sock, tmp, avail, MSG_DONTWAIT);
if (read == 0 || (read < 0 && errno == ECONNRESET)) {
f2b_log_msg(log_debug, "received connection close on socket %d", conn->sock);
return -1;
}
if (read < 0) {
f2b_log_msg(log_error, "received error on sock %d: %s", conn->sock, strerror(errno));
return -1;
}
if (read > 0) {
tmp[read] = '\0';
for (const char *p = tmp; *p != '\0'; p++) {
if (isgraph(*p) || isspace(*p))
continue;
f2b_log_msg(log_error, "non-printable character in data on sock %d", conn->sock);
return -1;
}
retval = f2b_buf_append(&conn->recv, tmp, read);
f2b_log_msg(log_debug, "received %zd bytes from socket %d, append %d to buf", read, conn->sock, retval);
/* extract message(s) */
while ((line = f2b_buf_extract(&conn->recv, "\n")) != NULL) {
if (strlen(line) == 0) {
free(line);
continue;
}
f2b_log_msg(log_debug, "extracted line: %s", line);
if ((cmd = f2b_cmd_create(line)) != NULL) {
cb(cmd, &conn->send); /* handle command */
f2b_cmd_destroy(cmd);
} else {
f2b_buf_append(&conn->send, "can't parse input\n", 0);
}
free(line);
}
if (conn->recv.used >= conn->recv.size) {
f2b_log_msg(log_error, "drop connection on socket %d, recv buffer overflow", conn->sock);
return -1;
}
}
}
/* handle outgoing data */
if (conn->send.used > 0) {
f2b_log_msg(log_debug, "sending %zu bytes to socket %d", conn->send.used, conn->sock);
retval = send(conn->sock, conn->send.data, conn->send.used, MSG_DONTWAIT | MSG_NOSIGNAL);
if (retval > 0) {
f2b_buf_splice(&conn->send, retval);
f2b_log_msg(log_debug, "sent %d bytes to socket %d (%zu remains)", retval, conn->sock, conn->send.used);
} else if (retval < 0 && errno != EAGAIN) {
f2b_log_msg(log_error, "can't send() to socket %d: %s", conn->sock, strerror(errno));
return -1; /* remote side closed connection */
}
}
return 0;
}
/* control socket-related functions */
assert(spath != NULL);
assert(cpath != NULL);
f2b_csock_t *
f2b_csocket_create(const char *path) {
f2b_csock_t *csock;
struct sockaddr_un addr;
int sock = -1;
assert(path != NULL);
memset(&saddr, 0x0, sizeof(caddr));
memset(&caddr, 0x0, sizeof(saddr));
if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) {
f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno));
return NULL;
}
caddr.sun_family = AF_LOCAL;
strlcpy(caddr.sun_path, cpath, sizeof(caddr.sun_path));
saddr.sun_family = AF_LOCAL;
strlcpy(saddr.sun_path, spath, sizeof(saddr.sun_path));
memset(&addr, 0x0, sizeof(addr));
addr.sun_family = AF_UNIX;
strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if ((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
f2b_log_msg(log_error, "can't create control socket");
return -1;
unlink(path);
if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) {
f2b_log_msg(log_error, "bind() on socket failed: %s", strerror(errno));
return NULL;
}
if (bind(csock, (struct sockaddr *) &caddr, sizeof(struct sockaddr_un)) != 0) {
f2b_log_msg(log_error, "bind() to local side of socket failed: %s", strerror(errno));
return -1;
if (listen(sock, 5) < 0) {
f2b_log_msg(log_error, "listen() on socket failed: %s", strerror(errno));
return NULL;
}
if (connect(csock, (struct sockaddr *) &saddr, sizeof(struct sockaddr_un)) != 0) {
f2b_log_msg(log_error, "connect() to socket failed: %s", strerror(errno));
return -1;
if ((csock = calloc(1, sizeof(f2b_csock_t))) == NULL) {
f2b_log_msg(log_error, "can't allocate memory for csocket struct");
shutdown(sock, SHUT_RDWR);
unlink(path);
return NULL;
}
csock->sock = sock;
csock->path = path;
return csock;
}
void
f2b_csocket_disconnect(int sock, const char *cpath) {
unlink(cpath);
if (sock >= 0)
close(sock);
f2b_csocket_destroy(f2b_csock_t *csock) {
f2b_conn_t *conn = NULL;
assert(csock != NULL);
if (csock->sock >= 0)
shutdown(csock->sock, SHUT_RDWR);
if (csock->path != NULL)
unlink(csock->path);
for (int i = 0; i < MAXCONNS; i++) {
if ((conn = csock->clients[i]) == NULL)
continue;
f2b_conn_destroy(conn);
csock->clients[i] = NULL;
}
free(csock);
return;
}
void
f2b_csocket_rtimeout(int sock, float timeout) {
int ret = 0;
struct timeval tv;
tv.tv_sec = (int) timeout;
tv.tv_usec = (int) ((timeout - tv.tv_sec) * 1000000);
ret = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(struct timeval));
if (ret == 0)
return;
f2b_log_msg(log_warn, "can't set recv timeout for csocket: %s", strerror(errno));
}
f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) {
struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };
struct ucred peer;
socklen_t peerlen = 0;
fd_set rfds, wfds;
f2b_conn_t *conn = NULL;
int retval, nfds;
assert(csock != NULL);
assert(cb != NULL);
const char *
f2b_csocket_error(int retcode) {
const char *err = "no error";
switch (retcode) {
case -1 : err = strerror(errno); break;
case -2 : err = "damaged cmsg on socket: truncated"; break;
case -3 : err = "damaged cmsg on socket: no magic"; break;
case -4 : err = "damaged cmsg on socket: version mismatch"; break;
case -5 : err = "damaged cmsg on socket: unknown command type"; break;
case -6 : err = "damaged cmsg on socket: size mismatch"; break;
default : err = "unknown cmsg error"; break;
/* loop / init */
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(csock->sock, &rfds); /* watch for new connections */
/* watch for new data on established connections */
nfds = csock->sock;
for (int cnum = 0; cnum < MAXCONNS; cnum++) {
if ((conn = csock->clients[cnum]) == NULL)
continue;
FD_SET(conn->sock, &rfds);
if (conn->send.used)
FD_SET(conn->sock, &wfds);
nfds = max(csock->sock, conn->sock);
}
return err;
}
int
f2b_csocket_recv(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen) {
struct msghdr msg;
uint16_t size;
int ret;
assert(csock >= 0);
assert(cmsg != NULL);
struct iovec iov[] = {
{ .iov_len = sizeof(cmsg->magic), .iov_base = &cmsg->magic[0] },
{ .iov_len = sizeof(cmsg->version), .iov_base = &cmsg->version },
{ .iov_len = sizeof(cmsg->type), .iov_base = &cmsg->type },
{ .iov_len = sizeof(cmsg->flags), .iov_base = &cmsg->flags },
{ .iov_len = sizeof(cmsg->size), .iov_base = &size /* need ntohs */ },
{ .iov_len = sizeof(cmsg->pass), .iov_base = &cmsg->pass[0] },
{ .iov_len = sizeof(cmsg->data), .iov_base = &cmsg->data[0] },
};
memset(&msg, 0x0, sizeof(msg));
msg.msg_name = addr;
msg.msg_namelen = *addrlen;
msg.msg_iov = iov;
msg.msg_iovlen = 7;
ret = recvmsg(csock, &msg, 0);
if (ret < 0 && errno == EAGAIN)
return 0; /* non-blocking mode & no messages */
if (ret < 0)
return -1; /* recvmsg() error, see errno */
if (msg.msg_flags & MSG_TRUNC)
return -2; /* truncated */
if (memcmp(cmsg->magic, "F2B", 3) != 0)
return -3; /* no magic */
if (cmsg->version != F2B_PROTO_VER)
return -4; /* version mismatch */
if (cmsg->type >= CMD_MAX_NUMBER)
return -5; /* unknown command */
cmsg->size = ntohs(size);
if (ret != (cmsg->size + 16))
return -6; /* size mismatch */
*addrlen = msg.msg_namelen;
return ret;
}
int
f2b_csocket_send(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen) {
struct msghdr msg;
uint16_t size;
int ret;
assert(csock >= 0);
assert(cmsg != NULL);
struct iovec iov[] = {
{ .iov_len = sizeof(cmsg->magic), .iov_base = &cmsg->magic[0] },
{ .iov_len = sizeof(cmsg->version), .iov_base = &cmsg->version },
{ .iov_len = sizeof(cmsg->type), .iov_base = &cmsg->type },
{ .iov_len = sizeof(cmsg->flags), .iov_base = &cmsg->flags },
{ .iov_len = sizeof(cmsg->size), .iov_base = &size /* need htons */ },
{ .iov_len = sizeof(cmsg->pass), .iov_base = &cmsg->pass[0] },
{ .iov_len = cmsg->size, .iov_base = &cmsg->data[0] },
};
size = htons(cmsg->size);
memset(&msg, 0x0, sizeof(msg));
msg.msg_name = addr;
msg.msg_namelen = *addrlen;
msg.msg_iov = iov;
msg.msg_iovlen = 7;
if ((ret = sendmsg(csock, &msg, 0)) <= 0)
return -1; /* see errno */
return ret;
}
int
f2b_csocket_poll(int csock, void (*cb)(const f2b_cmsg_t *cmsg, char *res, size_t ressize)) {
char res[DATA_LEN_MAX + 1];
f2b_cmsg_t cmsg;
struct sockaddr_storage addr;
socklen_t addrlen;
int ret, processed = 0;
assert(csock >= 0);
assert(cb != NULL);
while (1) {
memset(&cmsg, 0x0, sizeof(cmsg));
memset(&addr, 0x0, sizeof(addr));
addrlen = sizeof(addr);
ret = f2b_csocket_recv(csock, &cmsg, &addr, &addrlen);
if (ret == 0)
break; /* no messages */
if (ret < 0) {
f2b_log_msg(log_error, "%s", f2b_csocket_error(ret));
/* check for new data */
retval = select(nfds + 1, &rfds, &wfds, NULL, &tv);
if (retval < 0) {
if (errno == EINTR) {
/* interrupted by signal */
} else if (errno == EAGAIN) {
/* no data */
} else {
f2b_log_msg(log_error, "select() error: %s", strerror(errno));
}
/* TODO: check auth */
cb(&cmsg, res, sizeof(res));
if (cmsg.flags & CMSG_FLAG_NEED_REPLY) {
memset(&cmsg, 0x0, sizeof(cmsg));
strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic));
strlcpy(cmsg.data, res, sizeof(cmsg.data));
cmsg.version = F2B_PROTO_VER;
cmsg.type = CMD_RESP;
cmsg.size = strlen(res);
cmsg.data[cmsg.size] = '\0';
ret = f2b_csocket_send(csock, &cmsg, &addr, &addrlen);
return;
}
if (retval == 0)
return; /* no new data */
/* new connection on listening socket? */
if (FD_ISSET(csock->sock, &rfds)) {
/* find free connection slot */
int cnum = 0;
for (int cnum = 0; cnum < MAXCONNS; cnum++) {
if (csock->clients[cnum] == NULL) break;
}
int sock = -1;
/* accept() new connection */
if ((sock = accept(csock->sock, NULL, NULL)) < 0) {
f2b_log_msg(log_error, "can't accept() new connection: %s", strerror(errno));
} else if (cnum < MAXCONNS) {
peerlen = sizeof(peer);
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peer, &peerlen) < 0)
f2b_log_msg(log_error, "can't get remote peer credentials: %s", strerror(errno));
if ((conn = f2b_conn_create(RBUF_SIZE, WBUF_SIZE)) != NULL) {
f2b_log_msg(log_debug, "new connection accept()ed, socket %d from uid %d", sock, peer.uid);
conn->sock = sock;
csock->clients[cnum] = conn;
} else {
f2b_log_msg(log_error, "can't create new connection");
}
} else {
f2b_log_msg(log_error, "max number of clients reached, drop connection on socket %d", sock);
shutdown(sock, SHUT_RDWR);
}
processed++;
}
return processed;
for (int cnum = 0; cnum < MAXCONNS; cnum++) {
if ((conn = csock->clients[cnum]) == NULL)
continue;
retval = f2b_conn_process(conn, FD_ISSET(conn->sock, &rfds), cb);
if (retval < 0) {
f2b_log_msg(log_debug, "closing connection on socket %d", conn->sock);
shutdown(conn->sock, SHUT_RDWR);
f2b_conn_destroy(conn);
csock->clients[cnum] = NULL;
}
} /* foreach connection(s) */
return;
}

61
src/csocket.h

@ -7,56 +7,27 @@
#ifndef F2B_CSOCKET_H_
#define F2B_CSOCKET_H_
#define MAXCONNS 5
typedef struct f2b_csock_t f2b_csock_t;
/**
* @file
* This file contains control socket manage routines
*/
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/un.h>
/**
* @brief Create UNIX socket with given path
* @param path Path to socket endpoint
* @returns Socket fd
*/
int f2b_csocket_create (const char *path);
f2b_csock_t * f2b_csocket_create (const char *path);
/**
* @brief Close UNIX socket and unlink endpoint
* @param csock Socket fd
* @param path Path to socket endpoint
*/
void f2b_csocket_destroy(int csock, const char *path);
/**
* @brief Connect to given socket
* @param spath path to control socket endpoint
* @param cpath Path to client socket's endpoint
* @returns Connected fd or -1 on error
*/
int f2b_csocket_connect(const char *spath, const char *cpath);
/**
* @brief Close client connection and unlink client's endpoint
* @param csock Socket fd
* @param cpath Path to client socket's endpoint
*/
void f2b_csocket_disconnect(int csock, const char *cpath);
/**
* @brief Set recieve rimeout on socket
* @param csock Socket fd
* @param timeout Timeout in seconds
*/
void f2b_csocket_rtimeout(int csock, float timeout);
/**
* @brief Get error description for f2b_csocket_recv()
* @param retcode Return code fromf2b_csocket_recv()
* @returns Pointer to errro description
*/
const char *
f2b_csocket_error(int retcode);
void f2b_csocket_destroy(f2b_csock_t *csock);
/**
* @brief Poll control socket for new messages
@ -64,24 +35,6 @@ f2b_csocket_error(int retcode);
* @param cb Callback for handling message
* @returns -1 on error, 0 on no messages, and > 0 on some messages processed
*/
int f2b_csocket_poll(int csock, void (*cb)(const f2b_cmsg_t *cmsg, char *res, size_t ressize));
/**
* @brief Pack and send control message
* @param csock Opened socket fd
* @param cmsg Control message pointer
* @param addr Pointer for destination address store
* @param addrlen Size of address storage
* @returns >0 on success
*/
int f2b_csocket_send(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen);
/**
* @brief Recieve and unpack control message
* @param csock Opened socket fd
* @param cmsg Control message pointer
* @param addr Pointer for sender address store
* @param addrlen Size of address storage
* @returns >0 on success, 0 on no avalilable messages, <0 on error
*/
int f2b_csocket_recv(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen);
void f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res));
#endif /* F2B_CSOCKET_H_ */

135
src/daemon.c

@ -10,13 +10,25 @@
#include "jail.h"
#include "backend.h"
#include "appconfig.h"
#include "buf.h"
#include "commands.h"
#include "cmsg.h"
#include "csocket.h"
#include <getopt.h>
#include <signal.h>
/**
* @def SA_REGISTER
* Register signal handler
*/
#define SA_REGISTER(SIGNUM, HANDLER) \
memset(&act, 0x0, sizeof(act)); \
act.sa_handler = HANDLER; \
if (sigaction(SIGNUM, &act, NULL) != 0) { \
f2b_log_msg(log_fatal, "can't register handler for " #SIGNUM); \
return EXIT_FAILURE; \
}
enum { stop = 0, run, reconfig, logrotate, test } state = run;
void signal_handler(int signum) {
@ -44,89 +56,95 @@ void usage(int exitcode) {
exit(exitcode);
}
static f2b_csock_t *csock = NULL;
#ifndef WITH_CSOCKET
/* add stubs to reduce #ifdef count */
int f2b_csocket_create (const char *path) {
f2b_csock_t *
f2b_csocket_create (const char *path) {
UNUSED(path);
f2b_log_msg(log_warn, "control socket support was disabled at compile-time");
return -1;
return NULL;
}
void f2b_csocket_destroy(int csock, const char *path) {
UNUSED(csock); UNUSED(path); return;
void
f2b_csocket_destroy(f2b_csock_t *csock) {
UNUSED(csock); return;
}
int f2b_csocket_poll(int csock, void (*cb)(const f2b_cmsg_t *msg, char *res, size_t ressize)) {
int f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) {
UNUSED(csock); UNUSED(cb); return 0;
}
void
f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) {
UNUSED(msg); UNUSED(res); UNUSED(ressize); return;
f2b_csocket_cmd_process(const f2b_cmd_t *cmd, f2b_buf_t *res) {
UNUSED(cmd); UNUSED(res); return;
}
#else /* WITH_CSOCKET */
void
f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) {
const char *args[DATA_ARGS_MAX];
f2b_csocket_cmd_process(const f2b_cmd_t *cmd, f2b_buf_t *res) {
f2b_jail_t *jail = NULL;
char line[LINE_MAX];
char buf[4096] = "";
size_t len;
assert(msg != NULL);
assert(cmd != NULL);
assert(res != NULL);
assert(msg->type < CMD_MAX_NUMBER);
if (msg->type == CMD_NONE)
return;
memset(args, 0x0, sizeof(args));
int argc = f2b_cmsg_extract_args(msg, args);
if (f2b_cmd_check_argc(msg->type, argc) == false) {
strlcpy(res, "cmd args number mismatch", ressize);
if (cmd->type == CMD_UNKNOWN)
return;
}
if (msg->type >= CMD_JAIL_STATUS && msg->type <= CMD_MAX_NUMBER) {
if ((jail = f2b_jail_find(jails, args[0])) == NULL) {
snprintf(res, ressize, "can't find jail '%s'", args[0]);
if (cmd->type >= CMD_JAIL_STATUS && cmd->type <= CMD_JAIL_FILTER_RELOAD) {
if ((jail = f2b_jail_find(jails, cmd->args[1])) == NULL) {
len = snprintf(buf, sizeof(buf), "can't find jail '%s'\n", cmd->args[1]);
f2b_buf_append(res, buf, len);
return;
}
}
strlcpy(res, "ok", ressize); /* default reply */
if (msg->type == CMD_PING) {
/* nothing to do */
} else if (msg->type == CMD_RELOAD) {
if (cmd->type == CMD_RELOAD) {
state = reconfig;
} else if (msg->type == CMD_LOG_ROTATE) {
} else if (cmd->type == CMD_LOG_ROTATE) {
state = logrotate;
} else if (msg->type == CMD_LOG_LEVEL) {
f2b_log_set_level(args[0]);
} else if (msg->type == CMD_SHUTDOWN) {
} else if (cmd->type == CMD_LOG_LEVEL) {
f2b_log_set_level(cmd->args[2]);
} else if (cmd->type == CMD_SHUTDOWN) {
state = stop;
} else if (msg->type == CMD_STATUS) {
snprintf(line, sizeof(line), "pid: %u\npidfile: %s\ncsocket: %s\nstatedir: %s\njails:\n",
getpid(), appconfig.pidfile_path, appconfig.csocket_path, appconfig.statedir_path);
strlcpy(res, line, ressize);
} else if (cmd->type == CMD_STATUS) {
len = snprintf(buf, sizeof(buf), "pid: %u\npidfile: %s\n", getpid(), appconfig.pidfile_path);
f2b_buf_append(res, buf, len);
len = snprintf(buf, sizeof(buf), "csocket: %s\n", appconfig.csocket_path);
f2b_buf_append(res, buf, len);
len = snprintf(buf, sizeof(buf), "statedir: %s\n", appconfig.statedir_path);
f2b_buf_append(res, buf, len);
f2b_buf_append(res, "jails:\n", 0);
for (jail = jails; jail != NULL; jail = jail->next) {
snprintf(line, sizeof(line), "- %s\n", jail->name);
strlcat(res, line, ressize);
len = snprintf(buf, sizeof(buf), "- %s\n", jail->name);
f2b_buf_append(res, buf, len);
}
} else if (msg->type == CMD_JAIL_STATUS) {
f2b_jail_cmd_status(res, ressize, jail);
} else if (msg->type == CMD_JAIL_SET) {
f2b_jail_cmd_set(res, ressize, jail, args[1], args[2]);
} else if (msg->type == CMD_JAIL_IP_STATUS) {
f2b_jail_cmd_ip_xxx(res, ressize, jail, 0, args[1]);
} else if (msg->type == CMD_JAIL_IP_BAN) {
f2b_jail_cmd_ip_xxx(res, ressize, jail, 1, args[1]);
} else if (msg->type == CMD_JAIL_IP_RELEASE) {
f2b_jail_cmd_ip_xxx(res, ressize, jail, -1, args[1]);
} else if (msg->type == CMD_JAIL_FILTER_STATS) {
f2b_filter_cmd_stats(res, ressize, jail->filter);
} else if (msg->type == CMD_JAIL_FILTER_RELOAD) {
f2b_filter_cmd_reload(res, ressize, jail->filter);
} else if (cmd->type == CMD_JAIL_STATUS) {
f2b_jail_cmd_status(buf, sizeof(buf), jail);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_SET) {
f2b_jail_cmd_set(buf, sizeof(buf), jail, cmd->args[3], cmd->args[4]);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_IP_STATUS) {
f2b_jail_cmd_ip_xxx(buf, sizeof(buf), jail, 0, cmd->args[4]);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_IP_BAN) {
f2b_jail_cmd_ip_xxx(buf, sizeof(buf), jail, 1, cmd->args[4]);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_IP_RELEASE) {
f2b_jail_cmd_ip_xxx(buf, sizeof(buf), jail, -1, cmd->args[4]);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_FILTER_STATS) {
f2b_filter_cmd_stats(buf, sizeof(buf), jail->filter);
f2b_buf_append(res, buf, 0);
} else if (cmd->type == CMD_JAIL_FILTER_RELOAD) {
f2b_filter_cmd_reload(buf, sizeof(buf), jail->filter);
f2b_buf_append(res, buf, 0);
} else {
strlcpy(res, "error: unsupported command type", ressize);
f2b_buf_append(res, "error: unknown command\n", 0);
}
if (res->used == 0)
f2b_buf_append(res, "ok\n", 3); /* default reply if not set above */
return;
}
#endif /* WITH_CSOCKET */
@ -277,8 +295,9 @@ int main(int argc, char *argv[]) {
}
}
if (appconfig.csocket_path[0] != '\0')
appconfig.csock = f2b_csocket_create(appconfig.csocket_path);
if (appconfig.csocket_path[0] != '\0') {
csock = f2b_csocket_create(appconfig.csocket_path);
}
if (config.defaults)
f2b_jail_set_defaults(config.defaults);
@ -295,7 +314,7 @@ int main(int argc, char *argv[]) {
for (f2b_jail_t *jail = jails; jail != NULL; jail = jail->next) {
f2b_jail_process(jail);
}
f2b_csocket_poll(appconfig.csock, f2b_cmsg_process);
f2b_csocket_poll(csock, f2b_csocket_cmd_process);
sleep(1);
if (state == logrotate && strcmp(appconfig.logdest, "file") == 0) {
state = run;
@ -317,7 +336,7 @@ int main(int argc, char *argv[]) {
}
}
f2b_csocket_destroy(appconfig.csock, appconfig.csocket_path);
f2b_csocket_destroy(csock);
jails_stop(jails);
jails = NULL;

2
src/filter.h

@ -77,7 +77,7 @@ bool f2b_filter_append(f2b_filter_t *f, const char *pattern);
*/
bool f2b_filter_match (f2b_filter_t *f, const char *line, char *buf, size_t bufsize);
/* handlers for cmsg processing */
/* handlers for csocket commands processing */
/** handler of 'jail $JAIL filter reload' cmd */
void f2b_filter_cmd_reload(char *buf, size_t bufsize, f2b_filter_t *f);
/** handler of 'jail $JAIL filter stats' cmd */

2
src/jail.h

@ -110,7 +110,7 @@ size_t f2b_jail_process (f2b_jail_t *jail);
*/
bool f2b_jail_stop (f2b_jail_t *jail);
/* handlers for cmsg */
/* handlers for csocket commands processing */
/**
* @brief Get jail status

6
src/sources/CMakeLists.txt

@ -8,12 +8,6 @@ list(APPEND SOURCES "files")
add_library("s_portknock" MODULE "portknock.c" "../strlcpy.c")
list(APPEND SOURCES "portknock")
if (WITH_MCAST)
add_library("s_mcast" MODULE "mcast.c" "../strlcpy.c"
"../commands.c" "../cmsg.c" "../csocket.c")
list(APPEND SOURCES "mcast")
endif ()
find_library(REDIS_FOUND "hiredis")
if (WITH_REDIS AND REDIS_FOUND)
add_library("s_redis" MODULE "redis.c" "../strlcpy.c")

271
src/sources/mcast.c

@ -1,271 +0,0 @@
/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "source.h"
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netdb.h>
#include "../commands.h"
#include "../cmsg.h"
#include "../csocket.h"
#define DEFAULT_BIND_ADDR "0.0.0.0"
#define DEFAULT_MCAST_ADDR "239.255.186.1"
#define DEFAULT_MCAST_PORT "3370"
#if defined(IPV6_JOIN_GROUP) && !defined(IPV6_ADD_MEMBERSHIP)
/* bsd-derivatives */
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
struct _config {
char name[32];
char error[256];
void (*errcb)(const char *errstr);
char baddr[INET6_ADDRSTRLEN]; /**< bind address */
char maddr[INET6_ADDRSTRLEN]; /**< multicast address */
char mport[6]; /**< multicast port */
char iface[IF_NAMESIZE]; /**< bind interface */
int sock;
};
static void
errcb_stub(const char *str) {
assert(str != NULL);
(void)(str);
}
cfg_t *
create(const char *init) {
cfg_t *cfg = NULL;
assert(init != NULL);
if ((cfg = calloc(1, sizeof(cfg_t))) == NULL)
return NULL;
strlcpy(cfg->name, init, sizeof(cfg->name));
strlcpy(cfg->baddr, DEFAULT_BIND_ADDR, sizeof(cfg->baddr));
strlcpy(cfg->maddr, DEFAULT_MCAST_ADDR, sizeof(cfg->maddr));
strlcpy(cfg->mport, DEFAULT_MCAST_PORT, sizeof(cfg->mport));
cfg->errcb = &errcb_stub;
return cfg;
}
bool
config(cfg_t *cfg, const char *key, const char *value) {
assert(cfg != NULL);
assert(key != NULL);
assert(value != NULL);
if (strcmp(key, "group") == 0) {
if (strncmp(value, "239.255.", 8) != 0) {
strlcpy(cfg->error, "mcast group address should be inside 239.255.0.0/16 block", sizeof(cfg->error));
return false;
}
strlcpy(cfg->maddr, value, sizeof(cfg->maddr));
return true;
}
if (strcmp(key, "address") == 0) {
strlcpy(cfg->baddr, value, sizeof(cfg->baddr));
return true;
}
if (strcmp(key, "port") == 0) {
strlcpy(cfg->mport, value, sizeof(cfg->mport));
return true;
}
if (strcmp(key, "iface") == 0) {
strlcpy(cfg->iface, value, sizeof(cfg->iface));
return true;
}
return false;
}
bool
ready(cfg_t *cfg) {
assert(cfg != NULL);
if (cfg->maddr[0] && (cfg->iface[0] || cfg->baddr[0]))
return true;
return false;
}
char *
error(cfg_t *cfg) {
assert(cfg != NULL);
return cfg->error;
}
void
errcb(cfg_t *cfg, void (*cb)(const char *errstr)) {
assert(cfg != NULL);
assert(cb != NULL);
cfg->errcb = cb;
}
bool
start(cfg_t *cfg) {
struct addrinfo hints;
struct addrinfo *maddr, *laddr;
int opt, ret, sock = -1;
assert(cfg != NULL);
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_NUMERICHOST;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = 0;
if ((ret = getaddrinfo(cfg->maddr, cfg->mport, &hints, &maddr)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't resolve mcast addr: %s", gai_strerror(ret));
return false;
}
hints.ai_family = maddr->ai_family;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_DGRAM;
if ((ret = getaddrinfo(cfg->baddr[0] != '\0' ? cfg->baddr : NULL, cfg->mport, &hints, &laddr)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't resolve local addr: %s", gai_strerror(ret));
freeaddrinfo(maddr);
return false;
}
do {
cfg->sock = -1;
/* create socket */
if ((sock = socket(laddr->ai_family, laddr->ai_socktype, laddr->ai_protocol)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't create socket: %s", strerror(errno));
continue;
}
/* set non-blocking mode */
if ((opt = fcntl(sock, F_GETFL, 0)) < 0) {
continue;
}
fcntl(sock, F_SETFL, opt | O_NONBLOCK);
/* reuse address */
opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
/* bind to given address */
if (bind(sock, laddr->ai_addr, laddr->ai_addrlen) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't bind socket to addr %s: %s",
cfg->baddr, strerror(errno));
continue;
}
/* IP_MULTICAST_LOOP -- default: yes */
/* IP_MULTICAST_TTL -- default: 1 */
/* join mcast group */
if (maddr->ai_family == AF_INET) {
struct ip_mreq mreq;
struct in_addr *addr = NULL;
memset(&mreq, 0x0, sizeof(mreq));
addr = &((struct sockaddr_in *)(maddr->ai_addr))->sin_addr;
memcpy(&mreq.imr_multiaddr, addr, sizeof(mreq.imr_multiaddr));
addr = &((struct sockaddr_in *)(laddr->ai_addr))->sin_addr;
memcpy(&mreq.imr_interface, addr, sizeof(mreq.imr_interface));
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't join mcast group: %s",
strerror(errno));
continue;
}
} else if (maddr->ai_family == AF_INET6) {
struct ipv6_mreq mreq;
struct in6_addr *addr = NULL;
memset(&mreq, 0x0, sizeof(mreq));
addr = &((struct sockaddr_in6 *)(maddr->ai_addr))->sin6_addr;
memcpy(&mreq.ipv6mr_multiaddr, addr, sizeof(mreq.ipv6mr_multiaddr));
if (cfg->iface[0] != '\0') {
mreq.ipv6mr_interface = if_nametoindex(cfg->iface);
}
if (setsockopt(sock, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {
snprintf(cfg->error, sizeof(cfg->error), "can't join mcast group: %s",
strerror(errno));
continue;
}
} else {
snprintf(cfg->error, sizeof(cfg->error), "unknown family for mcast addr: %d", laddr->ai_family);
continue;
}
/* all ok */
cfg->sock = sock;
sock = -1;
} while (0);
freeaddrinfo(maddr);
freeaddrinfo(laddr);
if (cfg->sock < 0)
return false;
return true;
}
bool
stop(cfg_t *cfg) {
assert(cfg != NULL);
/* mcast group will be leaved automatically */
close(cfg->sock);
cfg->sock = -1;
return true;
}
bool
next(cfg_t *cfg, char *buf, size_t bufsize, bool reset) {
const char *args[DATA_ARGS_MAX];
struct sockaddr_storage addr;
socklen_t socklen;
f2b_cmsg_t cmsg;
int ret;
(void)(reset);
assert(cfg != NULL);
assert(buf != NULL);
assert(bufsize > 0);
memset(&cmsg, 0x0, sizeof(cmsg));
memset(&addr, 0x0, sizeof(addr));
while (1) {
ret = f2b_csocket_recv(cfg->sock, &cmsg, &addr, &socklen);
if (ret == 0)
break; /* no messages */
if (ret < 0) {
cfg->errcb(f2b_csocket_error(ret));
continue;
}
/* ret > 0 */
if (cmsg.type != CMD_JAIL_IP_BAN)
continue;
ret = f2b_cmsg_extract_args(&cmsg, args);
if (!f2b_cmd_check_argc(cmsg.type, ret)) {
cfg->errcb("received cmsg with wrong args count");
continue;
}
if (strcmp(cfg->name, args[0]) != 0)
continue; /* wrong group */
strlcpy(buf, args[1], bufsize);
return true;
}
return false;
}
void
destroy(cfg_t *cfg) {
assert(cfg != NULL);
free(cfg);
}

6
t/CMakeLists.txt

@ -2,16 +2,16 @@ enable_testing()
set(SRC_DIR "../src")
add_executable("t_cmd" "t_cmd.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/commands.c")
add_executable("t_cmsg" "t_cmsg.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/cmsg.c")
add_executable("t_buf" "t_buf.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/buf.c")
add_executable("t_cmd" "t_cmd.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/buf.c" "${SRC_DIR}/commands.c")
add_executable("t_matches" "t_matches.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c")
add_executable("t_ipaddr" "t_ipaddr.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c" "${SRC_DIR}/ipaddr.c")
add_executable("t_statefile" "t_statefile.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/matches.c" "${SRC_DIR}/ipaddr.c" "${SRC_DIR}/statefile.c" "${SRC_DIR}/log.c")
add_executable("t_config_param" "t_config_param.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/config.c" "${SRC_DIR}/log.c")
add_executable("t_backend_usage" "t_backend_usage.c" "${SRC_DIR}/strlcpy.c")
add_test("tests/f2b_buf_*" "t_buf")
add_test("tests/f2b_cmd_*" "t_cmd")
add_test("tests/f2b_cmsg_*" "t_cmsg")
add_test("tests/f2b_matches_*" "t_matches")
add_test("tests/f2b_ipaddr_*" "t_ipaddr")
add_test("tests/f2b_statefile_*" "t_statefile")

68
t/t_buf.c

@ -0,0 +1,68 @@
#include "../src/common.h"
#include "../src/buf.h"
int main() {
f2b_buf_t buf;
char *line;
bool ret;
int len;
memset(&buf, 0x0, sizeof(buf));
ret = f2b_buf_alloc(&buf, 512);
assert(ret = true);
assert(buf.used == 0);
assert(buf.size == 512);
ret = f2b_buf_append(&buf, "test ", 0);
assert(ret == true);
assert(buf.used == 5);
line = f2b_buf_extract(&buf, "\n");
assert(line == NULL);
ret = f2b_buf_append(&buf, "test2\n", 0);
assert(ret == true);
assert(buf.used == 11);
ret = f2b_buf_append(&buf, "test3\n", 0);
assert(ret == true);
assert(buf.used == 17);
line = f2b_buf_extract(&buf, "\n");
assert(line != NULL);
assert(strcmp(line, "test test2") == 0);
assert(buf.used == 6);
free(line);
line = f2b_buf_extract(&buf, "\n");
assert(line != NULL);
assert(buf.used == 0);
free(line);
f2b_buf_append(&buf, "test4\n\n", 6);
assert(buf.used == 6);
assert(strcmp(buf.data, "test4\n") == 0);
len = f2b_buf_splice(&buf, 0);
assert(len == 0);
assert(buf.used == 6);
assert(strcmp(buf.data, "test4\n") == 0);
len = f2b_buf_splice(&buf, 2);
assert(len == 2);
assert(buf.used == 4);
assert(strcmp(buf.data, "st4\n") == 0);
len = f2b_buf_splice(&buf, 6);
assert(len == 4);
assert(buf.used == 0);
assert(buf.data[0] == '\0');
f2b_buf_free(&buf);
assert(buf.used == 0);
assert(buf.size == 0);
assert(buf.data == NULL);
return 0;
}

61
t/t_cmd.c

@ -1,34 +1,41 @@
#include "../src/common.h"
#include "../src/buf.h"
#include "../src/commands.h"
int main() {
char buf[1024];
const char *line;
UNUSED(line);
buf[0] = '\0';
f2b_cmd_append_arg(buf, sizeof(buf), "42");
assert(strcmp(buf, "42\n") == 0);
line = "status";
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_STATUS);
line = "statu"; /* no such command */
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_NONE);
line = "jail test"; /* incomplete command */
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_NONE);
buf[0] = '\0';
line = "jail test status";
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_JAIL_STATUS);
assert(strcmp(buf, "test\n") == 0);
buf[0] = '\0';
line = "jail test set bantime 7200";
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_JAIL_SET);
assert(strcmp(buf, "test\nbantime\n7200\n") == 0);
f2b_cmd_t cmd;
assert(f2b_cmd_parse(&cmd, "status") == true);
assert(cmd.type = CMD_STATUS);
assert(cmd.argc == 1);
assert(strcmp(cmd.args[0], "status") == 0);
assert(cmd.data.used == 6); /* "status" */
f2b_buf_free(&cmd.data);
assert(f2b_cmd_parse(&cmd, "stat") == false); /* no such command */
assert(cmd.type == CMD_UNKNOWN);
assert(f2b_cmd_parse(&cmd, "jail test") == false); /* incomplete command */
assert(cmd.type == CMD_UNKNOWN);
assert(f2b_cmd_parse(&cmd, "jail test status") == true);
assert(cmd.type == CMD_JAIL_STATUS);
assert(cmd.argc == 3);
assert(strcmp(cmd.args[0], "jail") == 0);
assert(strcmp(cmd.args[1], "test") == 0);
assert(strcmp(cmd.args[2], "status") == 0);
assert(cmd.data.used == 16); /* "jail\0test\0status" */
f2b_buf_free(&cmd.data);
assert(f2b_cmd_parse(&cmd, "jail test set bantime 7200") == true);
assert(cmd.type == CMD_JAIL_SET);
assert(cmd.argc == 5);
assert(strcmp(cmd.args[0], "jail") == 0);
assert(strcmp(cmd.args[1], "test") == 0);
assert(strcmp(cmd.args[2], "set") == 0);
assert(strcmp(cmd.args[3], "bantime") == 0);
assert(strcmp(cmd.args[4], "7200") == 0);
f2b_buf_free(&cmd.data);
return EXIT_SUCCESS;
}

32
t/t_cmsg.c

@ -1,32 +0,0 @@
#include "../src/common.h"
#include "../src/cmsg.h"
int main() {
const char *argv[DATA_ARGS_MAX];
f2b_cmsg_t msg;
memset(&msg, 0x0, sizeof(msg));
memset(argv, 0x0, sizeof(argv));
snprintf(msg.data, sizeof(msg.data), "test1\ntest2\n");
msg.size = strlen(msg.data);
f2b_cmsg_convert_args(&msg);
assert(memcmp(msg.data, "test1\0test2\0", 12) == 0);
f2b_cmsg_extract_args(&msg, argv);
assert(argv[0] == msg.data + 0);
assert(argv[1] == msg.data + 6);
assert(memcmp(argv[0], "test1\0", 6) == 0);
assert(memcmp(argv[1], "test2\0", 6) == 0);
/* data not null-terminated */
msg.size = 10;
memcpy(msg.data, "test1\0test2\n", 10);
assert(f2b_cmsg_extract_args(&msg, argv) == -1);
msg.size = 0;
assert(f2b_cmsg_extract_args(&msg, argv) == 0);
return EXIT_SUCCESS;
}
Loading…
Cancel
Save