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_READLINE "Use readline library for client" ON)
option(WITH_PCRE "Build pcre-compatible filter" ON) option(WITH_PCRE "Build pcre-compatible filter" ON)
option(WITH_REDIS "Build redis source/backend" OFF) option(WITH_REDIS "Build redis source/backend" OFF)
option(WITH_MCAST "Build mcast source/backend" OFF)
option(WITH_IPSET "Build native ipset backend" OFF) option(WITH_IPSET "Build native ipset backend" OFF)
if (NOT DEFINED CMAKE_INSTALL_PREFIX) 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_HARDENING : ${WITH_HARDENING}")
message(STATUS "- WITH_PCRE : ${WITH_PCRE}") message(STATUS "- WITH_PCRE : ${WITH_PCRE}")
message(STATUS "- WITH_REDIS : ${WITH_REDIS}") message(STATUS "- WITH_REDIS : ${WITH_REDIS}")
message(STATUS "- WITH_MCAST : ${WITH_MCAST}")
message(STATUS "- WITH_IPSET : ${WITH_IPSET}") message(STATUS "- WITH_IPSET : ${WITH_IPSET}")
message(STATUS "- WITH_READLINE : ${WITH_READLINE}") message(STATUS "- WITH_READLINE : ${WITH_READLINE}")
message(STATUS "Components:") message(STATUS "Components:")

9
src/CMakeLists.txt

@ -1,10 +1,10 @@
set(CMAKE_INCLUDE_CURRENT_DIR ON) 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") "appconfig.c" "statefile.c" "source.c" "filter.c" "backend.c" "jail.c")
if (WITH_CSOCKET) if (WITH_CSOCKET)
list(APPEND SOURCES "commands.c" "csocket.c" "cmsg.c") list(APPEND SOURCES "commands.c" "csocket.c")
add_definitions("-DWITH_CSOCKET") add_definitions("-DWITH_CSOCKET")
endif () endif ()
@ -12,7 +12,7 @@ add_executable("f2b" ${SOURCES})
install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) install(TARGETS "f2b" RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
if (WITH_CLIENT) 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}) add_executable("f2bc" ${SOURCES})
if (WITH_READLINE) if (WITH_READLINE)
add_definitions("-DWITH_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") set(SOURCES "strlcpy.c" "backend-test.c" "log.c" "config.c" "backend.c")
add_executable("f2b-backend-test" ${SOURCES}) 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") if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_link_libraries("f2b" "dl") target_link_libraries("f2b" "dl")
target_link_libraries("f2b-source-test" "dl") target_link_libraries("f2b-source-test" "dl")

1
src/appconfig.c

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

1
src/appconfig.h

@ -14,7 +14,6 @@ typedef struct f2b_appconfig_t {
bool daemon; bool daemon;
uid_t uid; uid_t uid;
gid_t gid; gid_t gid;
int csock;
char logdest[CONFIG_KEY_MAX]; char logdest[CONFIG_KEY_MAX];
char config_path[PATH_MAX]; char config_path[PATH_MAX];
char logfile_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") add_library("b_exec" MODULE "exec.c" "../strlcpy.c")
list(APPEND BACKENDS "exec") 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") find_library(REDIS_FOUND "hiredis")
if (WITH_REDIS AND REDIS_FOUND) if (WITH_REDIS AND REDIS_FOUND)
add_library("b_redis" MODULE "redis.c" "../strlcpy.c") 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 * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include "common.h" #include "common.h"
#include "cmsg.h"
#include "commands.h"
#include "csocket.h"
#include "client.h" #include "client.h"
#include "log.h"
#include <signal.h>
struct { struct {
enum { interactive = 0, oneshot } mode; enum { interactive = 0, oneshot } mode;
int csocket; char spath[PATH_MAX];
float timeout;
char csocket_spath[PATH_MAX];
char csocket_cpath[PATH_MAX];
} opts = { } opts = {
interactive, -1, 5.0, interactive,
DEFAULT_CSOCKET_PATH, DEFAULT_CSOCKET_PATH,
DEFAULT_CSOCKET_CPATH, /* template */
}; };
static int csock = -1;
void usage(int exitcode) { void usage(int exitcode) {
fputs("Usage: f2bc [-h] [-s <path>] [-c <command>]\n", stdout); fputs("Usage: f2bc [-h] [-s <path>] [-c <command>]\n", stdout);
fputs("\t-h Show this message\n", stdout); fputs("\t-h Show this message\n", stdout);
@ -34,60 +30,61 @@ void usage(int exitcode) {
} }
int int
handle_cmd(const char *line) { handle_recv() {
f2b_cmsg_t cmsg; char buf[WBUF_SIZE] = ""; /* our "read" is server "write" */
struct sockaddr_storage addr;
socklen_t addrlen = 0;
int ret; int ret;
memset(&addr, 0x0, sizeof(addr)); if (csock < 0)
memset(&cmsg, 0x0, sizeof(cmsg)); return 0; /* not connected */
cmsg.type = f2b_cmd_parse(&cmsg.data[0], sizeof(cmsg.data), line); ret = recv(csock, &buf, sizeof(buf), MSG_DONTWAIT);
if (cmsg.type == CMD_HELP) { if (ret > 0) {
f2b_cmd_help(); write(fileno(stdout), buf, ret);
return EXIT_SUCCESS; } else if (ret == 0) {
} else if (cmsg.type == CMD_NONE) { puts("connection closed");
printf("! unable to parse command line\n"); exit(EXIT_SUCCESS); /* received EOF */
return EXIT_FAILURE; } else if (ret < 0 && errno == EAGAIN) {
} return 0;
/* fill other fields */ } else /* ret < 0 */ {
strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic)); perror("recv()");
cmsg.version = F2B_PROTO_VER; exit(EXIT_FAILURE);
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;
} }
return 0;
}
if (cmsg.type != CMD_RESP) { int
printf("! recieved message not a 'response' type\n"); handle_cmd(const char *line) {
return EXIT_FAILURE; 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() { return 0;
f2b_csocket_disconnect(opts.csocket, opts.csocket_cpath);
unlink(opts.csocket_cpath);
} }
void void
signal_handler(int signum) { handle_signal(int signum) {
switch (signum) { switch (signum) {
case SIGINT: case SIGINT:
case SIGTERM: case SIGTERM:
cleanup();
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
default: 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 #ifdef WITH_READLINE
#include <readline/readline.h> #include <readline/readline.h>
#include <readline/history.h> #include <readline/history.h>
rl_hook_func_t *rl_event_hook = &handle_recv;
#else #else
char * char *
readline(const char *prompt) { readline(const char *prompt) {
char line[INPUT_LINE_MAX]; char line[RBUF_SIZE+1];
char *p; char *p;
while (1) { while (1) {
line[0] = '\0'; line[0] = '\0';
p = &line[0]; p = &line[0];
handle_recv();
fputs(prompt, stdout); fputs(prompt, stdout);
if (!fgets(line, sizeof(line) - 1, stdin)) { if (!fgets(line, sizeof(line) - 1, stdin)) {
if (feof(stdin)) { if (!feof(stdin))
fputc('\n', stdout);
} else {
fputs("read error\n", stdout); fputs("read error\n", stdout);
}
return NULL; return NULL;
} }
while (isspace(*p)) p++; while (isspace(*p)) p++;
@ -129,7 +154,6 @@ readline(const char *prompt) {
#endif #endif
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
struct sigaction act;
char *line = NULL; char *line = NULL;
char opt = '\0'; char opt = '\0';
int ret; int ret;
@ -138,10 +162,10 @@ int main(int argc, char *argv[]) {
switch (opt) { switch (opt) {
case 'c': case 'c':
opts.mode = oneshot; opts.mode = oneshot;
line = strndup(optarg, INPUT_LINE_MAX); line = strndup(optarg, RBUF_SIZE);
break; break;
case 's': case 's':
strlcpy(opts.csocket_spath, optarg, sizeof(opts.csocket_spath)); strlcpy(opts.spath, optarg, sizeof(opts.spath));
break; break;
case 'h': case 'h':
usage(EXIT_SUCCESS); usage(EXIT_SUCCESS);
@ -152,23 +176,14 @@ int main(int argc, char *argv[]) {
} }
} }
SA_REGISTER(SIGTERM, &signal_handler); setup_sigaction(SIGTERM);
SA_REGISTER(SIGINT, &signal_handler); setup_sigaction(SIGINT);
/* prepare client side of socket */ setup_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);
if (opts.mode == oneshot) { if (opts.mode == oneshot) {
ret = handle_cmd(line); ret = handle_cmd(line);
f2b_csocket_disconnect(opts.csocket, opts.csocket_cpath); shutdown(csock, SHUT_RDWR);
exit(ret); exit(ret);
} }
@ -183,7 +198,5 @@ int main(int argc, char *argv[]) {
} }
putc('\n', stdout); putc('\n', stdout);
cleanup();
return EXIT_SUCCESS; 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. * published by the Free Software Foundation.
*/ */
#include "common.h" #include "common.h"
#include "buf.h"
#include "commands.h" #include "commands.h"
struct f2b_cmd_t { struct cmd_desc {
enum f2b_command_type type;
const short int argc; const short int argc;
const short int tokenc; const short int tokenc;
const char *help; const char *help;
const char *tokens[CMD_TOKENS_MAX]; const char *tokens[CMD_TOKENS_MAXCOUNT];
} commands[CMD_MAX_NUMBER] = { } commands[] = {
[CMD_NONE] = { {
.argc = 0, .tokenc = 0, .type = CMD_HELP,
.tokens = { NULL },
.help = "Unspecified command"
},
[CMD_RESP] = {
.argc = 1, .tokenc = 0,
.tokens = { NULL },
.help = "Command response, used internally",
},
[CMD_HELP] = {
.argc = 0, .tokenc = 1, .argc = 0, .tokenc = 1,
.tokens = { "help", NULL }, .tokens = { "help", NULL },
.help = "Show available commands", .help = "Show available commands",
}, }, {
[CMD_PING] = { .type = CMD_STATUS,
.argc = 0, .tokenc = 1,
.tokens = { "ping", NULL },
.help = "Check the connection",
},
[CMD_STATUS] = {
.argc = 0, .tokenc = 1, .argc = 0, .tokenc = 1,
.tokens = { "status", NULL }, .tokens = { "status", NULL },
.help = "Show general stats and jails list", .help = "Show general stats and jails list",
}, }, {
[CMD_LOG_ROTATE] = { .type = CMD_RELOAD,
.argc = 0, .tokenc = 2,
.tokens = { "log", "rotate", NULL },
.help = "Reopen daemon's own log file",
},
[CMD_RELOAD] = {
.argc = 0, .tokenc = 1, .argc = 0, .tokenc = 1,
.tokens = { "reload", NULL }, .tokens = { "reload", NULL },
.help = "Reload own config, all jails will be reset.", .help = "Reload own config, all jails will be reset.",
}, }, {
[CMD_SHUTDOWN] = { .type = CMD_SHUTDOWN,
.argc = 0, .tokenc = 1, .argc = 0, .tokenc = 1,
.tokens = { "shutdown", NULL }, .tokens = { "shutdown", NULL },
.help = "Gracefully terminate f2b daemon", .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, .argc = 1, .tokenc = 3,
.tokens = { "log", "level", "<level>", NULL }, .tokens = { "log", "level", "<level>", NULL },
.help = "Change maximum level of logged messages", .help = "Change maximum level of logged messages",
}, }, {
[CMD_JAIL_STATUS] = { .type = CMD_JAIL_STATUS,
.argc = 1, .tokenc = 3, .argc = 1, .tokenc = 3,
.tokens = { "jail", "<jailname>", "status", NULL }, .tokens = { "jail", "<jailname>", "status", NULL },
.help = "Show status and stats of given jail", .help = "Show status and stats of given jail",
}, }, {
[CMD_JAIL_SET] = { .type = CMD_JAIL_SET,
.argc = 3, .tokenc = 5, .argc = 3, .tokenc = 5,
.tokens = { "jail", "<jailname>", "set", "<param>", "<value>", NULL }, .tokens = { "jail", "<jailname>", "set", "<param>", "<value>", NULL },
.help = "Set parameter of given jail", .help = "Set parameter of given jail",
}, }, {
[CMD_JAIL_IP_STATUS] = { .type = CMD_JAIL_IP_STATUS,
.argc = 2, .tokenc = 5, .argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "status", "<ip>", NULL }, .tokens = { "jail", "<jailname>", "ip", "status", "<ip>", NULL },
.help = "Show ip status in given jail", .help = "Show ip status in given jail",
}, }, {
[CMD_JAIL_IP_BAN] = { .type = CMD_JAIL_IP_BAN,
.argc = 2, .tokenc = 5, .argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "ban", "<ip>", NULL }, .tokens = { "jail", "<jailname>", "ip", "ban", "<ip>", NULL },
.help = "Forcefully ban some ip in given jail", .help = "Forcefully ban some ip in given jail",
}, }, {
[CMD_JAIL_IP_RELEASE] = { .type = CMD_JAIL_IP_RELEASE,
.argc = 2, .tokenc = 5, .argc = 2, .tokenc = 5,
.tokens = { "jail", "<jailname>", "ip", "release", "<ip>", NULL }, .tokens = { "jail", "<jailname>", "ip", "release", "<ip>", NULL },
.help = "Forcefully release some ip in given jail", .help = "Forcefully release some ip in given jail",
}, }, {
[CMD_JAIL_FILTER_STATS] = { .type = CMD_JAIL_FILTER_STATS,
.argc = 1, .tokenc = 4, .argc = 1, .tokenc = 4,
.tokens = { "jail", "<jailname>", "filter", "stats", NULL }, .tokens = { "jail", "<jailname>", "filter", "stats", NULL },
.help = "Show matches stats for jail regexps", .help = "Show matches stats for jail regexps",
}, }, {
[CMD_JAIL_FILTER_RELOAD] = { .type = CMD_JAIL_FILTER_RELOAD,
.argc = 1, .tokenc = 4, .argc = 1, .tokenc = 4,
.tokens = { "jail", "<jailname>", "filter", "reload", NULL }, .tokens = { "jail", "<jailname>", "filter", "reload", NULL },
.help = "Reload regexps for given jail", .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() { f2b_cmd_help() {
struct f2b_cmd_t *cmd = NULL; f2b_buf_t buf;
const char **p = NULL; const char **p = NULL;
fputs("Available commands:\n\n", stdout); if (help)
for (size_t i = CMD_PING; i < CMD_MAX_NUMBER; i++) { return help;
cmd = &commands[i];
if (cmd->tokens[0] == NULL) if (!f2b_buf_alloc(&buf, 8192))
continue; return "internal error: can't allocate memory\n";
for (p = cmd->tokens; *p != NULL; p++) f2b_buf_append(&buf, "Available commands:\n\n", 0);
fprintf(stdout, "%s ", *p); for (struct cmd_desc *cmd = commands; cmd->type != CMD_UNKNOWN; cmd++) {
fprintf(stdout, "\n\t%s\n\n", cmd->help); 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 void
f2b_cmd_append_arg(char *buf, size_t bufsize, const char *arg) { f2b_cmd_destroy(f2b_cmd_t *cmd) {
assert(buf != NULL); f2b_buf_free(&cmd->data);
assert(arg != NULL); free(cmd);
strlcat(buf, arg, bufsize);
strlcat(buf, "\n", bufsize);
} }
/** bool
* @brief Parse command from line f2b_cmd_parse(f2b_cmd_t *cmd, const char *src) {
* @param buf Buffer for command parameters char *p = NULL;
* @param bufsize SSize of buffer above
* @param src Line taken from user input assert(cmd != NULL);
* @return Type of parsed command or CMD_NONE if no matches assert(src != NULL);
*/
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;
assert(line != NULL);
/* strip leading spaces */ /* strip leading spaces */
while (isblank(*line)) while (isblank(*src))
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') f2b_buf_alloc(&cmd->data, strlen(src) + 1);
return CMD_NONE; /* empty string */ f2b_buf_append(&cmd->data, src, 0);
/* simple commands without args */ /* simple commands without args */
if (strcmp(line, "ping") == 0) { return CMD_PING; } cmd->argc = 1; /* we has at least one arg */
else if (strcmp(line, "help") == 0) { return CMD_HELP; } cmd->args[0] = cmd->data.data;
else if (strcmp(line, "status") == 0) { return CMD_STATUS; } if (strcmp(src, "help") == 0) { cmd->type = CMD_HELP; return true; }
else if (strcmp(line, "reload") == 0) { return CMD_RELOAD; } else if (strcmp(src, "status") == 0) { cmd->type = CMD_STATUS; return true; }
else if (strcmp(line, "shutdown") == 0) { return CMD_SHUTDOWN; } 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 */ /* split string to tokens */
tokenc = 1; /* we has at least one token */ p = cmd->data.data;
tokens[0] = strtok_r(line, " \t", &p); cmd->args[0] = strtok_r(cmd->data.data, " \t", &p);
for (size_t i = 1; i < CMD_TOKENS_MAX; i++) { for (size_t i = 1; i < CMD_TOKENS_MAXCOUNT; i++) {
tokens[i] = strtok_r(NULL, " \t", &p); cmd->args[i] = strtok_r(NULL, " \t", &p);
if (tokens[i] == NULL) if (cmd->args[i] == NULL)
break; break;
tokenc++; cmd->argc++;
} }
buf[0] = '\0'; if (strcmp(cmd->args[0], "jail") == 0 && cmd->argc > 1) {
if (strcmp(line, "jail") == 0 && tokenc > 1) {
/* commands for jail */ /* commands for jail */
f2b_cmd_append_arg(buf, bufsize, tokens[1]); if (cmd->argc == 3 && strcmp(cmd->args[2], "status") == 0) {
if (tokenc == 3 && strcmp(tokens[2], "status") == 0) { cmd->type = CMD_JAIL_STATUS; return true;
return CMD_JAIL_STATUS;
} }
if (tokenc == 5 && strcmp(tokens[2], "set") == 0) { if (cmd->argc == 5 && strcmp(cmd->args[2], "set") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[3]); cmd->type = CMD_JAIL_SET; return true;
f2b_cmd_append_arg(buf, bufsize, tokens[4]);
return CMD_JAIL_SET;
} }
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "status") == 0) { if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "status") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]); cmd->type = CMD_JAIL_IP_STATUS; return true;
return CMD_JAIL_IP_STATUS;
} }
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "ban") == 0) { if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "ban") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]); cmd->type = CMD_JAIL_IP_BAN; return true;
return CMD_JAIL_IP_BAN;
} }
if (tokenc == 5 && strcmp(tokens[2], "ip") == 0 && strcmp(tokens[3], "release") == 0) { if (cmd->argc == 5 && strcmp(cmd->args[2], "ip") == 0 && strcmp(cmd->args[3], "release") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[4]); cmd->type = CMD_JAIL_IP_RELEASE; return true;
return CMD_JAIL_IP_RELEASE;
} }
if (tokenc == 4 && strcmp(tokens[2], "filter") == 0 && strcmp(tokens[3], "stats") == 0) { if (cmd->argc == 4 && strcmp(cmd->args[2], "filter") == 0 && strcmp(cmd->args[3], "stats") == 0) {
return CMD_JAIL_FILTER_STATS; cmd->type = CMD_JAIL_FILTER_STATS; return true;
} }
if (tokenc == 4 && strcmp(tokens[2], "filter") == 0 && strcmp(tokens[3], "reload") == 0) { if (cmd->argc == 4 && strcmp(cmd->args[2], "filter") == 0 && strcmp(cmd->args[3], "reload") == 0) {
return CMD_JAIL_FILTER_RELOAD; cmd->type = CMD_JAIL_FILTER_RELOAD; return true;
} }
} else if (strcmp(line, "log") == 0 && tokenc > 1) { } else if (strcmp(cmd->args[0], "log") == 0 && cmd->argc > 1) {
if (tokenc == 2 && strcmp(tokens[1], "rotate") == 0) { if (cmd->argc == 2 && strcmp(cmd->args[1], "rotate") == 0) {
return CMD_LOG_ROTATE; cmd->type = CMD_LOG_ROTATE; return true;
} }
if (tokenc == 3 && strcmp(tokens[1], "level") == 0) { if (cmd->argc == 3 && strcmp(cmd->args[1], "level") == 0) {
f2b_cmd_append_arg(buf, bufsize, tokens[2]); cmd->type = CMD_LOG_LEVEL; return true;
return CMD_LOG_LEVEL;
} }
} }
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; return false;
} }

77
src/commands.h

@ -10,30 +10,23 @@
/** /**
* @file * @file
* This header contains definition of control commands and routines * This header contains definition of control commands and routines
* for work with data buffer of control message * for parsing user input
*/ */
/** #define CMD_TOKENS_MAXCOUNT 6 /**< Maximum count of data pieces in control message data buf */
* Maximum length of input line in client #define CMD_TOKENS_MAXSIZE 260 /**< parsed tokens */
* @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
/** control command type */ enum f2b_command_type {
enum f2b_cmd_type { CMD_UNKNOWN = 0, /**< unset */
CMD_NONE = 0, /**< unset */ CMD_HELP, /**< show help for commands */
CMD_RESP, /**< response of command */ CMD_STATUS, /**< show general status of f2b daemon */
CMD_HELP, /**< show help for commands (used internally by client) */ CMD_RELOAD, /**< reload all jails */
CMD_PING = 8, /**< check connection */ CMD_SHUTDOWN, /**< gracefull shutdown daemon */
CMD_STATUS, /**< show general status of f2b daemon */ /* logging */
CMD_LOG_ROTATE,/**< reopen logfile. works only if set `logdest = file` */ CMD_LOG_ROTATE, /**< reopen logfile. (only for `logdest = file`) */
CMD_RELOAD, /**< reload all jails */ CMD_LOG_LEVEL, /**< change maximum level of logged messages */
CMD_SHUTDOWN, /**< gracefull shutdown */
CMD_LOG_LEVEL, /**< change maximum level of logged messages */
/* jail commands */ /* 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_SET, /**< set parameter of given jail */
CMD_JAIL_IP_STATUS, /**< show status of given ip */ CMD_JAIL_IP_STATUS, /**< show status of given ip */
CMD_JAIL_IP_BAN, /**< force ban given ip */ CMD_JAIL_IP_BAN, /**< force ban given ip */
@ -43,36 +36,42 @@ enum f2b_cmd_type {
CMD_MAX_NUMBER, /**< placeholder */ 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 * @brief Creates new struct from user input
* @param buf Buffer of control message for storing parsed args * @param line User input string
* @param bufsize Size of buffer size above * @returns pointer to newly allocated struct or NULL on malloc()/parse error
* @param src Line with user input
* @returns @a CMD_NONE if parsing fails, or cmd type less than @a CMD_MAX_NUMBER on success
*/ */
enum f2b_cmd_type f2b_cmd_t *
f2b_cmd_parse(char *buf, size_t bufsize, const char *src); f2b_cmd_create(const char *line);
/** /**
* @brief Append data piece to data buffer of control message * @brief Frees memory
* @param buf Buffer of control message for storing parsed args
* @param bufsize Size of buffer size above
* @param arg Piece to append
*/ */
void 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 * @brief Try to parse user input
* @param type Command type * @param cmd pointer to preallocated f2b_cmd_t struct
* @param argc Args count * @param src Line with user input
* @returns true if matches, false if not * @returns true on success, false otherwise
*/ */
bool 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_ */ #endif /* F2B_COMMANDS_H_ */

20
src/common.h

@ -39,17 +39,13 @@
*/ */
#define DEFAULT_PIDFILE_PATH "/var/run/f2b.pid" #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" #define DEFAULT_CSOCKET_PATH "/var/run/f2b.sock"
/** /**
* Default path of directory to store ip states for jails * Default path of directory to store ip states for jails
*/ */
#define DEFAULT_STATEDIR_PATH "/var/db/f2b" #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 * @def UNUSED
@ -57,16 +53,8 @@
*/ */
#define UNUSED(x) (void)(x) #define UNUSED(x) (void)(x)
/** /* default size of buffers */
* @def SA_REGISTER #define RBUF_SIZE 256
* Register signal handler #define WBUF_SIZE 32768 /* 32Kb */
*/
#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; \
}
#endif /* F2B_COMMON_H_ */ #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. * published by the Free Software Foundation.
*/ */
#include "common.h" #include "common.h"
#include "buf.h"
#include "log.h"
#include "commands.h" #include "commands.h"
#include "cmsg.h"
#include "csocket.h" #include "csocket.h"
#include "log.h"
int #include <sys/socket.h>
f2b_csocket_create(const char *path) { #include <arpa/inet.h>
struct sockaddr_un addr; #include <sys/un.h>
int csock = -1; #include <sys/select.h>
int flags = -1;
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) { struct f2b_csock_t {
f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno)); f2b_conn_t *clients[MAXCONNS];
return -1; const char *path;
} int sock;
};
memset(&addr, 0x0, sizeof(addr)); /* helpers */
addr.sun_family = AF_UNIX;
strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if ((flags = fcntl(csock, F_GETFL, 0)) < 0) static inline int
return -1; max(int a, int b) {
if (fcntl(csock, F_SETFL, flags | O_NONBLOCK) < 0) { return a > b ? a : b;
f2b_log_msg(log_error, "can't set non-blocking mode on socket: %s", strerror(errno)); }
return -1;
}
unlink(path); /* connection-related functions */
if (bind(csock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) {
f2b_log_msg(log_error, "bind() on socket failed: %s", strerror(errno)); f2b_conn_t *
return -1; 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 NULL;
return csock;
} }
void void
f2b_csocket_destroy(int sock, const char *path) { f2b_conn_destroy(f2b_conn_t *conn) {
assert(path != NULL); f2b_buf_free(&conn->recv);
f2b_buf_free(&conn->send);
if (sock >= 0) free(conn);
close(sock);
unlink(path);
return;
} }
int int
f2b_csocket_connect(const char *spath, const char *cpath) { f2b_conn_process(f2b_conn_t *conn, bool in, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) {
struct sockaddr_un caddr, saddr; f2b_cmd_t *cmd = NULL;
int csock = -1; 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); f2b_csock_t *
assert(cpath != NULL); 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)); if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) {
memset(&caddr, 0x0, sizeof(saddr)); f2b_log_msg(log_error, "can't create control socket: %s", strerror(errno));
return NULL;
}
caddr.sun_family = AF_LOCAL; memset(&addr, 0x0, sizeof(addr));
strlcpy(caddr.sun_path, cpath, sizeof(caddr.sun_path)); addr.sun_family = AF_UNIX;
saddr.sun_family = AF_LOCAL; strlcpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
strlcpy(saddr.sun_path, spath, sizeof(saddr.sun_path));
if ((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { unlink(path);
f2b_log_msg(log_error, "can't create control socket"); if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) != 0) {
return -1; 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) { if (listen(sock, 5) < 0) {
f2b_log_msg(log_error, "bind() to local side of socket failed: %s", strerror(errno)); f2b_log_msg(log_error, "listen() on socket failed: %s", strerror(errno));
return -1; return NULL;
} }
if (connect(csock, (struct sockaddr *) &saddr, sizeof(struct sockaddr_un)) != 0) { if ((csock = calloc(1, sizeof(f2b_csock_t))) == NULL) {
f2b_log_msg(log_error, "connect() to socket failed: %s", strerror(errno)); f2b_log_msg(log_error, "can't allocate memory for csocket struct");
return -1; shutdown(sock, SHUT_RDWR);
unlink(path);
return NULL;
} }
csock->sock = sock;
csock->path = path;
return csock; return csock;
} }
void void
f2b_csocket_disconnect(int sock, const char *cpath) { f2b_csocket_destroy(f2b_csock_t *csock) {
unlink(cpath); f2b_conn_t *conn = NULL;
if (sock >= 0) assert(csock != NULL);
close(sock);
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; return;
} }
void void
f2b_csocket_rtimeout(int sock, float timeout) { f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res)) {
int ret = 0; struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };
struct timeval tv; struct ucred peer;
tv.tv_sec = (int) timeout; socklen_t peerlen = 0;
tv.tv_usec = (int) ((timeout - tv.tv_sec) * 1000000); fd_set rfds, wfds;
ret = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *) &tv, sizeof(struct timeval)); f2b_conn_t *conn = NULL;
if (ret == 0) int retval, nfds;
return;
f2b_log_msg(log_warn, "can't set recv timeout for csocket: %s", strerror(errno)); assert(csock != NULL);
} assert(cb != NULL);
const char * /* loop / init */
f2b_csocket_error(int retcode) { FD_ZERO(&rfds);
const char *err = "no error"; FD_ZERO(&wfds);
switch (retcode) {
case -1 : err = strerror(errno); break; FD_SET(csock->sock, &rfds); /* watch for new connections */
case -2 : err = "damaged cmsg on socket: truncated"; break;
case -3 : err = "damaged cmsg on socket: no magic"; break; /* watch for new data on established connections */
case -4 : err = "damaged cmsg on socket: version mismatch"; break; nfds = csock->sock;
case -5 : err = "damaged cmsg on socket: unknown command type"; break; for (int cnum = 0; cnum < MAXCONNS; cnum++) {
case -6 : err = "damaged cmsg on socket: size mismatch"; break; if ((conn = csock->clients[cnum]) == NULL)
default : err = "unknown cmsg error"; break; 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 /* check for new data */
f2b_csocket_send(int csock, f2b_cmsg_t *cmsg, struct sockaddr_storage *addr, socklen_t *addrlen) { retval = select(nfds + 1, &rfds, &wfds, NULL, &tv);
struct msghdr msg; if (retval < 0) {
uint16_t size; if (errno == EINTR) {
int ret; /* interrupted by signal */
} else if (errno == EAGAIN) {
assert(csock >= 0); /* no data */
assert(cmsg != NULL); } else {
f2b_log_msg(log_error, "select() error: %s", strerror(errno));
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));
} }
/* TODO: check auth */ return;
cb(&cmsg, res, sizeof(res)); }
if (cmsg.flags & CMSG_FLAG_NEED_REPLY) { if (retval == 0)
memset(&cmsg, 0x0, sizeof(cmsg)); return; /* no new data */
strncpy(cmsg.magic, "F2B", sizeof(cmsg.magic));
strlcpy(cmsg.data, res, sizeof(cmsg.data)); /* new connection on listening socket? */
cmsg.version = F2B_PROTO_VER; if (FD_ISSET(csock->sock, &rfds)) {
cmsg.type = CMD_RESP; /* find free connection slot */
cmsg.size = strlen(res); int cnum = 0;
cmsg.data[cmsg.size] = '\0'; for (int cnum = 0; cnum < MAXCONNS; cnum++) {
ret = f2b_csocket_send(csock, &cmsg, &addr, &addrlen); 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_ #ifndef F2B_CSOCKET_H_
#define F2B_CSOCKET_H_ #define F2B_CSOCKET_H_
#define MAXCONNS 5
typedef struct f2b_csock_t f2b_csock_t;
/** /**
* @file * @file
* This file contains control socket manage routines * 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 * @brief Create UNIX socket with given path
* @param path Path to socket endpoint * @param path Path to socket endpoint
* @returns Socket fd * @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 * @brief Close UNIX socket and unlink endpoint
* @param csock Socket fd * @param csock Socket fd
* @param path Path to socket endpoint * @param path Path to socket endpoint
*/ */
void f2b_csocket_destroy(int csock, const char *path); void f2b_csocket_destroy(f2b_csock_t *csock);
/**
* @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);
/** /**
* @brief Poll control socket for new messages * @brief Poll control socket for new messages
@ -64,24 +35,6 @@ f2b_csocket_error(int retcode);
* @param cb Callback for handling message * @param cb Callback for handling message
* @returns -1 on error, 0 on no messages, and > 0 on some messages processed * @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)); void f2b_csocket_poll(f2b_csock_t *csock, void (*cb)(const f2b_cmd_t *cmd, f2b_buf_t *res));
/**
* @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);
#endif /* F2B_CSOCKET_H_ */ #endif /* F2B_CSOCKET_H_ */

135
src/daemon.c

@ -10,13 +10,25 @@
#include "jail.h" #include "jail.h"
#include "backend.h" #include "backend.h"
#include "appconfig.h" #include "appconfig.h"
#include "buf.h"
#include "commands.h" #include "commands.h"
#include "cmsg.h"
#include "csocket.h" #include "csocket.h"
#include <getopt.h> #include <getopt.h>
#include <signal.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; enum { stop = 0, run, reconfig, logrotate, test } state = run;
void signal_handler(int signum) { void signal_handler(int signum) {
@ -44,89 +56,95 @@ void usage(int exitcode) {
exit(exitcode); exit(exitcode);
} }
static f2b_csock_t *csock = NULL;
#ifndef WITH_CSOCKET #ifndef WITH_CSOCKET
/* add stubs to reduce #ifdef count */ /* add stubs to reduce #ifdef count */
int f2b_csocket_create (const char *path) { f2b_csock_t *
f2b_csocket_create (const char *path) {
UNUSED(path); UNUSED(path);
f2b_log_msg(log_warn, "control socket support was disabled at compile-time"); 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) { void
UNUSED(csock); UNUSED(path); return; 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; UNUSED(csock); UNUSED(cb); return 0;
} }
void void
f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { f2b_csocket_cmd_process(const f2b_cmd_t *cmd, f2b_buf_t *res) {
UNUSED(msg); UNUSED(res); UNUSED(ressize); return; UNUSED(cmd); UNUSED(res); return;
} }
#else /* WITH_CSOCKET */ #else /* WITH_CSOCKET */
void void
f2b_cmsg_process(const f2b_cmsg_t *msg, char *res, size_t ressize) { f2b_csocket_cmd_process(const f2b_cmd_t *cmd, f2b_buf_t *res) {
const char *args[DATA_ARGS_MAX];
f2b_jail_t *jail = NULL; 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(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) { if (cmd->type == CMD_UNKNOWN)
strlcpy(res, "cmd args number mismatch", ressize);
return; return;
}
if (msg->type >= CMD_JAIL_STATUS && msg->type <= CMD_MAX_NUMBER) { if (cmd->type >= CMD_JAIL_STATUS && cmd->type <= CMD_JAIL_FILTER_RELOAD) {
if ((jail = f2b_jail_find(jails, args[0])) == NULL) { if ((jail = f2b_jail_find(jails, cmd->args[1])) == NULL) {
snprintf(res, ressize, "can't find jail '%s'", args[0]); len = snprintf(buf, sizeof(buf), "can't find jail '%s'\n", cmd->args[1]);
f2b_buf_append(res, buf, len);
return; return;
} }
} }
strlcpy(res, "ok", ressize); /* default reply */ if (cmd->type == CMD_RELOAD) {
if (msg->type == CMD_PING) {
/* nothing to do */
} else if (msg->type == CMD_RELOAD) {
state = reconfig; state = reconfig;
} else if (msg->type == CMD_LOG_ROTATE) { } else if (cmd->type == CMD_LOG_ROTATE) {
state = logrotate; state = logrotate;
} else if (msg->type == CMD_LOG_LEVEL) { } else if (cmd->type == CMD_LOG_LEVEL) {
f2b_log_set_level(args[0]); f2b_log_set_level(cmd->args[2]);
} else if (msg->type == CMD_SHUTDOWN) { } else if (cmd->type == CMD_SHUTDOWN) {
state = stop; state = stop;
} else if (msg->type == CMD_STATUS) { } else if (cmd->type == CMD_STATUS) {
snprintf(line, sizeof(line), "pid: %u\npidfile: %s\ncsocket: %s\nstatedir: %s\njails:\n", len = snprintf(buf, sizeof(buf), "pid: %u\npidfile: %s\n", getpid(), appconfig.pidfile_path);
getpid(), appconfig.pidfile_path, appconfig.csocket_path, appconfig.statedir_path); f2b_buf_append(res, buf, len);
strlcpy(res, line, ressize); 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) { for (jail = jails; jail != NULL; jail = jail->next) {
snprintf(line, sizeof(line), "- %s\n", jail->name); len = snprintf(buf, sizeof(buf), "- %s\n", jail->name);
strlcat(res, line, ressize); f2b_buf_append(res, buf, len);
} }
} else if (msg->type == CMD_JAIL_STATUS) { } else if (cmd->type == CMD_JAIL_STATUS) {
f2b_jail_cmd_status(res, ressize, jail); f2b_jail_cmd_status(buf, sizeof(buf), jail);
} else if (msg->type == CMD_JAIL_SET) { f2b_buf_append(res, buf, 0);
f2b_jail_cmd_set(res, ressize, jail, args[1], args[2]); } else if (cmd->type == CMD_JAIL_SET) {
} else if (msg->type == CMD_JAIL_IP_STATUS) { f2b_jail_cmd_set(buf, sizeof(buf), jail, cmd->args[3], cmd->args[4]);
f2b_jail_cmd_ip_xxx(res, ressize, jail, 0, args[1]); f2b_buf_append(res, buf, 0);
} else if (msg->type == CMD_JAIL_IP_BAN) { } else if (cmd->type == CMD_JAIL_IP_STATUS) {
f2b_jail_cmd_ip_xxx(res, ressize, jail, 1, args[1]); f2b_jail_cmd_ip_xxx(buf, sizeof(buf), jail, 0, cmd->args[4]);
} else if (msg->type == CMD_JAIL_IP_RELEASE) { f2b_buf_append(res, buf, 0);
f2b_jail_cmd_ip_xxx(res, ressize, jail, -1, args[1]); } else if (cmd->type == CMD_JAIL_IP_BAN) {
} else if (msg->type == CMD_JAIL_FILTER_STATS) { f2b_jail_cmd_ip_xxx(buf, sizeof(buf), jail, 1, cmd->args[4]);
f2b_filter_cmd_stats(res, ressize, jail->filter); f2b_buf_append(res, buf, 0);
} else if (msg->type == CMD_JAIL_FILTER_RELOAD) { } else if (cmd->type == CMD_JAIL_IP_RELEASE) {
f2b_filter_cmd_reload(res, ressize, jail->filter); 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 { } 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; return;
} }
#endif /* WITH_CSOCKET */ #endif /* WITH_CSOCKET */
@ -277,8 +295,9 @@ int main(int argc, char *argv[]) {
} }
} }
if (appconfig.csocket_path[0] != '\0') if (appconfig.csocket_path[0] != '\0') {
appconfig.csock = f2b_csocket_create(appconfig.csocket_path); csock = f2b_csocket_create(appconfig.csocket_path);
}
if (config.defaults) if (config.defaults)
f2b_jail_set_defaults(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) { for (f2b_jail_t *jail = jails; jail != NULL; jail = jail->next) {
f2b_jail_process(jail); f2b_jail_process(jail);
} }
f2b_csocket_poll(appconfig.csock, f2b_cmsg_process); f2b_csocket_poll(csock, f2b_csocket_cmd_process);
sleep(1); sleep(1);
if (state == logrotate && strcmp(appconfig.logdest, "file") == 0) { if (state == logrotate && strcmp(appconfig.logdest, "file") == 0) {
state = run; 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_stop(jails);
jails = NULL; 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); 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 */ /** handler of 'jail $JAIL filter reload' cmd */
void f2b_filter_cmd_reload(char *buf, size_t bufsize, f2b_filter_t *f); void f2b_filter_cmd_reload(char *buf, size_t bufsize, f2b_filter_t *f);
/** handler of 'jail $JAIL filter stats' cmd */ /** 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); bool f2b_jail_stop (f2b_jail_t *jail);
/* handlers for cmsg */ /* handlers for csocket commands processing */
/** /**
* @brief Get jail status * @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") add_library("s_portknock" MODULE "portknock.c" "../strlcpy.c")
list(APPEND SOURCES "portknock") 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") find_library(REDIS_FOUND "hiredis")
if (WITH_REDIS AND REDIS_FOUND) if (WITH_REDIS AND REDIS_FOUND)
add_library("s_redis" MODULE "redis.c" "../strlcpy.c") 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") set(SRC_DIR "../src")
add_executable("t_cmd" "t_cmd.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/commands.c") add_executable("t_buf" "t_buf.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/buf.c")
add_executable("t_cmsg" "t_cmsg.c" "${SRC_DIR}/strlcpy.c" "${SRC_DIR}/cmsg.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_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_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_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_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_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_cmd_*" "t_cmd")
add_test("tests/f2b_cmsg_*" "t_cmsg")
add_test("tests/f2b_matches_*" "t_matches") add_test("tests/f2b_matches_*" "t_matches")
add_test("tests/f2b_ipaddr_*" "t_ipaddr") add_test("tests/f2b_ipaddr_*" "t_ipaddr")
add_test("tests/f2b_statefile_*" "t_statefile") 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/common.h"
#include "../src/buf.h"
#include "../src/commands.h" #include "../src/commands.h"
int main() { int main() {
char buf[1024]; f2b_cmd_t cmd;
const char *line;
assert(f2b_cmd_parse(&cmd, "status") == true);
UNUSED(line); assert(cmd.type = CMD_STATUS);
assert(cmd.argc == 1);
buf[0] = '\0'; assert(strcmp(cmd.args[0], "status") == 0);
f2b_cmd_append_arg(buf, sizeof(buf), "42"); assert(cmd.data.used == 6); /* "status" */
assert(strcmp(buf, "42\n") == 0); f2b_buf_free(&cmd.data);
line = "status"; assert(f2b_cmd_parse(&cmd, "stat") == false); /* no such command */
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_STATUS); assert(cmd.type == CMD_UNKNOWN);
line = "statu"; /* no such command */ assert(f2b_cmd_parse(&cmd, "jail test") == false); /* incomplete command */
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_NONE); assert(cmd.type == CMD_UNKNOWN);
line = "jail test"; /* incomplete command */ assert(f2b_cmd_parse(&cmd, "jail test status") == true);
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_NONE); assert(cmd.type == CMD_JAIL_STATUS);
assert(cmd.argc == 3);
buf[0] = '\0'; assert(strcmp(cmd.args[0], "jail") == 0);
line = "jail test status"; assert(strcmp(cmd.args[1], "test") == 0);
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_JAIL_STATUS); assert(strcmp(cmd.args[2], "status") == 0);
assert(strcmp(buf, "test\n") == 0); assert(cmd.data.used == 16); /* "jail\0test\0status" */
f2b_buf_free(&cmd.data);
buf[0] = '\0';
line = "jail test set bantime 7200"; assert(f2b_cmd_parse(&cmd, "jail test set bantime 7200") == true);
assert(f2b_cmd_parse(buf, sizeof(buf), line) == CMD_JAIL_SET); assert(cmd.type == CMD_JAIL_SET);
assert(strcmp(buf, "test\nbantime\n7200\n") == 0); 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; 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