diff --git a/Makefile.am b/Makefile.am index c5b15ab..d36c49a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -14,7 +14,7 @@ endif dist_doc_DATA = README.md TODO ChangeLog COPYING knock_SOURCES = src/knock.c -knockd_SOURCES = src/knockd.c src/list.c src/list.h src/knock_helper_ipt.sh +knockd_SOURCES = src/knockd.c src/list.c src/list.h src/utils.c src/utils.h src/dprint.c src/dprint.h src/door.c src/door.h src/sequences.c src/sequences.h src/config_parser.c src/config_parser.h src/knock_helper_ipt.sh %.1: %.1.in sed -e "s/#VERSION#/$(VERSION)/" $< > $@ diff --git a/src/config_parser.c b/src/config_parser.c new file mode 100644 index 0000000..97721ca --- /dev/null +++ b/src/config_parser.c @@ -0,0 +1,333 @@ +#include "config_parser.h" +#include +#include "dprint.h" +#include +#include "door.h" +#include +#include +#include "sequences.h" +#include "utils.h" +#include + +PMList *doors = NULL; +int o_usesyslog = 0; +int o_verbose = 0; +int o_debug = 0; +int o_daemon = 0; +int o_lookup = 0; +int o_skipIpV6 = 0; +char o_int[32] = ""; /* default (eth0) is set after parseconfig() */ +char o_cfg[PATH_MAX] = "/etc/knockd.conf"; +char o_pidfile[PATH_MAX] = "/var/run/knockd.pid"; +char o_logfile[PATH_MAX] = ""; + +int parseconfig_options(char *key, char* ptr, char* include_dir, int *include_dir_specified, int linenum) { + if(!strcmp(key, "LOGFILE")) { + strncpy(o_logfile, ptr, PATH_MAX-1); + o_logfile[PATH_MAX-1] = '\0'; + dprint("config: log file: %s\n", o_logfile); + } else if(!strcmp(key, "PIDFILE")) { + strncpy(o_pidfile, ptr, PATH_MAX-1); + o_pidfile[PATH_MAX-1] = '\0'; + dprint("config: pid file: %s\n", o_pidfile); + } else if(!strcmp(key, "INTERFACE")) { + /* set interface only if it has not already been set by the -i switch */ + if(strlen(o_int) == 0) { + strncpy(o_int, ptr, sizeof(o_int)-1); + o_int[sizeof(o_int)-1] = '\0'; + dprint("config: interface: %s\n", o_int); + } + } else if(!strcmp(key, "INCLUDE_DIR")) { + strncpy(include_dir, ptr, PATH_MAX-1); + include_dir[PATH_MAX-1] = '\0'; + *include_dir_specified = 1; + dprint("config: include directory: %s\n", include_dir); + } else { + fprintf(stderr, "config: line %d: syntax error\n", linenum); + return(1); + } + + return(0); +} + +int parseconfig_sequence(char *ptr, opendoor_t *door) { + int i = parse_port_sequence(ptr, door); + if(i > 0) { + return(i); + } + dprint_sequence(door, "config: %s: sequence: ", door->name); + return(0); +} + +int parseconfig_door(char *key, char *ptr, opendoor_t *door, int linenum) { + if(!strcmp(key, "TARGET")) { + door->target = malloc(sizeof(char) * (strlen(ptr)+1)); + if(door->target == NULL) { + perror("malloc"); + exit(1); + } + strcpy(door->target, ptr); + dprint("config: %s: target: %s\n", door->name, door->target); + } else if(!strcmp(key, "SEQUENCE")) { + int i = parseconfig_sequence(ptr, door); + if(i != 0) { + return(i); + } + } else if(!strcmp(key, "SEQUENCE_FILE")) { + FILE *sfp = fopen(ptr, "r"); + if(sfp == NULL) { + perror(ptr); + return(1); + } + char buf[PATH_MAX] = ""; + dprint("config: %s: sequence file: %s\n", door->name, ptr); + size_t sfp_read = fread(buf, sizeof(char), PATH_MAX - 1, sfp); + fclose(sfp); + if(sfp_read == 0) { + fprintf(stderr, "config: line %d: sequence file empty: %s", linenum, ptr); + return(1); + } + buf[sfp_read] = '\0'; + int i = parseconfig_sequence(buf, door); + if(i != 0) { + return(i); + } + } else if(!strcmp(key, "ONE_TIME_SEQUENCES")) { + if((door->one_time_sequences_fd = fopen(ptr, "r+")) == NULL) { + perror(ptr); + return(1); + } + dprint("config: %s: one time sequences file: %s\n", door->name, ptr); + if(get_new_one_time_sequence(door) == 0) { + dprint_sequence(door, "config: %s: sequence: ", door->name); + } else { /* no more sequences left in the one time sequences file */ + dprint("config: no more sequences left in the one time sequences file %s\n", ptr); + return(1); + } + } else if(!strcmp(key, "SEQ_TIMEOUT") || !strcmp(key, "TIMEOUT")) { + door->seq_timeout = (time_t)atoi(ptr); + dprint("config: %s: seq_timeout: %d\n", door->name, door->seq_timeout); + } else if(!strcmp(key, "START_COMMAND") || !strcmp(key, "COMMAND")) { + door->start_command = malloc(sizeof(char) * (strlen(ptr)+1)); + if(door->start_command == NULL) { + perror("malloc"); + exit(1); + } + strcpy(door->start_command, ptr); + dprint("config: %s: start_command: %s\n", door->name, door->start_command); + } else if(!strcmp(key, "START_COMMAND_6") || !strcmp(key, "COMMAND_6")) { + door->start_command6 = malloc(sizeof(char) * (strlen(ptr)+1)); + if(door->start_command6 == NULL) { + perror("malloc"); + exit(1); + } + strcpy(door->start_command6, ptr); + dprint("config: %s: start_command_6: %s\n", door->name, door->start_command6); + } else if(!strcmp(key, "CMD_TIMEOUT")) { + door->cmd_timeout = (time_t)atoi(ptr); + dprint("config: %s: cmd_timeout: %d\n", door->name, door->cmd_timeout); + } else if(!strcmp(key, "STOP_COMMAND")) { + door->stop_command = malloc(sizeof(char) * (strlen(ptr)+1)); + if(door->stop_command == NULL) { + perror("malloc"); + exit(1); + } + strcpy(door->stop_command, ptr); + dprint("config: %s: stop_command: %s\n", door->name, door->stop_command); + } else if(!strcmp(key, "STOP_COMMAND_6")) { + door->stop_command6 = malloc(sizeof(char) * (strlen(ptr)+1)); + if(door->stop_command6 == NULL) { + perror("malloc"); + exit(1); + } + strcpy(door->stop_command6, ptr); + dprint("config: %s: stop_command_6: %s\n", door->name, door->stop_command6); + } else if(!strcmp(key, "TCPFLAGS")) { + char *flag; + strtoupper(ptr); + while((flag = strsep(&ptr, ","))) { + /* allow just some flags to be specified */ + if(!strcmp(flag,"FIN")) { + door->flag_fin = SET; + } else if(!strcmp(flag,"!FIN")) { + door->flag_fin = NOT_SET; + } else if(!strcmp(flag, "SYN")) { + door->flag_syn = SET; + } else if(!strcmp(flag, "!SYN")) { + door->flag_syn = NOT_SET; + } else if(!strcmp(flag, "RST")) { + door->flag_rst = SET; + } else if(!strcmp(flag, "!RST")) { + door->flag_rst = NOT_SET; + } else if(!strcmp(flag, "PSH")) { + door->flag_psh = SET; + } else if(!strcmp(flag, "!PSH")) { + door->flag_psh = NOT_SET; + } else if(!strcmp(flag, "ACK")) { + door->flag_ack = SET; + } else if(!strcmp(flag, "!ACK")) { + door->flag_ack = NOT_SET; + } else if(!strcmp(flag, "URG")) { + door->flag_urg = SET; + } else if(!strcmp(flag, "!URG")) { + door->flag_urg = NOT_SET; + } else { + fprintf(stderr, "config: line %d: unrecognized flag \"%s\"\n", + linenum, flag); + return(1); + } + dprint("config: tcp flag: %s\n", flag); + } + } else { + fprintf(stderr, "config: line %d: syntax error\n", linenum); + return(1); + } + + return(0); +} + +/* Parse a config file + */ +int parseconfig(char *configfile, int is_service_config) +{ + FILE *fp = NULL; + char line[PATH_MAX+1]; + char *ptr = NULL; + char *key = NULL; + int linenum = 0; + char section[256] = ""; + char include_dir[PATH_MAX] = ""; + int include_dir_specified = 0; + opendoor_t *door = NULL; + PMList *lp; + + if((fp = fopen(configfile, "r")) == NULL) { + perror(configfile); + return(1); + } + + while(fgets(line, PATH_MAX, fp)) { + linenum++; + trim(line); + if(strlen(line) == 0 || line[0] == '#') { + continue; + } + if(line[0] == '[' && line[strlen(line)-1] == ']') { + /* new config section */ + ptr = line; + ptr++; + strncpy(section, ptr, sizeof(section)); + section[sizeof(section)-1] = '\0'; + section[strlen(section)-1] = '\0'; + dprint("config: new section: '%s'\n", section); + if(!strlen(section)) { + fprintf(stderr, "config: line %d: bad section name\n", linenum); + return(1); + } + if(strcmp(section, "options")) { + /* start a new knock/event record */ + door = malloc(sizeof(opendoor_t)); + if(door == NULL) { + perror("malloc"); + exit(1); + } + init_door(door, section); + doors = list_add(doors, door); + } + else if(is_service_config) { + fprintf(stderr, "config: line %d: service configs can't have [options] section\n", linenum); + return(1); + } + } else { + /* directive */ + if(!strlen(section)) { + fprintf(stderr, "config: line %d: all directives must belong to a section\n", linenum); + return(1); + } + ptr = line; + key = strsep(&ptr, "="); + if(key == NULL) { + fprintf(stderr, "config: line %d: syntax error\n", linenum); + return(1); + } + trim(key); + key = strtoupper(key); + if(ptr == NULL) { + if(!strcmp(key, "USESYSLOG")) { + o_usesyslog = 1; + dprint("config: usesyslog\n"); + } else { + fprintf(stderr, "config: line %d: syntax error\n", linenum); + return(1); + } + } else { + trim(ptr); + if(!strcmp(section, "options")) { + if(is_service_config) { + // To get here we need options to be modified without going through the loop + // It's either a bug or an attack attempt, we should simply exit. + fprintf(stderr, "error: Somehow got inside [options] section on a service config"); + exit(1); + } + int scode = parseconfig_options(key, ptr, include_dir, &include_dir_specified, linenum); + if(scode != 0) { + return(scode); + } + } else { + if(door == NULL) { + fprintf(stderr, "config: line %d: \"%s\" can only be used within a Door section\n", + linenum, key); + return(1); + } + parseconfig_door(key, ptr, door, linenum); + } + line[0] = '\0'; + } + } + } + fclose(fp); + + /* sanity checks */ + for(lp = doors; lp; lp = lp->next) { + door = (opendoor_t*)lp->data; + if(door->seqcount == 0) { + fprintf(stderr, "error: section '%s' has an empty knock sequence\n", door->name); + return(1); + } + } + + if(include_dir_specified) { + DIR *d = opendir(include_dir); + char service_path[PATH_MAX] = ""; + if(d == NULL) { + perror("opendir"); + } + else { + struct dirent *entry; + while((entry = readdir(d)) != NULL) { + if(entry->d_name[0] == '.' && (entry->d_name[1] == '\0' || (entry->d_name[1] == '.' && entry->d_name[2] == '\0'))) { + continue; + } + + if(entry->d_type == DT_REG) { + int path_length = snprintf(service_path, PATH_MAX, "%s/%s", include_dir, entry->d_name); + if(path_length > PATH_MAX) { + fprintf(stderr, "error: path too long, skipping: %s\n", entry->d_name); + continue; + } + int scode = parseconfig(service_path, 1); + if(scode != 0) { + closedir(d); + return(scode); + } + } + else { + fprintf(stderr, "warning: Found non-regular file in the include directory: %s\n", entry->d_name); + } + } + closedir(d); + } + } + + return(0); +} diff --git a/src/config_parser.h b/src/config_parser.h new file mode 100644 index 0000000..7175a5e --- /dev/null +++ b/src/config_parser.h @@ -0,0 +1,47 @@ +/* + * list.h + * + * Copyright (c) 2002-2005 by Judd Vinet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef _CONFIG_H +#define _CONFIG_H + +#include +#include +#include +#include +#include "list.h" + +#define SEQ_TIMEOUT 25 /* default knock timeout in seconds */ +#define CMD_TIMEOUT 10 /* default timeout in seconds between start and stop commands */ +#define SEQ_MAX 32 /* maximum number of ports in a knock sequence */ + +extern PMList *doors; +extern int o_usesyslog; +extern int o_verbose; +extern int o_debug; +extern int o_daemon; +extern int o_lookup; +extern int o_skipIpV6; +extern char o_int[32]; +extern char o_cfg[PATH_MAX]; +extern char o_pidfile[PATH_MAX]; +extern char o_logfile[PATH_MAX]; + +int parseconfig(char *configfile, int is_service_config); + +#endif \ No newline at end of file diff --git a/src/door.c b/src/door.c new file mode 100644 index 0000000..907b720 --- /dev/null +++ b/src/door.c @@ -0,0 +1,45 @@ +#include "door.h" +#include +#include + +void init_door(opendoor_t *door, char *name) { + strncpy(door->name, name, sizeof(door->name)-1); + door->name[sizeof(door->name)-1] = '\0'; + door->target = 0; + door->seqcount = 0; + door->seq_timeout = SEQ_TIMEOUT; /* default sequence timeout (seconds) */ + door->start_command = NULL; + door->start_command6 = NULL; + door->cmd_timeout = CMD_TIMEOUT; /* default command timeout (seconds) */ + door->stop_command = NULL; + door->stop_command6 = NULL; + door->flag_fin = DONT_CARE; + door->flag_syn = DONT_CARE; + door->flag_rst = DONT_CARE; + door->flag_psh = DONT_CARE; + door->flag_ack = DONT_CARE; + door->flag_urg = DONT_CARE; + door->one_time_sequences_fd = NULL; + door->pcap_filter_exp = NULL; + door->pcap_filter_expv6 = NULL; +} + +void close_door(opendoor_t *door) +{ + doors = list_remove(doors, door); + free_door(door); +} + +void free_door(opendoor_t *door) +{ + if(door) { + free(door->target); + free(door->start_command); + free(door->stop_command); + if(door->one_time_sequences_fd) { + fclose(door->one_time_sequences_fd); + } + free(door->pcap_filter_exp); + free(door); + } +} \ No newline at end of file diff --git a/src/door.h b/src/door.h new file mode 100644 index 0000000..276dce3 --- /dev/null +++ b/src/door.h @@ -0,0 +1,43 @@ +#ifndef _DOOR_H +#define _DOOR_H + +#include "config_parser.h" + +typedef enum _flag_stat { + DONT_CARE, /* 0 */ + SET, /* 1 */ + NOT_SET /* 2 */ +} flag_stat; + +/* knock/event tuples */ +typedef struct opendoor { + char name[128]; + unsigned short seqcount; + unsigned short sequence[SEQ_MAX]; + unsigned short protocol[SEQ_MAX]; + char *target; + time_t seq_timeout; + char *start_command; + char *start_command6; + time_t cmd_timeout; + char *stop_command; + char *stop_command6; + flag_stat flag_fin; + flag_stat flag_syn; + flag_stat flag_rst; + flag_stat flag_psh; + flag_stat flag_ack; + flag_stat flag_urg; + FILE *one_time_sequences_fd; + char *pcap_filter_exp; + char *pcap_filter_expv6; +} opendoor_t; + +void init_door(opendoor_t *door, char *name); + +/* Disable the door by removing it from the doors list and free all allocated memory. + */ +void close_door(opendoor_t *door); +void free_door(opendoor_t *door); + +#endif \ No newline at end of file diff --git a/src/dprint.c b/src/dprint.c new file mode 100644 index 0000000..eeae553 --- /dev/null +++ b/src/dprint.c @@ -0,0 +1,75 @@ +#include "dprint.h" +#include +#include +#include + +FILE *logfd = NULL; + +void dprint(char *fmt, ...) +{ + va_list args; + if(o_debug) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); + } +} + +void vprint(char *fmt, ...) +{ + va_list args; + if(o_verbose) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); + } +} + +/* Output a message to syslog and/or a logfile */ +void logprint(char *fmt, ...) +{ + char msg[1024]; + va_list args; + va_start(args, fmt); + vsnprintf(msg, 1024, fmt, args); + va_end(args); + if(o_usesyslog) { + syslog(LOG_NOTICE, "%s", msg); + } + if(logfd) { + time_t t; + struct tm *tm; + t = time(NULL); + tm = localtime(&t); + + fprintf(logfd, "[%04d-%02d-%02d %02d:%02d] %s\n", tm->tm_year+1900, + tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, msg); + fflush(logfd); + } +} + +/* Output current sequence of door for debugging */ +void dprint_sequence(opendoor_t *door, char *fmt, ...) +{ + va_list args; + int i; + + if(o_debug) { + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + for(i = 0; i < door->seqcount; i++) { + switch(door->protocol[i]){ + case IPPROTO_UDP: + printf((i == door->seqcount-1 ? "%u:udp\n" : "%u:udp,"), door->sequence[i]); + break; + case IPPROTO_TCP: /* fallthrough */ + default: + printf((i == door->seqcount-1 ? "%u:tcp\n" : "%u:tcp,"), door->sequence[i]); + } + } + fflush(stdout); + } +} \ No newline at end of file diff --git a/src/dprint.h b/src/dprint.h new file mode 100644 index 0000000..37a4b37 --- /dev/null +++ b/src/dprint.h @@ -0,0 +1,35 @@ +/* + * list.h + * + * Copyright (c) 2002-2005 by Judd Vinet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _KNOCK_DPRINT +#define _KNOCK_DPRINT + +#include "door.h" + +extern FILE *logfd; + + +void dprint(char *fmt, ...); +void vprint(char *fmt, ...); +void logprint(char *fmt, ...); +void dprint_sequence(opendoor_t *door, char *fmt, ...); + + +#endif \ No newline at end of file diff --git a/src/knockd.c b/src/knockd.c index eff10bc..908509e 100644 --- a/src/knockd.c +++ b/src/knockd.c @@ -32,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -56,7 +55,11 @@ #include #include #include +#include #include "list.h" +#include "config_parser.h" +#include "dprint.h" +#include "sequences.h" #if __APPLE__ #undef daemon @@ -65,41 +68,6 @@ extern int daemon(int, int); static char version[] = "0.8"; -#define SEQ_TIMEOUT 25 /* default knock timeout in seconds */ -#define CMD_TIMEOUT 10 /* default timeout in seconds between start and stop commands */ -#define SEQ_MAX 32 /* maximum number of ports in a knock sequence */ - -typedef enum _flag_stat { - DONT_CARE, /* 0 */ - SET, /* 1 */ - NOT_SET /* 2 */ -} flag_stat; - -/* knock/event tuples */ -typedef struct opendoor { - char name[128]; - unsigned short seqcount; - unsigned short sequence[SEQ_MAX]; - unsigned short protocol[SEQ_MAX]; - char *target; - time_t seq_timeout; - char *start_command; - char *start_command6; - time_t cmd_timeout; - char *stop_command; - char *stop_command6; - flag_stat flag_fin; - flag_stat flag_syn; - flag_stat flag_rst; - flag_stat flag_psh; - flag_stat flag_ack; - flag_stat flag_urg; - FILE *one_time_sequences_fd; - char *pcap_filter_exp; - char *pcap_filter_expv6; -} opendoor_t; -PMList *doors = NULL; - /* we keep one list of knock attempts per IP address, * and increment the stage as they progress through the sequence. */ @@ -114,28 +82,14 @@ typedef struct knocker { PMList *attempts = NULL; /* function prototypes */ -void dprint(char *fmt, ...); -void vprint(char *fmt, ...); -void logprint(char *fmt, ...); -void dprint_sequence(opendoor_t *door, char *fmt, ...); void cleanup(int signum); void child_exit(int signum); void reload(int signum); void ver(); void usage(int exit_code); -char* strtoupper(char *str); -char* trim(char *str); void runCommand(char *cmd); -int parseconfig(char *configfile); -int parse_port_sequence(char *sequence, opendoor_t *door); -int get_new_one_time_sequence(opendoor_t *door); -long get_next_one_time_sequence(opendoor_t *door); -int disable_used_one_time_sequence(opendoor_t *door); -long get_current_one_time_sequence_position(opendoor_t *door); void generate_pcap_filter(); size_t realloc_strcat(char **dest, const char *src, size_t size); -void free_door(opendoor_t *door); -void close_door(opendoor_t *door); char* get_ip(const char *iface, char *buf, int bufsize); size_t parse_cmd(char *dest, size_t size, const char *command, const char *src); int exec_cmd(char *command, char *name); @@ -143,7 +97,6 @@ void sniff(u_char *arg, const struct pcap_pkthdr *hdr, const u_char *packet); int target_strcmp(char *ip, char *target); pcap_t *cap = NULL; -FILE *logfd = NULL; int lltype = -1; int has_ipv4 = 0; int has_ipv6 = 0; @@ -156,17 +109,6 @@ typedef struct ip_literal { } ip_literal_t; ip_literal_t *myips = NULL; -int o_usesyslog = 0; -int o_verbose = 0; -int o_debug = 0; -int o_daemon = 0; -int o_lookup = 0; -int o_skipIpV6 = 0; -char o_int[32] = ""; /* default (eth0) is set after parseconfig() */ -char o_cfg[PATH_MAX] = "/etc/knockd.conf"; -char o_pidfile[PATH_MAX] = "/var/run/knockd.pid"; -char o_logfile[PATH_MAX] = ""; - int main(int argc, char **argv) { struct ifaddrs *ifaddr, *ifa; @@ -219,7 +161,7 @@ int main(int argc, char **argv) } } - if(parseconfig(o_cfg)) { + if(parseconfig(o_cfg, 0)) { usage(1); } @@ -348,75 +290,6 @@ int main(int argc, char **argv) exit(0); } -void dprint(char *fmt, ...) -{ - va_list args; - if(o_debug) { - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - fflush(stdout); - } -} - -void vprint(char *fmt, ...) -{ - va_list args; - if(o_verbose) { - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - fflush(stdout); - } -} - -/* Output a message to syslog and/or a logfile */ -void logprint(char *fmt, ...) -{ - char msg[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(msg, 1024, fmt, args); - va_end(args); - if(o_usesyslog) { - syslog(LOG_NOTICE, "%s", msg); - } - if(logfd) { - time_t t; - struct tm *tm; - t = time(NULL); - tm = localtime(&t); - - fprintf(logfd, "[%04d-%02d-%02d %02d:%02d] %s\n", tm->tm_year+1900, - tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, msg); - fflush(logfd); - } -} - -/* Output current sequence of door for debugging */ -void dprint_sequence(opendoor_t *door, char *fmt, ...) -{ - va_list args; - int i; - - if(o_debug) { - va_start(args, fmt); - vprintf(fmt, args); - va_end(args); - for(i = 0; i < door->seqcount; i++) { - switch(door->protocol[i]){ - case IPPROTO_UDP: - printf((i == door->seqcount-1 ? "%u:udp\n" : "%u:udp,"), door->sequence[i]); - break; - case IPPROTO_TCP: /* fallthrough */ - default: - printf((i == door->seqcount-1 ? "%u:tcp\n" : "%u:tcp,"), door->sequence[i]); - } - } - fflush(stdout); - } -} - /* Signal handlers */ void cleanup(int signum) { @@ -470,7 +343,7 @@ void reload(int signum) list_free(attempts); attempts = NULL; - res_cfg = parseconfig(o_cfg); + res_cfg = parseconfig(o_cfg, 0); vprint("closing log file: %s\n", o_logfile); @@ -523,417 +396,6 @@ void ver() { exit(0); } -/* Convert a string to uppercase - */ -char* strtoupper(char *str) -{ - char *ptr = str; - - while(*ptr) { - (*ptr) = toupper(*ptr); - ptr++; - } - return str; -} - -/* Trim whitespace and newlines from a string - */ -char* trim(char *str) -{ - char *pch = str; - while(isspace(*pch)) { - pch++; - } - if(pch != str) { - memmove(str, pch, (strlen(pch) + 1)); - } - - size_t len = strlen(str); - if(len == 0) { - return str; - } - - pch = (char*)(str + (len - 1)); - while(isspace(*pch)) { - pch--; - } - *++pch = '\0'; - - return str; -} - -/* Parse a config file - */ -int parseconfig(char *configfile) -{ - FILE *fp = NULL; - char line[PATH_MAX+1]; - char *ptr = NULL; - char *key = NULL; - int linenum = 0; - char section[256] = ""; - opendoor_t *door = NULL; - PMList *lp; - - if((fp = fopen(configfile, "r")) == NULL) { - perror(configfile); - return(1); - } - - while(fgets(line, PATH_MAX, fp)) { - linenum++; - trim(line); - if(strlen(line) == 0 || line[0] == '#') { - continue; - } - if(line[0] == '[' && line[strlen(line)-1] == ']') { - /* new config section */ - ptr = line; - ptr++; - strncpy(section, ptr, sizeof(section)); - section[sizeof(section)-1] = '\0'; - section[strlen(section)-1] = '\0'; - dprint("config: new section: '%s'\n", section); - if(!strlen(section)) { - fprintf(stderr, "config: line %d: bad section name\n", linenum); - return(1); - } - if(strcmp(section, "options")) { - /* start a new knock/event record */ - door = malloc(sizeof(opendoor_t)); - if(door == NULL) { - perror("malloc"); - exit(1); - } - strncpy(door->name, section, sizeof(door->name)-1); - door->name[sizeof(door->name)-1] = '\0'; - door->target = 0; - door->seqcount = 0; - door->seq_timeout = SEQ_TIMEOUT; /* default sequence timeout (seconds) */ - door->start_command = NULL; - door->start_command6 = NULL; - door->cmd_timeout = CMD_TIMEOUT; /* default command timeout (seconds) */ - door->stop_command = NULL; - door->stop_command6 = NULL; - door->flag_fin = DONT_CARE; - door->flag_syn = DONT_CARE; - door->flag_rst = DONT_CARE; - door->flag_psh = DONT_CARE; - door->flag_ack = DONT_CARE; - door->flag_urg = DONT_CARE; - door->one_time_sequences_fd = NULL; - door->pcap_filter_exp = NULL; - door->pcap_filter_expv6 = NULL; - doors = list_add(doors, door); - } - } else { - /* directive */ - if(!strlen(section)) { - fprintf(stderr, "config: line %d: all directives must belong to a section\n", linenum); - return(1); - } - ptr = line; - key = strsep(&ptr, "="); - if(key == NULL) { - fprintf(stderr, "config: line %d: syntax error\n", linenum); - return(1); - } - trim(key); - key = strtoupper(key); - if(ptr == NULL) { - if(!strcmp(key, "USESYSLOG")) { - o_usesyslog = 1; - dprint("config: usesyslog\n"); - } else { - fprintf(stderr, "config: line %d: syntax error\n", linenum); - return(1); - } - } else { - trim(ptr); - if(!strcmp(section, "options")) { - if(!strcmp(key, "LOGFILE")) { - strncpy(o_logfile, ptr, PATH_MAX-1); - o_logfile[PATH_MAX-1] = '\0'; - dprint("config: log file: %s\n", o_logfile); - } else if(!strcmp(key, "PIDFILE")) { - strncpy(o_pidfile, ptr, PATH_MAX-1); - o_pidfile[PATH_MAX-1] = '\0'; - dprint("config: pid file: %s\n", o_pidfile); - } else if(!strcmp(key, "INTERFACE")) { - /* set interface only if it has not already been set by the -i switch */ - if(strlen(o_int) == 0) { - strncpy(o_int, ptr, sizeof(o_int)-1); - o_int[sizeof(o_int)-1] = '\0'; - dprint("config: interface: %s\n", o_int); - } - } else { - fprintf(stderr, "config: line %d: syntax error\n", linenum); - return(1); - } - } else { - if(door == NULL) { - fprintf(stderr, "config: line %d: \"%s\" can only be used within a Door section\n", - linenum, key); - return(1); - } - if(!strcmp(key, "TARGET")) { - door->target = malloc(sizeof(char) * (strlen(ptr)+1)); - if(door->target == NULL) { - perror("malloc"); - exit(1); - } - strcpy(door->target, ptr); - dprint("config: %s: target: %s\n", door->name, door->target); - } else if(!strcmp(key, "SEQUENCE")) { - int i; - i = parse_port_sequence(ptr, door); - if(i > 0) { - return(i); - } - dprint_sequence(door, "config: %s: sequence: ", door->name); - } else if(!strcmp(key, "ONE_TIME_SEQUENCES")) { - if((door->one_time_sequences_fd = fopen(ptr, "r+")) == NULL) { - perror(ptr); - return(1); - } - dprint("config: %s: one time sequences file: %s\n", door->name, ptr); - if(get_new_one_time_sequence(door) == 0) { - dprint_sequence(door, "config: %s: sequence: ", door->name); - } else { /* no more sequences left in the one time sequences file */ - dprint("config: no more sequences left in the one time sequences file %s\n", ptr); - return(1); - } - } else if(!strcmp(key, "SEQ_TIMEOUT") || !strcmp(key, "TIMEOUT")) { - door->seq_timeout = (time_t)atoi(ptr); - dprint("config: %s: seq_timeout: %d\n", door->name, door->seq_timeout); - } else if(!strcmp(key, "START_COMMAND") || !strcmp(key, "COMMAND")) { - door->start_command = malloc(sizeof(char) * (strlen(ptr)+1)); - if(door->start_command == NULL) { - perror("malloc"); - exit(1); - } - strcpy(door->start_command, ptr); - dprint("config: %s: start_command: %s\n", door->name, door->start_command); - } else if(!strcmp(key, "START_COMMAND_6") || !strcmp(key, "COMMAND_6")) { - door->start_command6 = malloc(sizeof(char) * (strlen(ptr)+1)); - if(door->start_command6 == NULL) { - perror("malloc"); - exit(1); - } - strcpy(door->start_command6, ptr); - dprint("config: %s: start_command_6: %s\n", door->name, door->start_command6); - } else if(!strcmp(key, "CMD_TIMEOUT")) { - door->cmd_timeout = (time_t)atoi(ptr); - dprint("config: %s: cmd_timeout: %d\n", door->name, door->cmd_timeout); - } else if(!strcmp(key, "STOP_COMMAND")) { - door->stop_command = malloc(sizeof(char) * (strlen(ptr)+1)); - if(door->stop_command == NULL) { - perror("malloc"); - exit(1); - } - strcpy(door->stop_command, ptr); - dprint("config: %s: stop_command: %s\n", door->name, door->stop_command); - } else if(!strcmp(key, "STOP_COMMAND_6")) { - door->stop_command6 = malloc(sizeof(char) * (strlen(ptr)+1)); - if(door->stop_command6 == NULL) { - perror("malloc"); - exit(1); - } - strcpy(door->stop_command6, ptr); - dprint("config: %s: stop_command_6: %s\n", door->name, door->stop_command6); - } else if(!strcmp(key, "TCPFLAGS")) { - char *flag; - strtoupper(ptr); - while((flag = strsep(&ptr, ","))) { - /* allow just some flags to be specified */ - if(!strcmp(flag,"FIN")) { - door->flag_fin = SET; - } else if(!strcmp(flag,"!FIN")) { - door->flag_fin = NOT_SET; - } else if(!strcmp(flag, "SYN")) { - door->flag_syn = SET; - } else if(!strcmp(flag, "!SYN")) { - door->flag_syn = NOT_SET; - } else if(!strcmp(flag, "RST")) { - door->flag_rst = SET; - } else if(!strcmp(flag, "!RST")) { - door->flag_rst = NOT_SET; - } else if(!strcmp(flag, "PSH")) { - door->flag_psh = SET; - } else if(!strcmp(flag, "!PSH")) { - door->flag_psh = NOT_SET; - } else if(!strcmp(flag, "ACK")) { - door->flag_ack = SET; - } else if(!strcmp(flag, "!ACK")) { - door->flag_ack = NOT_SET; - } else if(!strcmp(flag, "URG")) { - door->flag_urg = SET; - } else if(!strcmp(flag, "!URG")) { - door->flag_urg = NOT_SET; - } else { - fprintf(stderr, "config: line %d: unrecognized flag \"%s\"\n", - linenum, flag); - return(1); - } - dprint("config: tcp flag: %s\n", flag); - } - } else { - fprintf(stderr, "config: line %d: syntax error\n", linenum); - return(1); - } - } - line[0] = '\0'; - } - } - } - fclose(fp); - - /* sanity checks */ - for(lp = doors; lp; lp = lp->next) { - door = (opendoor_t*)lp->data; - if(door->seqcount == 0) { - fprintf(stderr, "error: section '%s' has an empty knock sequence\n", door->name); - return(1); - } - } - - return(0); -} - -/* Parse a port:protocol sequence. Returns a positive integer on error. - */ -int parse_port_sequence(char *sequence, opendoor_t *door) -{ - char *num; - char *protocol; - char *port; - int portnum; - - door->seqcount = 0; /* reset seqcount */ - while((num = strsep(&sequence, ","))) { - if(door->seqcount >= SEQ_MAX) { - fprintf(stderr, "config: section %s: too many ports in knock sequence\n", door->name); - logprint("error: section %s: too many ports in knock sequence\n", door->name); - return(1); - } - port = strsep(&num, ":"); - /* convert to 4-byte int first so we can easily detect a short overflow */ - portnum = atoi(port); - if(portnum > 65535) { - fprintf(stderr, "config: section %s: port %s is invalid\n", door->name, port); - return(1); - } - door->sequence[door->seqcount++] = (unsigned short)portnum; - if((protocol = strsep(&num, ":"))){ - protocol = strtoupper(trim(protocol)); - if(!strcmp(protocol, "TCP")){ - door->protocol[door->seqcount-1] = IPPROTO_TCP; - } else if(!strcmp(protocol, "UDP")) { - door->protocol[door->seqcount-1] = IPPROTO_UDP; - } else { - fprintf(stderr, "config: section %s: unknown protocol in knock sequence\n", door->name); - logprint("error: section %s: unknown protocol in knock sequence\n", door->name); - return(1); - } - } else { - door->protocol[door->seqcount-1] = IPPROTO_TCP; /* default protocol */ - } - } - return(0); -} - -/* Read a new sequence from the one time sequences file and update the door. - */ -int get_new_one_time_sequence(opendoor_t *door) -{ - rewind(door->one_time_sequences_fd); - if(get_next_one_time_sequence(door) < 0) { - /* disable the door by removing it from the doors list if there are no sequences anymore */ - fprintf(stderr, "no more sequences left in the one time sequences file for door %s --> disabling the door\n", door->name); - logprint("no more sequences left in the one time sequences file for door %s --> disabling the door\n", door->name); - close_door(door); - return(1); - } - dprint_sequence(door, "new sequence for door %s: ", door->name); - - return(0); -} - -/* Search from the current position in the one time sequence file for the next - * valid sequence and insert it into the door structure. Returns the position of - * the beginning of the found line within the file or a negative value if no - * valid sequence has been found. - */ -long get_next_one_time_sequence(opendoor_t *door) -{ - char line[PATH_MAX+1]; - int pos; - - pos = ftell(door->one_time_sequences_fd); - while(fgets(line, PATH_MAX, door->one_time_sequences_fd)) { - trim(line); - if(strlen(line) == 0 || line[0] == '#') { - pos = ftell(door->one_time_sequences_fd); - continue; - } - if(parse_port_sequence(line, door) > 0) { - /* continue searching if parse_port_sequnce returned with an error */ - continue; - } - return(pos); - } - /* no valid line found */ - return(-1); -} - -/* Remove a one time sequence from the corresponding file (after a successful - * knock attempt) - */ -int disable_used_one_time_sequence(opendoor_t *door) -{ - long pos = get_current_one_time_sequence_position(door); - if(pos >= 0) { - if(fseek(door->one_time_sequences_fd, pos, SEEK_SET) < 0) { - fprintf(stderr, "error while disabling used one time sequence for door %s --> disabling the door\n", door->name); - logprint("error while disabling used one time sequence for door %s --> disabling the door\n", door->name); - close_door(door); - return(1); - } - if(fputc('#', door->one_time_sequences_fd) == EOF) { - fprintf(stderr, "error while disabling used one time sequence for door %s --> disabling the door\n", door->name); - logprint("error while disabling used one time sequence for door %s --> disabling the door\n", door->name); - close_door(door); - return(1); - } - } - return(0); -} - -/* Get the position (beginning of line) in the one time sequence file of the - * current sequence such that we know where to insert a '#' to disable the - * sequence in the one time sequence file - */ -long get_current_one_time_sequence_position(opendoor_t *door) -{ - opendoor_t pseudo_door; /* used to compare sequences in the file and the current sequence in door */ - long pos; - - rewind(door->one_time_sequences_fd); - pseudo_door.one_time_sequences_fd = door->one_time_sequences_fd; - - pos = get_next_one_time_sequence(&pseudo_door); - while(pos >= 0) { - if(door->seqcount == pseudo_door.seqcount) { - if((memcmp((void*) door->sequence, (void*) pseudo_door.sequence, door->seqcount) == 0) - && (memcmp((void*) door->protocol, (void*) pseudo_door.protocol, door->seqcount) == 0)) { - return(pos); - } - } - pos = get_next_one_time_sequence(&pseudo_door); - } - return(-1); -} - /* Generate and set the filter for pcap. That way only the relevant packets will * be forwarded to us (in sniff()). Note that generate_pcap_filter() will first * generate a subfilter (=substring of the whole filter string) for each door if @@ -1281,28 +743,6 @@ size_t realloc_strcat(char **dest, const char *src, size_t size) return new_size; } -void free_door(opendoor_t *door) -{ - if(door) { - free(door->target); - free(door->start_command); - free(door->stop_command); - if(door->one_time_sequences_fd) { - fclose(door->one_time_sequences_fd); - } - free(door->pcap_filter_exp); - free(door); - } -} - -/* Disable the door by removing it from the doors list and free all allocated memory. - */ -void close_door(opendoor_t *door) -{ - doors = list_remove(doors, door); - free_door(door); -} - /* Get the IP address of an interface */ char* get_ip(const char* iface, char *buf, int bufsize) diff --git a/src/sequences.c b/src/sequences.c new file mode 100644 index 0000000..3747987 --- /dev/null +++ b/src/sequences.c @@ -0,0 +1,141 @@ +#include "sequences.h" +#include "utils.h" +#include +#include "dprint.h" +#include +#include + +/* Search from the current position in the one time sequence file for the next + * valid sequence and insert it into the door structure. Returns the position of + * the beginning of the found line within the file or a negative value if no + * valid sequence has been found. + */ +long get_next_one_time_sequence(opendoor_t *door) +{ + char line[PATH_MAX+1]; + int pos; + + pos = ftell(door->one_time_sequences_fd); + while(fgets(line, PATH_MAX, door->one_time_sequences_fd)) { + trim(line); + if(strlen(line) == 0 || line[0] == '#') { + pos = ftell(door->one_time_sequences_fd); + continue; + } + if(parse_port_sequence(line, door) > 0) { + /* continue searching if parse_port_sequnce returned with an error */ + continue; + } + return(pos); + } + /* no valid line found */ + return(-1); +} + +/* Read a new sequence from the one time sequences file and update the door. + */ +int get_new_one_time_sequence(opendoor_t *door) +{ + rewind(door->one_time_sequences_fd); + if(get_next_one_time_sequence(door) < 0) { + /* disable the door by removing it from the doors list if there are no sequences anymore */ + fprintf(stderr, "no more sequences left in the one time sequences file for door %s --> disabling the door\n", door->name); + logprint("no more sequences left in the one time sequences file for door %s --> disabling the door\n", door->name); + close_door(door); + return(1); + } + dprint_sequence(door, "new sequence for door %s: ", door->name); + + return(0); +} + +/* Parse a port:protocol sequence. Returns a positive integer on error. + */ +int parse_port_sequence(char *sequence, opendoor_t *door) +{ + char *num; + char *protocol; + char *port; + int portnum; + + door->seqcount = 0; /* reset seqcount */ + while((num = strsep(&sequence, ","))) { + if(door->seqcount >= SEQ_MAX) { + fprintf(stderr, "config: section %s: too many ports in knock sequence\n", door->name); + logprint("error: section %s: too many ports in knock sequence\n", door->name); + return(1); + } + port = strsep(&num, ":"); + /* convert to 4-byte int first so we can easily detect a short overflow */ + portnum = atoi(port); + if(portnum > 65535) { + fprintf(stderr, "config: section %s: port %s is invalid\n", door->name, port); + return(1); + } + door->sequence[door->seqcount++] = (unsigned short)portnum; + if((protocol = strsep(&num, ":"))){ + protocol = strtoupper(trim(protocol)); + if(!strcmp(protocol, "TCP")){ + door->protocol[door->seqcount-1] = IPPROTO_TCP; + } else if(!strcmp(protocol, "UDP")) { + door->protocol[door->seqcount-1] = IPPROTO_UDP; + } else { + fprintf(stderr, "config: section %s: unknown protocol in knock sequence\n", door->name); + logprint("error: section %s: unknown protocol in knock sequence\n", door->name); + return(1); + } + } else { + door->protocol[door->seqcount-1] = IPPROTO_TCP; /* default protocol */ + } + } + return(0); +} + +/* Get the position (beginning of line) in the one time sequence file of the + * current sequence such that we know where to insert a '#' to disable the + * sequence in the one time sequence file + */ +long get_current_one_time_sequence_position(opendoor_t *door) +{ + opendoor_t pseudo_door; /* used to compare sequences in the file and the current sequence in door */ + long pos; + + rewind(door->one_time_sequences_fd); + pseudo_door.one_time_sequences_fd = door->one_time_sequences_fd; + + pos = get_next_one_time_sequence(&pseudo_door); + while(pos >= 0) { + if(door->seqcount == pseudo_door.seqcount) { + if((memcmp((void*) door->sequence, (void*) pseudo_door.sequence, door->seqcount) == 0) + && (memcmp((void*) door->protocol, (void*) pseudo_door.protocol, door->seqcount) == 0)) { + return(pos); + } + } + pos = get_next_one_time_sequence(&pseudo_door); + } + return(-1); +} + + +/* Remove a one time sequence from the corresponding file (after a successful + * knock attempt) + */ +int disable_used_one_time_sequence(opendoor_t *door) +{ + long pos = get_current_one_time_sequence_position(door); + if(pos >= 0) { + if(fseek(door->one_time_sequences_fd, pos, SEEK_SET) < 0) { + fprintf(stderr, "error while disabling used one time sequence for door %s --> disabling the door\n", door->name); + logprint("error while disabling used one time sequence for door %s --> disabling the door\n", door->name); + close_door(door); + return(1); + } + if(fputc('#', door->one_time_sequences_fd) == EOF) { + fprintf(stderr, "error while disabling used one time sequence for door %s --> disabling the door\n", door->name); + logprint("error while disabling used one time sequence for door %s --> disabling the door\n", door->name); + close_door(door); + return(1); + } + } + return(0); +} \ No newline at end of file diff --git a/src/sequences.h b/src/sequences.h new file mode 100644 index 0000000..c3a3915 --- /dev/null +++ b/src/sequences.h @@ -0,0 +1,35 @@ +#ifndef _SEQUENCES_H +#define _SEQUENCES_H + +#include "door.h" + +/* Search from the current position in the one time sequence file for the next + * valid sequence and insert it into the door structure. Returns the position of + * the beginning of the found line within the file or a negative value if no + * valid sequence has been found. + */ +long get_next_one_time_sequence(opendoor_t *door); + +/* Read a new sequence from the one time sequences file and update the door. + */ +int get_new_one_time_sequence(opendoor_t *door); + +/* Parse a port:protocol sequence. Returns a positive integer on error. + */ +int parse_port_sequence(char *sequence, opendoor_t *door); + + +/* Get the position (beginning of line) in the one time sequence file of the + * current sequence such that we know where to insert a '#' to disable the + * sequence in the one time sequence file + */ +long get_current_one_time_sequence_position(opendoor_t *door); + + +/* Remove a one time sequence from the corresponding file (after a successful + * knock attempt) + */ +int disable_used_one_time_sequence(opendoor_t *door); + + +#endif \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..57ec061 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,42 @@ +#include "utils.h" +#include +#include + +/* Convert a string to uppercase + */ +char* strtoupper(char *str) +{ + char *ptr = str; + + while(*ptr) { + (*ptr) = toupper(*ptr); + ptr++; + } + return str; +} + +/* Trim whitespace and newlines from a string + */ +char* trim(char *str) +{ + char *pch = str; + while(isspace(*pch)) { + pch++; + } + if(pch != str) { + memmove(str, pch, (strlen(pch) + 1)); + } + + size_t len = strlen(str); + if(len == 0) { + return str; + } + + pch = (char*)(str + (len - 1)); + while(isspace(*pch)) { + pch--; + } + *++pch = '\0'; + + return str; +} \ No newline at end of file diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..8b3864c --- /dev/null +++ b/src/utils.h @@ -0,0 +1,14 @@ +#ifndef _UTILS_H +#define _UTILS_H + + +/* Convert a string to uppercase + */ +char* strtoupper(char *str); + +/* Trim whitespace and newlines from a string + */ +char* trim(char *str); + + +#endif \ No newline at end of file