diff --git a/INSTALL b/INSTALL index 0b30e06..7a8449a 100644 --- a/INSTALL +++ b/INSTALL @@ -19,6 +19,8 @@ Supported configurations for this release - CONFIG_NF_CONNTRACK or CONFIG_IP_NF_CONNTRACK - CONFIG_NF_CONNTRACK_MARK or CONFIG_IP_NF_CONNTRACK_MARK enabled =y or as module (=m) + - CONFIG_CONNECTOR y/m if you wish to receive userspace + notifications from pknock through netlink/connector Extra notes: diff --git a/configure.ac b/configure.ac index 8b97a5a..7a5e0e9 100644 --- a/configure.ac +++ b/configure.ac @@ -102,5 +102,5 @@ AC_SUBST([ksourcedir]) AC_SUBST([xtlibdir]) AC_CONFIG_FILES([Makefile Makefile.iptrules Makefile.mans extensions/Makefile extensions/ACCOUNT/Makefile - extensions/ipset/Makefile]) + extensions/ipset/Makefile extensions/pknock/Makefile]) AC_OUTPUT diff --git a/doc/changelog.txt b/doc/changelog.txt index db68ef7..cf67d6c 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -6,6 +6,19 @@ HEAD - ipp2p: try to address underflows - psd: avoid potential crash when dealing with non-linear skbs - merge xt_ACCOUNT userspace utilities +- added reworked xt_pknock module + Changes from pknock v0.5: + - pknock: "strict" and "checkip" flags were not displayed in `iptables -L` + - pknock: the GC expire time's lower bound is now the default gc time + (65000 msec) to avoid rendering anti-spoof protection in SPA mode useless + - pknock: avoid crash on memory allocation failure and fix memleak + - pknock: avoid fillup of peer table during DDoS + - pknock: automatic closing of ports + - pknock: make non-zero time mandatory for TCP mode + - pknock: display only pknock mode and state relevant information in procfs + - pknock: check interknock time only for !ST_ALLOWED peers + - pknock: preserve time/autoclose values for rules added in + reverse/arbitrary order Xtables-addons 1.18 (September 09 2009) diff --git a/extensions/Kbuild b/extensions/Kbuild index ab3dfdd..5588c2c 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -26,6 +26,7 @@ obj-${build_ipset} += ipset/ obj-${build_ipv4options} += xt_ipv4options.o obj-${build_length2} += xt_length2.o obj-${build_lscan} += xt_lscan.o +obj-${build_pknock} += pknock/ obj-${build_psd} += xt_psd.o obj-${build_quota2} += xt_quota2.o diff --git a/extensions/Mbuild b/extensions/Mbuild index 65ae18f..533a703 100644 --- a/extensions/Mbuild +++ b/extensions/Mbuild @@ -21,5 +21,6 @@ obj-${build_ipset} += ipset/ obj-${build_ipv4options} += libxt_ipv4options.so obj-${build_length2} += libxt_length2.so obj-${build_lscan} += libxt_lscan.so +obj-${build_pknock} += pknock/ obj-${build_psd} += libxt_psd.so obj-${build_quota2} += libxt_quota2.so diff --git a/extensions/pknock/Kbuild b/extensions/pknock/Kbuild new file mode 100644 index 0000000..6318ca5 --- /dev/null +++ b/extensions/pknock/Kbuild @@ -0,0 +1,5 @@ +# -*- Makefile -*- + +EXTRA_CFLAGS = -I${src}/.. + +obj-m += xt_pknock.o diff --git a/extensions/pknock/Makefile.am b/extensions/pknock/Makefile.am new file mode 100644 index 0000000..af3f625 --- /dev/null +++ b/extensions/pknock/Makefile.am @@ -0,0 +1,3 @@ +# -*- Makefile -*- + +include ../../Makefile.extra diff --git a/extensions/pknock/Mbuild b/extensions/pknock/Mbuild new file mode 100644 index 0000000..7a46751 --- /dev/null +++ b/extensions/pknock/Mbuild @@ -0,0 +1,3 @@ +# -*- Makefile -*- + +obj-${build_pknock} += libxt_pknock.so diff --git a/extensions/pknock/libxt_pknock.c b/extensions/pknock/libxt_pknock.c new file mode 100644 index 0000000..c6ad809 --- /dev/null +++ b/extensions/pknock/libxt_pknock.c @@ -0,0 +1,343 @@ +/* + * Shared library add-on to iptables to add Port Knocking and SPA matching + * support. + * + * (C) 2006-2009 J. Federico Hernandez + * (C) 2006 Luis Floreani + * + * This program is released under the terms of GNU GPL version 2. + */ +#include +#include +#include +#include + +#include +#include +#include +#include "xt_pknock.h" + +static const struct option pknock_mt_opts[] = { + /* .name, .has_arg, .flag, .val */ + {.name = "knockports", .has_arg = true, .val = 'k'}, + {.name = "time", .has_arg = true, .val = 't'}, + {.name = "autoclose", .has_arg = true, .val = 'a'}, + {.name = "name", .has_arg = true, .val = 'n'}, + {.name = "opensecret", .has_arg = true, .val = 'o'}, + {.name = "closesecret", .has_arg = true, .val = 'z'}, + {.name = "strict", .has_arg = false, .val = 'x'}, + {.name = "checkip", .has_arg = false, .val = 'c'}, + {NULL}, +}; + +static void pknock_mt_help(void) +{ + printf("pknock match options:\n" + " --knockports port[,port,port,...] " + "Matches destination port(s).\n" + " --time seconds\n" + "Max allowed time between knocks.\n" + " --autoclose minutes\n" + "Time after which to automatically close opened\n" + "\t\t\t\t\tport(s).\n" + " --strict " + "Knocks sequence must be exact.\n" + " --name rule_name " + "Rule name.\n" + " --checkip " + "Matches if the source ip is in the list.\n" + ); +} + +static unsigned int +parse_ports(const char *portstring, uint16_t *ports, const char *proto) +{ + char *buffer, *cp, *next; + unsigned int i; + + buffer = strdup(portstring); + if (buffer == NULL) + xtables_error(OTHER_PROBLEM, "strdup failed"); + + for (cp = buffer, i = 0; cp != NULL && i < XT_PKNOCK_MAX_PORTS; cp = next, ++i) + { + next=strchr(cp, ','); + if (next != NULL) + *next++ = '\0'; + ports[i] = xtables_parse_port(cp, proto); + } + + if (cp != NULL) + xtables_error(PARAMETER_PROBLEM, "too many ports specified"); + + free(buffer); + return i; +} + +static char * +proto_to_name(uint8_t proto) +{ + switch (proto) { + case IPPROTO_TCP: + return "tcp"; + case IPPROTO_UDP: + return "udp"; + default: + return NULL; + } +} + +static const char * +check_proto(uint16_t pnum, uint8_t invflags) +{ + char *proto; + + if (invflags & XT_INV_PROTO) + xtables_error(PARAMETER_PROBLEM, PKNOCK "only works with TCP and UDP."); + + if ((proto = proto_to_name(pnum)) != NULL) + return proto; + else if (pnum == 0) + xtables_error(PARAMETER_PROBLEM, PKNOCK "needs `-p tcp' or `-p udp'"); + else + xtables_error(PARAMETER_PROBLEM, PKNOCK "only works with TCP and UDP."); +} + +static int +__pknock_parse(int c, char **argv, int invert, unsigned int *flags, + struct xt_entry_match **match, uint16_t pnum, + uint16_t invflags) +{ + const char *proto; + struct xt_pknock_mtinfo *info = (void *)(*match)->data; + unsigned int tmp; + + switch (c) { + case 'k': /* --knockports */ + if (*flags & XT_PKNOCK_KNOCKPORT) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --knockports twice.\n"); + proto = check_proto(pnum, invflags); + + info->ports_count = parse_ports(optarg, info->port, proto); + info->option |= XT_PKNOCK_KNOCKPORT; + *flags |= XT_PKNOCK_KNOCKPORT; +#if DEBUG + printf("ports_count: %d\n", info->ports_count); +#endif + break; + + case 't': /* --time */ + if (*flags & XT_PKNOCK_TIME) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --time twice.\n"); + info->max_time = atoi(optarg); + if (info->max_time == 0) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "--time number must be > 0.\n"); + info->option |= XT_PKNOCK_TIME; + *flags |= XT_PKNOCK_TIME; + break; + + case 'a': /* --autoclose */ + if (*flags & XT_PKNOCK_AUTOCLOSE) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --autoclose twice.\n"); + if (!xtables_strtoui(optarg, NULL, &tmp, 0, ~0U)) + xtables_param_act(XTF_BAD_VALUE, PKNOCK, + "--autoclose", optarg); + info->autoclose_time = tmp; + info->option |= XT_PKNOCK_AUTOCLOSE; + *flags |= XT_PKNOCK_AUTOCLOSE; + break; + + case 'n': /* --name */ + if (*flags & XT_PKNOCK_NAME) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --name twice.\n"); + memset(info->rule_name, 0, sizeof(info->rule_name)); + strncpy(info->rule_name, optarg, sizeof(info->rule_name) - 1); + + info->rule_name_len = strlen(info->rule_name); + info->option |= XT_PKNOCK_NAME; + *flags |= XT_PKNOCK_NAME; +#if DEBUG + printf("info->rule_name: %s\n", info->rule_name); +#endif + break; + + case 'o': /* --opensecret */ + if (*flags & XT_PKNOCK_OPENSECRET) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --opensecret twice.\n"); + memset(info->open_secret, 0, sizeof(info->open_secret)); + strncpy(info->open_secret, optarg, sizeof(info->open_secret) - 1); + + info->open_secret_len = strlen(info->open_secret); + info->option |= XT_PKNOCK_OPENSECRET; + *flags |= XT_PKNOCK_OPENSECRET; + break; + + case 'z': /* --closesecret */ + if (*flags & XT_PKNOCK_CLOSESECRET) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --closesecret twice.\n"); + memset(info->close_secret, 0, sizeof(info->close_secret)); + strncpy(info->close_secret, optarg, sizeof(info->close_secret) - 1); + + info->close_secret_len = strlen(info->close_secret); + info->option |= XT_PKNOCK_CLOSESECRET; + *flags |= XT_PKNOCK_CLOSESECRET; + break; + + case 'c': /* --checkip */ + if (*flags & XT_PKNOCK_CHECKIP) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --checkip twice.\n"); + info->option |= XT_PKNOCK_CHECKIP; + *flags |= XT_PKNOCK_CHECKIP; + break; + + case 'x': /* --strict */ + if (*flags & XT_PKNOCK_STRICT) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot use --strict twice.\n"); + info->option |= XT_PKNOCK_STRICT; + *flags |= XT_PKNOCK_STRICT; + break; + + default: + return 0; + } + + if (invert) + xtables_error(PARAMETER_PROBLEM, PKNOCK "does not support invert."); + + return 1; +} + +static int pknock_mt_parse(int c, char **argv, int invert, unsigned int *flags, + const void *e, struct xt_entry_match **match) +{ + const struct ipt_entry *entry = e; + return __pknock_parse(c, argv, invert, flags, match, + entry->ip.proto, entry->ip.invflags); +} + +static void pknock_mt_check(unsigned int flags) +{ + if (!(flags & XT_PKNOCK_NAME)) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "--name option is required.\n"); + + if (flags & XT_PKNOCK_KNOCKPORT) { + if (flags & XT_PKNOCK_CHECKIP) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot specify --knockports with --checkip.\n"); + if ((flags & XT_PKNOCK_OPENSECRET) + && !(flags & XT_PKNOCK_CLOSESECRET)) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "--opensecret must go with --closesecret.\n"); + if ((flags & XT_PKNOCK_CLOSESECRET) + && !(flags & XT_PKNOCK_OPENSECRET)) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "--closesecret must go with --opensecret.\n"); + } + + if (flags & XT_PKNOCK_CHECKIP) { + if (flags & XT_PKNOCK_KNOCKPORT) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot specify --checkip with --knockports.\n"); + if ((flags & XT_PKNOCK_OPENSECRET) + || (flags & XT_PKNOCK_CLOSESECRET)) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot specify --opensecret and" + " --closesecret with --checkip.\n"); + if (flags & XT_PKNOCK_TIME) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot specify --time with --checkip.\n"); + if (flags & XT_PKNOCK_AUTOCLOSE) + xtables_error(PARAMETER_PROBLEM, PKNOCK + "cannot specify --autoclose with --checkip.\n"); + } else if (!(flags & (XT_PKNOCK_OPENSECRET | XT_PKNOCK_TIME))) { + xtables_error(PARAMETER_PROBLEM, PKNOCK + "you must specify --time.\n"); + } +} + +static void pknock_mt_print(const void *ip, + const struct xt_entry_match *match, int numeric) +{ + const struct xt_pknock_mtinfo *info = (void *)match->data; + int i; + + printf("pknock "); + if (info->option & XT_PKNOCK_KNOCKPORT) { + printf("knockports "); + for (i = 0; i < info->ports_count; ++i) + printf("%s%d", i ? "," : "", info->port[i]); + printf(" "); + } + if (info->option & XT_PKNOCK_TIME) + printf("time %ld ", (long)info->max_time); + if (info->option & XT_PKNOCK_AUTOCLOSE) + printf("autoclose %lu ", (unsigned long)info->autoclose_time); + if (info->option & XT_PKNOCK_NAME) + printf("name %s ", info->rule_name); + if (info->option & XT_PKNOCK_OPENSECRET) + printf("opensecret "); + if (info->option & XT_PKNOCK_CLOSESECRET) + printf("closesecret "); + if (info->option & XT_PKNOCK_STRICT) + printf("strict "); + if (info->option & XT_PKNOCK_CHECKIP) + printf("checkip "); +} + +static void pknock_mt_save(const void *ip, const struct xt_entry_match *match) +{ + int i; + const struct xt_pknock_mtinfo *info = (void *)match->data; + + if (info->option & XT_PKNOCK_KNOCKPORT) { + printf("--knockports "); + for (i = 0; i < info->ports_count; ++i) + printf("%s%d", i ? "," : "", info->port[i]); + printf(" "); + } + if (info->option & XT_PKNOCK_TIME) + printf("--time %ld ", (long)info->max_time); + if (info->option & XT_PKNOCK_AUTOCLOSE) + printf("--autoclose %lu ", + (unsigned long)info->autoclose_time); + if (info->option & XT_PKNOCK_NAME) + printf("--name %s ", info->rule_name); + if (info->option & XT_PKNOCK_OPENSECRET) + printf("--opensecret "); + if (info->option & XT_PKNOCK_CLOSESECRET) + printf("--closesecret "); + if (info->option & XT_PKNOCK_STRICT) + printf("--strict "); + if (info->option & XT_PKNOCK_CHECKIP) + printf("--checkip "); +} + +static struct xtables_match pknock_mt_reg = { + .name = "pknock", + .version = XTABLES_VERSION, + .revision = 1, + .family = AF_INET, + .size = XT_ALIGN(sizeof(struct xt_pknock_mtinfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_pknock_mtinfo)), + .help = pknock_mt_help, + .parse = pknock_mt_parse, + .final_check = pknock_mt_check, + .print = pknock_mt_print, + .save = pknock_mt_save, + .extra_opts = pknock_mt_opts, +}; + +static __attribute__((constructor)) void pknock_mt_ldr(void) +{ + xtables_register_match(&pknock_mt_reg); +} diff --git a/extensions/pknock/xt_pknock.Kconfig b/extensions/pknock/xt_pknock.Kconfig new file mode 100644 index 0000000..7969c38 --- /dev/null +++ b/extensions/pknock/xt_pknock.Kconfig @@ -0,0 +1,13 @@ +config NETFILTER_XT_MATCH_PKNOCK + tristate "Port knocking match support" + depends on NETFILTER_XTABLES && CONNECTOR + ---help--- + pknock match implements so-called Port Knocking, a stealthy system + for network authentication: client sends packets to selected, closed + ports on target machine in a specific sequence. The target machine + (which has pknock match rule set up) then decides whether to + unblock or block (again) its protected port with listening + service. This can be, for instance, used to avoid brute force attacks + on ssh or ftp services. + + For more informations go to: http://portknocko.berlios.de/ diff --git a/extensions/pknock/xt_pknock.c b/extensions/pknock/xt_pknock.c new file mode 100644 index 0000000..9bfcdf4 --- /dev/null +++ b/extensions/pknock/xt_pknock.c @@ -0,0 +1,1200 @@ +/* + * Kernel module to implement Port Knocking and SPA matching support. + * + * (C) 2006-2009 J. Federico Hernandez Scarso + * (C) 2006 Luis A. Floreani + * + * This program is released under the terms of GNU GPL version 2. + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "xt_pknock.h" +#include "compat_xtables.h" + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19) +# define PK_CRYPTO 1 +#endif + +enum status { + ST_INIT = 1, + ST_MATCHING, + ST_ALLOWED, +}; + +/** + * @timestamp: seconds, but not since epoch (uses jiffies/HZ) + * @login_sec: seconds at login since the epoch + */ +struct peer { + struct list_head head; + __be32 ip; + uint32_t accepted_knock_count; + unsigned long timestamp; + unsigned long login_sec; + enum status status; + uint8_t proto; +}; + +/** + * @timer: garbage collector timer + * @max_time: max matching time between ports + */ +struct xt_pknock_rule { + struct list_head head; + char rule_name[XT_PKNOCK_MAX_BUF_LEN+1]; + int rule_name_len; + unsigned int ref_count; + struct timer_list timer; + struct list_head *peer_head; + struct proc_dir_entry *status_proc; + unsigned long max_time; + unsigned long autoclose_time; +}; + +/** + * @port: destination port + */ +struct transport_data { + uint8_t proto; + uint16_t port; + int payload_len; + const unsigned char *payload; +}; + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("J. Federico Hernandez Scarso, Luis A. Floreani"); +MODULE_DESCRIPTION("netfilter match for Port Knocking and SPA"); +MODULE_ALIAS("ipt_pknock"); + +enum { + DEFAULT_GC_EXPIRATION_TIME = 65000, /* in msecs */ + DEFAULT_RULE_HASH_SIZE = 8, + DEFAULT_PEER_HASH_SIZE = 16, +}; + +#define hashtable_for_each_safe(pos, n, head, size, i) \ + for ((i) = 0; (i) < (size); ++(i)) \ + list_for_each_safe((pos), (n), (&head[(i)])) + +#define pk_debug(msg, peer) pr_debug( \ + "(S) peer: " NIPQUAD_FMT " - %s.\n", \ + NIPQUAD((peer)->ip), msg) + +static uint32_t ipt_pknock_hash_rnd; + +static unsigned int rule_hashsize = DEFAULT_RULE_HASH_SIZE; +static unsigned int peer_hashsize = DEFAULT_PEER_HASH_SIZE; +static unsigned int gc_expir_time = DEFAULT_GC_EXPIRATION_TIME; +static int nl_multicast_group = -1; + +static struct list_head *rule_hashtable; +static struct proc_dir_entry *pde; + +static DEFINE_SPINLOCK(list_lock); + +#ifdef PK_CRYPTO +static struct { + const char *algo; + struct crypto_hash *tfm; + unsigned int size; + struct hash_desc desc; +} crypto = { + .algo = "hmac(sha256)", + .tfm = NULL, + .size = 0 +}; +#endif + +module_param(rule_hashsize, int, S_IRUGO); +MODULE_PARM_DESC(rule_hashsize, "Buckets in rule hash table (default: 8)"); +module_param(peer_hashsize, int, S_IRUGO); +MODULE_PARM_DESC(peer_hashsize, "Buckets in peer hash table (default: 16)"); +module_param(gc_expir_time, int, S_IRUGO); +MODULE_PARM_DESC(gc_expir_time, "Time until garbage collection after valid knock packet (default: 65000 msec)"); +module_param(nl_multicast_group, int, S_IRUGO); +MODULE_PARM_DESC(nl_multicast_group, "Netlink multicast group number for pknock messages"); + +/** + * Calculates a value from 0 to max from a hash of the arguments. + * + * @key + * @len: length + * @initval + * @max + * @return: a 32 bits index + */ +static inline uint32_t +pknock_hash(const void *key, uint32_t len, uint32_t initval, uint32_t max) +{ + return jhash(key, len, initval) % max; +} + +/** + * Alloc a hashtable with n buckets. + * + * @size + * @return: hashtable + */ +static struct list_head * +alloc_hashtable(unsigned int size) +{ + struct list_head *hash; + unsigned int i; + + hash = kmalloc(sizeof(*hash) * size, GFP_ATOMIC); + if (hash == NULL) + return NULL; + for (i = 0; i < size; ++i) + INIT_LIST_HEAD(&hash[i]); + + return hash; +} + +/** + * This function converts the status from integer to string. + * + * @status + * @return: status + */ +static inline const char * +status_itoa(enum status status) +{ + switch (status) { + case ST_INIT: return "INIT"; + case ST_MATCHING: return "MATCHING"; + case ST_ALLOWED: return "ALLOWED"; + default: return "UNKNOWN"; + } +} + +/** + * @s + * @pos + * @return: private value used by the iterator + */ +static void * +pknock_seq_start(struct seq_file *s, loff_t *pos) +{ + const struct proc_dir_entry *pde = s->private; + const struct xt_pknock_rule *rule = pde->data; + + spin_lock_bh(&list_lock); + + if (*pos >= peer_hashsize) + return NULL; + + return rule->peer_head + *pos; +} + +/** + * @s + * @v + * @pos + * @return: next value for the iterator + */ +static void * +pknock_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + const struct proc_dir_entry *pde = s->private; + const struct xt_pknock_rule *rule = pde->data; + + ++*pos; + if (*pos >= peer_hashsize) + return NULL; + + return rule->peer_head + *pos; +} + +/** + * @s + * @v + */ +static void +pknock_seq_stop(struct seq_file *s, void *v) +{ + spin_unlock_bh(&list_lock); +} + +/** + * @s + * @v + * @return: 0 if OK + */ +static int +pknock_seq_show(struct seq_file *s, void *v) +{ + const struct list_head *pos, *n; + const struct peer *peer; + unsigned long time; + const struct list_head *peer_head = v; + + const struct proc_dir_entry *pde = s->private; + const struct xt_pknock_rule *rule = pde->data; + + list_for_each_safe(pos, n, peer_head) { + peer = list_entry(pos, struct peer, head); + + seq_printf(s, "src=" NIPQUAD_FMT " ", NIPQUAD(peer->ip)); + seq_printf(s, "proto=%s ", (peer->proto == IPPROTO_TCP) ? + "TCP" : "UDP"); + seq_printf(s, "status=%s ", status_itoa(peer->status)); + seq_printf(s, "accepted_knock_count=%lu ", + (unsigned long)peer->accepted_knock_count); + if (peer->status == ST_MATCHING) { + time = 0; + if (time_before(jiffies / HZ, peer->timestamp + + rule->max_time)) + time = peer->timestamp + rule->max_time - + jiffies / HZ; + seq_printf(s, "expir_time=%lu [secs] ", time); + } + if (peer->status == ST_ALLOWED && rule->autoclose_time != 0) { + time = 0; + if (time_before(get_seconds(), peer->login_sec + + rule->autoclose_time * 60)) + time = peer->login_sec + + rule->autoclose_time * 60 - + get_seconds(); + seq_printf(s, "autoclose_time=%lu [secs] ", time); + } + seq_printf(s, "\n"); + } + + return 0; +} + +static const struct seq_operations pknock_seq_ops = { + .start = pknock_seq_start, + .next = pknock_seq_next, + .stop = pknock_seq_stop, + .show = pknock_seq_show +}; + +/** + * @inode + * @file + */ +static int +pknock_proc_open(struct inode *inode, struct file *file) +{ + int ret = seq_open(file, &pknock_seq_ops); + if (ret == 0) { + struct seq_file *sf = file->private_data; + sf->private = PDE(inode); + } + return ret; +} + +static const struct file_operations pknock_proc_ops = { + .owner = THIS_MODULE, + .open = pknock_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release +}; + +/** + * It updates the rule timer to execute garbage collector. + * + * @rule + */ +static void update_rule_gc_timer(struct xt_pknock_rule *rule) +{ + if (timer_pending(&rule->timer)) + del_timer(&rule->timer); + + rule->timer.expires = jiffies + msecs_to_jiffies(gc_expir_time); + add_timer(&rule->timer); +} + +/** + * @peer + * @autoclose_time + * + * Returns true if autoclose due, or false if still valid. + */ +static inline bool +autoclose_time_passed(const struct peer *peer, unsigned int autoclose_time) +{ + return peer != NULL && autoclose_time != 0 && time_after(get_seconds(), + peer->login_sec + autoclose_time * 60); +} + +/** + * @peer + * @max_time + * @return: 1 time exceeded, 0 still valid + */ +static inline bool +is_interknock_time_exceeded(const struct peer *peer, unsigned int max_time) +{ + return peer != NULL && time_after(jiffies / HZ, + peer->timestamp + max_time); +} + +/** + * @peer + * @return: 1 has logged, 0 otherwise + */ +static inline bool +has_logged_during_this_minute(const struct peer *peer) +{ + return peer != NULL && peer->login_sec / 60 == get_seconds() / 60; +} + +/** + * Garbage collector. It removes the old entries after tis timers have expired. + * + * @r: rule + */ +static void +peer_gc(unsigned long r) +{ + unsigned int i; + struct xt_pknock_rule *rule = (struct xt_pknock_rule *)r; + struct peer *peer; + struct list_head *pos, *n; + + pr_debug("(S) running %s\n", __func__); + hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) { + peer = list_entry(pos, struct peer, head); + + /* + * Remove any peer whose (inter-knock) max_time + * or autoclose_time passed. + */ + if ((peer->status != ST_ALLOWED && + is_interknock_time_exceeded(peer, rule->max_time)) || + (peer->status == ST_ALLOWED && + autoclose_time_passed(peer, rule->autoclose_time))) + { + pk_debug("GC-DELETED", peer); + list_del(pos); + kfree(peer); + } + } +} + +/** + * Compares length and name equality for the rules. + * + * @info + * @rule + * @return: 0 equals, 1 otherwise + */ +static inline bool +rulecmp(const struct xt_pknock_mtinfo *info, const struct xt_pknock_rule *rule) +{ + if (info->rule_name_len != rule->rule_name_len) + return true; + if (strncmp(info->rule_name, rule->rule_name, info->rule_name_len) != 0) + return true; + return false; +} + +/** + * Search the rule and returns a pointer if it exists. + * + * @info + * @return: rule or NULL + */ +static struct xt_pknock_rule *search_rule(const struct xt_pknock_mtinfo *info) +{ + struct xt_pknock_rule *rule; + struct list_head *pos, *n; + unsigned int hash = pknock_hash(info->rule_name, info->rule_name_len, + ipt_pknock_hash_rnd, rule_hashsize); + + list_for_each_safe(pos, n, &rule_hashtable[hash]) { + rule = list_entry(pos, struct xt_pknock_rule, head); + if (rulecmp(info, rule)) + return rule; + } + return NULL; +} + +/** + * It adds a rule to list only if it doesn't exist. + * + * @info + * @return: 1 success, 0 failure + */ +static bool +add_rule(struct xt_pknock_mtinfo *info) +{ + struct xt_pknock_rule *rule; + struct list_head *pos, *n; + unsigned int hash = pknock_hash(info->rule_name, info->rule_name_len, + ipt_pknock_hash_rnd, rule_hashsize); + + list_for_each_safe(pos, n, &rule_hashtable[hash]) { + rule = list_entry(pos, struct xt_pknock_rule, head); + + if (rulecmp(info, rule)) { + ++rule->ref_count; + + if (info->option & XT_PKNOCK_OPENSECRET) { + rule->max_time = info->max_time; + rule->autoclose_time = info->autoclose_time; + } + + if (info->option & XT_PKNOCK_CHECKIP) { + pr_debug("add_rule() (AC)" + " rule found: %s - " + "ref_count: %d\n", + rule->rule_name, + rule->ref_count); + } + return true; + } + } + + rule = kmalloc(sizeof(*rule), GFP_ATOMIC); + if (rule == NULL) + return false; + + INIT_LIST_HEAD(&rule->head); + + memset(rule->rule_name, 0, sizeof(rule->rule_name)); + strncpy(rule->rule_name, info->rule_name, info->rule_name_len); + rule->rule_name_len = info->rule_name_len; + + rule->ref_count = 1; + rule->max_time = info->max_time; + rule->autoclose_time = info->autoclose_time; + rule->peer_head = alloc_hashtable(peer_hashsize); + if (rule->peer_head == NULL) + goto out; + + init_timer(&rule->timer); + rule->timer.function = peer_gc; + rule->timer.data = (unsigned long)rule; + + rule->status_proc = create_proc_entry(info->rule_name, 0, pde); + if (rule->status_proc == NULL) + goto out; + + rule->status_proc->proc_fops = &pknock_proc_ops; + rule->status_proc->data = rule; + + list_add(&rule->head, &rule_hashtable[hash]); + pr_debug("(A) rule_name: %s - created.\n", rule->rule_name); + return true; + out: + kfree(rule->peer_head); + kfree(rule); + return false; +} + +/** + * It removes a rule only if it exists. + * + * @info + */ +static void +remove_rule(struct xt_pknock_mtinfo *info) +{ + struct xt_pknock_rule *rule = NULL; + struct list_head *pos, *n; + struct peer *peer; + unsigned int i; + int found = 0; + unsigned int hash = pknock_hash(info->rule_name, info->rule_name_len, + ipt_pknock_hash_rnd, rule_hashsize); + + if (list_empty(&rule_hashtable[hash])) return; + + list_for_each_safe(pos, n, &rule_hashtable[hash]) { + rule = list_entry(pos, struct xt_pknock_rule, head); + + if (rulecmp(info, rule)) { + found = 1; + rule->ref_count--; + break; + } + } + if (!found) { + pr_debug("(N) rule not found: %s.\n", info->rule_name); + return; + } + if (rule == NULL || rule->ref_count != 0) + return; + + hashtable_for_each_safe(pos, n, rule->peer_head, peer_hashsize, i) { + peer = list_entry(pos, struct peer, head); + pk_debug("DELETED", peer); + list_del(pos); + kfree(peer); + } + + if (rule->status_proc != NULL) + remove_proc_entry(info->rule_name, pde); + pr_debug("(D) rule deleted: %s.\n", rule->rule_name); + if (timer_pending(&rule->timer)) + del_timer(&rule->timer); + + list_del(&rule->head); + kfree(rule->peer_head); + kfree(rule); +} + +/** + * If peer status exist in the list it returns peer status, if not it returns NULL. + * + * @rule + * @ip + * @return: peer or NULL + */ +static struct peer *get_peer(struct xt_pknock_rule *rule, __be32 ip) +{ + struct peer *peer; + struct list_head *pos, *n; + unsigned int hash; + + hash = pknock_hash(&ip, sizeof(ip), ipt_pknock_hash_rnd, peer_hashsize); + + list_for_each_safe(pos, n, &rule->peer_head[hash]) { + peer = list_entry(pos, struct peer, head); + if (peer->ip == ip) return peer; + } + return NULL; +} + +/** + * Reset the knock sequence status of the peer. + * + * @peer + */ +static void reset_knock_status(struct peer *peer) +{ + peer->accepted_knock_count = 0; + peer->status = ST_INIT; +} + +/** + * It creates a new peer matching status. + * + * @rule + * @ip + * @proto + * @return: peer or NULL + */ +static struct peer *new_peer(__be32 ip, uint8_t proto) +{ + struct peer *peer = kmalloc(sizeof(*peer), GFP_ATOMIC); + + if (peer == NULL) + return NULL; + + INIT_LIST_HEAD(&peer->head); + peer->ip = ip; + peer->proto = proto; + peer->timestamp = jiffies/HZ; + peer->login_sec = 0; + reset_knock_status(peer); + + return peer; +} + +/** + * It adds a new peer matching status to the list. + * + * @peer + * @rule + */ +static void add_peer(struct peer *peer, struct xt_pknock_rule *rule) +{ + unsigned int hash = pknock_hash(&peer->ip, sizeof(peer->ip), + ipt_pknock_hash_rnd, peer_hashsize); + list_add(&peer->head, &rule->peer_head[hash]); +} + +/** + * It removes a peer matching status. + * + * @peer + */ +static void remove_peer(struct peer *peer) +{ + list_del(&peer->head); + if (peer != NULL) + kfree(peer); +} + +/** + * @peer + * @info + * @port + * @return: 1 success, 0 failure + */ +static inline bool +is_first_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info, + uint16_t port) +{ + return peer == NULL && info->port[0] == port; +} + +/** + * @peer + * @info + * @port + * @return: 1 success, 0 failure + */ +static inline bool +is_wrong_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info, + uint16_t port) +{ + return peer != NULL && info->port[peer->accepted_knock_count] != port; +} + +/** + * @peer + * @info + * @return: 1 success, 0 failure + */ +static inline bool +is_last_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info) +{ + return peer != NULL && peer->accepted_knock_count == info->ports_count; +} + +/** + * @peer + * @return: 1 success, 0 failure + */ +static inline bool +is_allowed(const struct peer *peer) +{ + return peer != NULL && peer->status == ST_ALLOWED; +} + +/** + * Sends a message to user space through netlink sockets. + * + * @info + * @peer + * @return: 1 success, 0 otherwise + */ +static bool +msg_to_userspace_nl(const struct xt_pknock_mtinfo *info, + const struct peer *peer, int multicast_group) +{ +#if defined(CONFIG_CONNECTOR) || defined(CONFIG_CONNECTOR_MODULE) + struct cn_msg *m; + struct xt_pknock_nl_msg msg; + + m = kmalloc(sizeof(*m) + sizeof(msg), GFP_ATOMIC); + if (m == NULL) + return false; + + memset(m, 0, sizeof(*m) + sizeof(msg)); + m->seq = 0; + m->len = sizeof(msg); + + msg.peer_ip = peer->ip; + scnprintf(msg.rule_name, info->rule_name_len + 1, info->rule_name); + + memcpy(m + 1, &msg, m->len); + + cn_netlink_send(m, multicast_group, GFP_ATOMIC); + + kfree(m); +#endif + return true; +} + +#ifdef PK_CRYPTO +/** + * Transforms a sequence of characters to hexadecimal. + * + * @out: the hexadecimal result + * @crypt: the original sequence + * @size + */ +static void +crypt_to_hex(char *out, const char *crypt, unsigned int size) +{ + unsigned int i; + for (i = 0; i < size; ++i) { + unsigned char c = crypt[i]; + *out++ = '0' + ((c&0xf0)>>4) + (c>=0xa0)*('a'-'9'-1); + *out++ = '0' + (c&0x0f) + ((c&0x0f)>=0x0a)*('a'-'9'-1); + } +} + +/** + * Checks that the payload has the hmac(secret+ipsrc+epoch_min). + * + * @secret + * @secret_len + * @ipsrc + * @payload + * @payload_len + * @return: 1 success, 0 failure + */ +static bool +has_secret(const unsigned char *secret, unsigned int secret_len, uint32_t ipsrc, + const unsigned char *payload, unsigned int payload_len) +{ + struct scatterlist sg[2]; + char result[64]; // 64 bytes * 8 = 512 bits + char *hexresult; + unsigned int hexa_size; + int ret; + bool fret = false; + unsigned int epoch_min; + + if (payload_len == 0) + return false; + + /* + * hexa: 4bits + * ascii: 8bits + * hexa = ascii * 2 + */ + hexa_size = crypto.size * 2; + + /* + 1 cause we MUST add NULL in the payload */ + if (payload_len != hexa_size + 1) + return false; + + hexresult = kmalloc(hexa_size, GFP_ATOMIC); + if (hexresult == NULL) + return false; + + memset(result, 0, sizeof(result)); + memset(hexresult, 0, hexa_size); + + epoch_min = get_seconds() / 60; + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 24) + sg_init_table(sg, ARRAY_SIZE(sg)); +#endif + sg_set_buf(&sg[0], &ipsrc, sizeof(ipsrc)); + sg_set_buf(&sg[1], &epoch_min, sizeof(epoch_min)); + + ret = crypto_hash_setkey(crypto.tfm, secret, secret_len); + if (ret != 0) { + printk("crypto_hash_setkey() failed ret=%d\n", ret); + goto out; + } + + /* + * The third parameter is the number of bytes INSIDE the sg! + * 4 bytes IP (32 bits) + + * 4 bytes int epoch_min (32 bits) + */ + ret = crypto_hash_digest(&crypto.desc, sg, + sizeof(ipsrc) + sizeof(epoch_min), result); + if (ret != 0) { + printk("crypto_hash_digest() failed ret=%d\n", ret); + goto out; + } + + crypt_to_hex(hexresult, result, crypto.size); + + if (memcmp(hexresult, payload, hexa_size) != 0) + pr_debug("secret match failed\n"); + else + fret = true; + + out: + kfree(hexresult); + return fret; +} + +/** + * If the peer pass the security policy. + * + * @peer + * @info + * @payload + * @payload_len + * @return: 1 if pass security, 0 otherwise + */ +static bool +pass_security(struct peer *peer, const struct xt_pknock_mtinfo *info, + const unsigned char *payload, unsigned int payload_len) +{ + if (is_allowed(peer)) + return true; + + /* The peer can't log more than once during the same minute. */ + if (has_logged_during_this_minute(peer)) { + pk_debug("DENIED (anti-spoof protection)", peer); + return false; + } + /* Check for OPEN secret */ + if (!has_secret(info->open_secret, + info->open_secret_len, peer->ip, + payload, payload_len)) + return false; + + return true; +} +#endif /* PK_CRYPTO */ + +/** + * Validates the peer and updates the peer status for an initiating or + * in-sequence knock packet. + * + * @peer + * @info + * @rule + * @hdr + * + * Returns true if allowed, false otherwise. + */ +static bool +update_peer(struct peer *peer, const struct xt_pknock_mtinfo *info, + struct xt_pknock_rule *rule, + const struct transport_data *hdr) +{ + unsigned long time; + + if (is_wrong_knock(peer, info, hdr->port)) { + pk_debug("DIDN'T MATCH", peer); + /* Peer must start the sequence from scratch. */ + if (info->option & XT_PKNOCK_STRICT) + remove_peer(peer); + + return false; + } + + /* If security is needed. */ + if (info->option & XT_PKNOCK_OPENSECRET ) { + if (hdr->proto != IPPROTO_UDP) + return false; + + if (!pass_security(peer, info, hdr->payload, hdr->payload_len)) + return false; + } + + /* Update the gc timer when there is a state change. */ + update_rule_gc_timer(rule); + + ++peer->accepted_knock_count; + + if (is_last_knock(peer, info)) { + peer->status = ST_ALLOWED; + + pk_debug("ALLOWED", peer); + peer->login_sec = get_seconds(); + + if (nl_multicast_group > 0) + msg_to_userspace_nl(info, peer, nl_multicast_group); + + return true; + } + + /* Immediate control over the maximum time between knocks. */ + if (info->option & XT_PKNOCK_TIME) { + time = jiffies/HZ; + + if (is_interknock_time_exceeded(peer, info->max_time)) { + pk_debug("ST_MATCHING knock received after interknock " + "time passed => destroyed", peer); + pr_debug("max_time: %ld - time: %ld\n", + peer->timestamp + info->max_time, + time); + remove_peer(peer); + return false; + } + peer->timestamp = time; + } + pk_debug("MATCHING", peer); + peer->status = ST_MATCHING; + return false; +} + +#ifdef PK_CRYPTO +/** + * Make the peer no more ALLOWED sending a payload with a special secret for + * closure. + * + * @peer + * @info + * @payload + * @payload_len + * @return: 1 if close knock, 0 otherwise + */ +static bool +is_close_knock(const struct peer *peer, const struct xt_pknock_mtinfo *info, + const unsigned char *payload, unsigned int payload_len) +{ + /* Check for CLOSE secret. */ + if (has_secret(info->close_secret, + info->close_secret_len, peer->ip, + payload, payload_len)) + { + pk_debug("BLOCKED", peer); + return true; + } + return false; +} +#endif /* PK_CRYPTO */ + +static bool pknock_mt(const struct sk_buff *skb, + const struct xt_match_param *par) +{ + const struct xt_pknock_mtinfo *info = par->matchinfo; + struct xt_pknock_rule *rule; + struct peer *peer; + const struct iphdr *iph = ip_hdr(skb); + unsigned int hdr_len = 0; + __be16 _ports[2]; + const __be16 *pptr; + struct transport_data hdr = {0, 0, 0, NULL}; + bool ret = false; + + pptr = skb_header_pointer(skb, par->thoff, sizeof _ports, &_ports); + if (pptr == NULL) { + /* We've been asked to examine this packet, and we + * can't. Hence, no choice but to drop. + */ + *par->hotdrop = true; + return false; + } + + hdr.port = ntohs(pptr[1]); + hdr.proto = iph->protocol; + + switch (hdr.proto) { + case IPPROTO_TCP: + break; + + case IPPROTO_UDP: +#ifdef PK_CRYPTO + hdr_len = (iph->ihl * 4) + sizeof(struct udphdr); + break; +#else + pr_debug("UDP protocol not supported\n"); + return false; +#endif + + default: + pr_debug("IP payload protocol is neither tcp nor udp.\n"); + return false; + } + + spin_lock_bh(&list_lock); + + /* Searches a rule from the list depending on info structure options. */ + rule = search_rule(info); + if (rule == NULL) { + printk(KERN_INFO PKNOCK "The rule %s doesn't exist.\n", + info->rule_name); + goto out; + } + + /* Gives the peer matching status added to rule depending on ip src. */ + peer = get_peer(rule, iph->saddr); + + if (info->option & XT_PKNOCK_CHECKIP) { + ret = is_allowed(peer); + goto out; + } + + if (iph->protocol == IPPROTO_UDP) { + hdr.payload = (void *)iph + hdr_len; + hdr.payload_len = skb->len - hdr_len; + } + + /* Sets, updates, removes or checks the peer matching status. */ + if (info->option & XT_PKNOCK_KNOCKPORT) { + if ((ret = is_allowed(peer))) { + if (info->option & XT_PKNOCK_CLOSESECRET && + iph->protocol == IPPROTO_UDP) + { + if (is_close_knock(peer, info, hdr.payload, hdr.payload_len)) + { + reset_knock_status(peer); + ret = false; + } + } + goto out; + } + + if (is_first_knock(peer, info, hdr.port)) { + peer = new_peer(iph->saddr, iph->protocol); + add_peer(peer, rule); + } + + if (peer == NULL) goto out; + + update_peer(peer, info, rule, &hdr); + } + +out: + /* Handle cur.peer matching and deletion after autoclose_time passed */ + if (ret && autoclose_time_passed(peer, rule->autoclose_time)) { + pk_debug("AUTOCLOSE TIME PASSED => BLOCKED", peer); + ret = false; + if (iph->protocol == IPPROTO_TCP || + !has_logged_during_this_minute(peer)) + remove_peer(peer); + } + + if (ret) + pk_debug("PASS OK", peer); + spin_unlock_bh(&list_lock); + return ret; +} + +#define RETURN_ERR(err) do { printk(KERN_ERR PKNOCK err); return false; } while (false) + +static bool pknock_mt_check(const struct xt_mtchk_param *par) +{ + struct xt_pknock_mtinfo *info = par->matchinfo; + + /* Singleton. */ + if (rule_hashtable == NULL) { + rule_hashtable = alloc_hashtable(rule_hashsize); + if (rule_hashtable == NULL) + RETURN_ERR("alloc_hashtable() error in checkentry()\n"); + + get_random_bytes(&ipt_pknock_hash_rnd, sizeof (ipt_pknock_hash_rnd)); + } + + if (!(info->option & XT_PKNOCK_NAME)) + RETURN_ERR("You must specify --name option.\n"); + +#ifndef PK_CRYPTO + if (info->option & (XT_PKNOCK_OPENSECRET | XT_PKNOCK_CLOSESECRET)) + RETURN_ERR("No crypto support available; " + "cannot use opensecret/closescret\n"); +#endif + if ((info->option & XT_PKNOCK_OPENSECRET) && (info->ports_count != 1)) + RETURN_ERR("--opensecret must have just one knock port\n"); + if (info->option & XT_PKNOCK_KNOCKPORT) { + if (info->option & XT_PKNOCK_CHECKIP) + RETURN_ERR("Can't specify --knockports with --checkip.\n"); + if ((info->option & XT_PKNOCK_OPENSECRET) && + !(info->option & XT_PKNOCK_CLOSESECRET)) + RETURN_ERR("--opensecret must go with --closesecret.\n"); + if ((info->option & XT_PKNOCK_CLOSESECRET) && + !(info->option & XT_PKNOCK_OPENSECRET)) + RETURN_ERR("--closesecret must go with --opensecret.\n"); + } + + if (info->option & XT_PKNOCK_CHECKIP) { + if (info->option & XT_PKNOCK_KNOCKPORT) + RETURN_ERR("Can't specify --checkip with --knockports.\n"); + if ((info->option & XT_PKNOCK_OPENSECRET) || + (info->option & XT_PKNOCK_CLOSESECRET)) + RETURN_ERR("Can't specify --opensecret and --closesecret" + " with --checkip.\n"); + if (info->option & XT_PKNOCK_TIME) + RETURN_ERR("Can't specify --time with --checkip.\n"); + if (info->option & XT_PKNOCK_AUTOCLOSE) + RETURN_ERR("Can't specify --autoclose with --checkip.\n"); + } else if (!(info->option & (XT_PKNOCK_OPENSECRET | XT_PKNOCK_TIME))) { + RETURN_ERR("you must specify --time.\n"); + } + + if (info->option & XT_PKNOCK_OPENSECRET) { + if (info->open_secret_len == info->close_secret_len) { + if (memcmp(info->open_secret, info->close_secret, + info->open_secret_len) == 0) + RETURN_ERR("opensecret & closesecret cannot be equal.\n"); + } + } + + if (!add_rule(info)) + RETURN_ERR("add_rule() error in checkentry() function.\n"); + + return true; +} + +static void pknock_mt_destroy(const struct xt_mtdtor_param *par) +{ + struct xt_pknock_mtinfo *info = par->matchinfo; + /* Removes a rule only if it exits and ref_count is equal to 0. */ + remove_rule(info); +} + +static struct xt_match xt_pknock_mt_reg __read_mostly = { + .name = "pknock", + .revision = 1, + .family = NFPROTO_IPV4, + .matchsize = sizeof(struct xt_pknock_mtinfo), + .match = pknock_mt, + .checkentry = pknock_mt_check, + .destroy = pknock_mt_destroy, + .me = THIS_MODULE +}; + +static int __init xt_pknock_mt_init(void) +{ +#if !defined(CONFIG_CONNECTOR) && !defined(CONFIG_CONNECTOR_MODULE) + if (nl_multicast_group != -1) + pr_info("CONFIG_CONNECTOR not present; " + "netlink messages disabled\n"); +#endif + + if (gc_expir_time < DEFAULT_GC_EXPIRATION_TIME) + gc_expir_time = DEFAULT_GC_EXPIRATION_TIME; +#ifdef PK_CRYPTO + if (request_module(crypto.algo) < 0) { + printk(KERN_ERR PKNOCK "request_module('%s') error.\n", + crypto.algo); + return -ENXIO; + } + + crypto.tfm = crypto_alloc_hash(crypto.algo, 0, CRYPTO_ALG_ASYNC); + + if (crypto.tfm == NULL) { + printk(KERN_ERR PKNOCK "failed to load transform for %s\n", + crypto.algo); + return -ENXIO; + } + + crypto.size = crypto_hash_digestsize(crypto.tfm); + crypto.desc.tfm = crypto.tfm; + crypto.desc.flags = 0; +#else + pr_info("No crypto support for < 2.6.19\n"); +#endif + + pde = proc_mkdir("xt_pknock", init_net__proc_net); + if (pde == NULL) { + printk(KERN_ERR PKNOCK "proc_mkdir() error in _init().\n"); + return -ENXIO; + } + return xt_register_match(&xt_pknock_mt_reg); +} + +static void __exit xt_pknock_mt_exit(void) +{ + remove_proc_entry("xt_pknock", init_net__proc_net); + xt_unregister_match(&xt_pknock_mt_reg); + kfree(rule_hashtable); + +#ifdef PK_CRYPTO + if (crypto.tfm != NULL) crypto_free_hash(crypto.tfm); +#endif +} + +module_init(xt_pknock_mt_init); +module_exit(xt_pknock_mt_exit); diff --git a/extensions/pknock/xt_pknock.h b/extensions/pknock/xt_pknock.h new file mode 100644 index 0000000..d44905b --- /dev/null +++ b/extensions/pknock/xt_pknock.h @@ -0,0 +1,53 @@ +/* + * Kernel module to implement Port Knocking and SPA matching support. + * + * (C) 2006-2008 J. Federico Hernandez + * (C) 2006 Luis Floreani + * + * $Id$ + * + * This program is released under the terms of GNU GPL version 2. + */ +#ifndef _XT_PKNOCK_H +#define _XT_PKNOCK_H + +#define PKNOCK "xt_pknock: " + +enum { + XT_PKNOCK_KNOCKPORT = 1 << 0, + XT_PKNOCK_TIME = 1 << 1, + XT_PKNOCK_NAME = 1 << 2, + XT_PKNOCK_STRICT = 1 << 3, + XT_PKNOCK_CHECKIP = 1 << 4, + XT_PKNOCK_OPENSECRET = 1 << 5, + XT_PKNOCK_CLOSESECRET = 1 << 6, + XT_PKNOCK_AUTOCLOSE = 1 << 7, + + /* Can never change these, as they are make up the user protocol. */ + XT_PKNOCK_MAX_PORTS = 15, + XT_PKNOCK_MAX_BUF_LEN = 31, + XT_PKNOCK_MAX_PASSWD_LEN = 31, +}; + +#define DEBUG 1 + +struct xt_pknock_mtinfo { + char rule_name[XT_PKNOCK_MAX_BUF_LEN+1]; + uint32_t rule_name_len; + char open_secret[XT_PKNOCK_MAX_PASSWD_LEN+1]; + uint32_t open_secret_len; + char close_secret[XT_PKNOCK_MAX_PASSWD_LEN+1]; + uint32_t close_secret_len; + uint8_t option; /* --time, --knock-port, ... */ + uint8_t ports_count; /* number of ports */ + uint16_t port[XT_PKNOCK_MAX_PORTS]; /* port[,port,port,...] */ + uint32_t max_time; /* max matching time between ports */ + uint32_t autoclose_time; +}; + +struct xt_pknock_nl_msg { + char rule_name[XT_PKNOCK_MAX_BUF_LEN+1]; + __be32 peer_ip; +}; + +#endif /* _XT_PKNOCK_H */ diff --git a/mconfig b/mconfig index cbeaa69..b10731b 100644 --- a/mconfig +++ b/mconfig @@ -21,5 +21,6 @@ build_ipset=m build_ipv4options=m build_length2=m build_lscan=m +build_pknock=m build_psd=m build_quota2=m