| /* |
| * Copyright (c) 2005 Evgeniy Polyakov <johnpol@2ka.mxt.ru> |
| * |
| * |
| * 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. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/poll.h> |
| #include <sys/time.h> |
| |
| #include <arpa/inet.h> |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include <netinet/ip.h> |
| #include <netinet/tcp.h> |
| |
| #include <linux/connector.h> |
| #include <linux/types.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <linux/unistd.h> |
| |
| #include <libnfnetlink/libnfnetlink.h> |
| |
| #include <linux/netfilter/nfnetlink.h> |
| #include <linux/netfilter/xt_osf.h> |
| |
| #define OPTDEL ',' |
| #define OSFPDEL ':' |
| #define MAXOPTSTRLEN 128 |
| |
| #ifndef NIPQUAD |
| #define NIPQUAD(addr) \ |
| ((unsigned char *)&addr)[0], \ |
| ((unsigned char *)&addr)[1], \ |
| ((unsigned char *)&addr)[2], \ |
| ((unsigned char *)&addr)[3] |
| #endif |
| |
| static struct nfnl_handle *nfnlh; |
| static struct nfnl_subsys_handle *nfnlssh; |
| |
| static struct xt_osf_opt IANA_opts[] = { |
| { .kind = 0, .length = 1,}, |
| { .kind=1, .length=1,}, |
| { .kind=2, .length=4,}, |
| { .kind=3, .length=3,}, |
| { .kind=4, .length=2,}, |
| { .kind=5, .length=1,}, /* SACK length is not defined */ |
| { .kind=6, .length=6,}, |
| { .kind=7, .length=6,}, |
| { .kind=8, .length=10,}, |
| { .kind=9, .length=2,}, |
| { .kind=10, .length=3,}, |
| { .kind=11, .length=1,}, /* CC: Suppose 1 */ |
| { .kind=12, .length=1,}, /* the same */ |
| { .kind=13, .length=1,}, /* and here too */ |
| { .kind=14, .length=3,}, |
| { .kind=15, .length=1,}, /* TCP Alternate Checksum Data. Length is not defined */ |
| { .kind=16, .length=1,}, |
| { .kind=17, .length=1,}, |
| { .kind=18, .length=3,}, |
| { .kind=19, .length=18,}, |
| { .kind=20, .length=1,}, |
| { .kind=21, .length=1,}, |
| { .kind=22, .length=1,}, |
| { .kind=23, .length=1,}, |
| { .kind=24, .length=1,}, |
| { .kind=25, .length=1,}, |
| { .kind=26, .length=1,}, |
| }; |
| |
| static FILE *osf_log_stream; |
| |
| static void uloga(const char *f, ...) |
| { |
| va_list ap; |
| |
| if (!osf_log_stream) |
| osf_log_stream = stdout; |
| |
| va_start(ap, f); |
| vfprintf(osf_log_stream, f, ap); |
| va_end(ap); |
| |
| fflush(osf_log_stream); |
| } |
| |
| static void ulog(const char *f, ...) |
| { |
| char str[64]; |
| struct tm tm; |
| struct timeval tv; |
| va_list ap; |
| |
| if (!osf_log_stream) |
| osf_log_stream = stdout; |
| |
| gettimeofday(&tv, NULL); |
| localtime_r((time_t *)&tv.tv_sec, &tm); |
| strftime(str, sizeof(str), "%F %R:%S", &tm); |
| |
| fprintf(osf_log_stream, "%s.%lu %ld ", str, tv.tv_usec, syscall(__NR_gettid)); |
| |
| va_start(ap, f); |
| vfprintf(osf_log_stream, f, ap); |
| va_end(ap); |
| |
| fflush(osf_log_stream); |
| } |
| |
| #define ulog_err(f, a...) uloga(f ": %s [%d].\n", ##a, strerror(errno), errno) |
| |
| static char *xt_osf_strchr(char *ptr, char c) |
| { |
| char *tmp; |
| |
| tmp = strchr(ptr, c); |
| if (tmp) |
| *tmp = '\0'; |
| |
| while (tmp && isspace(*(tmp + 1))) |
| tmp++; |
| |
| return tmp; |
| } |
| |
| static void xt_osf_parse_opt(struct xt_osf_opt *opt, __u16 *optnum, char *obuf, int olen) |
| { |
| int i, op; |
| char *ptr, wc; |
| unsigned long val; |
| |
| ptr = &obuf[0]; |
| i = 0; |
| while (ptr != NULL && i < olen && *ptr != 0) { |
| val = 0; |
| wc = OSF_WSS_PLAIN; |
| switch (obuf[i]) { |
| case 'N': |
| op = OSFOPT_NOP; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| *ptr = '\0'; |
| ptr++; |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| case 'S': |
| op = OSFOPT_SACKP; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| *ptr = '\0'; |
| ptr++; |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| case 'T': |
| op = OSFOPT_TS; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| *ptr = '\0'; |
| ptr++; |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| case 'W': |
| op = OSFOPT_WSO; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| switch (obuf[i + 1]) { |
| case '%': |
| wc = OSF_WSS_MODULO; |
| break; |
| case 'S': |
| wc = OSF_WSS_MSS; |
| break; |
| case 'T': |
| wc = OSF_WSS_MTU; |
| break; |
| default: |
| wc = OSF_WSS_PLAIN; |
| break; |
| } |
| |
| *ptr = '\0'; |
| ptr++; |
| if (wc) |
| val = strtoul(&obuf[i + 2], NULL, 10); |
| else |
| val = strtoul(&obuf[i + 1], NULL, 10); |
| i += (int)(ptr - &obuf[i]); |
| |
| } else |
| i++; |
| break; |
| case 'M': |
| op = OSFOPT_MSS; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| if (obuf[i + 1] == '%') |
| wc = OSF_WSS_MODULO; |
| *ptr = '\0'; |
| ptr++; |
| if (wc) |
| val = strtoul(&obuf[i + 2], NULL, 10); |
| else |
| val = strtoul(&obuf[i + 1], NULL, 10); |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| case 'E': |
| op = OSFOPT_EOL; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| *ptr = '\0'; |
| ptr++; |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| default: |
| op = OSFOPT_EMPTY; |
| ptr = xt_osf_strchr(&obuf[i], OPTDEL); |
| if (ptr) { |
| ptr++; |
| i += (int)(ptr - &obuf[i]); |
| } else |
| i++; |
| break; |
| } |
| |
| if (op != OSFOPT_EMPTY) { |
| opt[*optnum].kind = IANA_opts[op].kind; |
| opt[*optnum].length = IANA_opts[op].length; |
| opt[*optnum].wc.wc = wc; |
| opt[*optnum].wc.val = val; |
| (*optnum)++; |
| } |
| } |
| } |
| |
| static int osf_load_line(char *buffer, int len, int del) |
| { |
| int i, cnt = 0; |
| char obuf[MAXOPTSTRLEN]; |
| struct xt_osf_user_finger f; |
| char *pbeg, *pend; |
| char buf[NFNL_HEADER_LEN + NFA_LENGTH(sizeof(struct xt_osf_user_finger))]; |
| struct nlmsghdr *nmh = (struct nlmsghdr *) buf; |
| |
| memset(&f, 0, sizeof(struct xt_osf_user_finger)); |
| |
| ulog("Loading '%s'.\n", buffer); |
| |
| for (i = 0; i < len && buffer[i] != '\0'; ++i) { |
| if (buffer[i] == ':') |
| cnt++; |
| } |
| |
| if (cnt != 8) { |
| ulog("Wrong input line '%s': cnt: %d, must be 8, i: %d, must be %d.\n", buffer, cnt, i, len); |
| return -EINVAL; |
| } |
| |
| memset(obuf, 0, sizeof(obuf)); |
| |
| pbeg = buffer; |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| if (pbeg[0] == 'S') { |
| f.wss.wc = OSF_WSS_MSS; |
| if (pbeg[1] == '%') |
| f.wss.val = strtoul(&pbeg[2], NULL, 10); |
| else if (pbeg[1] == '*') |
| f.wss.val = 0; |
| else |
| f.wss.val = strtoul(&pbeg[1], NULL, 10); |
| } else if (pbeg[0] == 'T') { |
| f.wss.wc = OSF_WSS_MTU; |
| if (pbeg[1] == '%') |
| f.wss.val = strtoul(&pbeg[2], NULL, 10); |
| else if (pbeg[1] == '*') |
| f.wss.val = 0; |
| else |
| f.wss.val = strtoul(&pbeg[1], NULL, 10); |
| } else if (pbeg[0] == '%') { |
| f.wss.wc = OSF_WSS_MODULO; |
| f.wss.val = strtoul(&pbeg[1], NULL, 10); |
| } else if (isdigit(pbeg[0])) { |
| f.wss.wc = OSF_WSS_PLAIN; |
| f.wss.val = strtoul(&pbeg[0], NULL, 10); |
| } |
| |
| pbeg = pend + 1; |
| } |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| f.ttl = strtoul(pbeg, NULL, 10); |
| pbeg = pend + 1; |
| } |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| f.df = strtoul(pbeg, NULL, 10); |
| pbeg = pend + 1; |
| } |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| f.ss = strtoul(pbeg, NULL, 10); |
| pbeg = pend + 1; |
| } |
| |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| i = sizeof(obuf); |
| snprintf(obuf, i, "%.*s,", i - 2, pbeg); |
| pbeg = pend + 1; |
| } |
| |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| i = sizeof(f.genre); |
| if (pbeg[0] == '@' || pbeg[0] == '*') |
| pbeg++; |
| snprintf(f.genre, i, "%.*s", i - 1, pbeg); |
| pbeg = pend + 1; |
| } |
| |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| i = sizeof(f.version); |
| snprintf(f.version, i, "%.*s", i - 1, pbeg); |
| pbeg = pend + 1; |
| } |
| |
| pend = xt_osf_strchr(pbeg, OSFPDEL); |
| if (pend) { |
| *pend = '\0'; |
| i = sizeof(f.subtype); |
| snprintf(f.subtype, i, "%.*s", i - 1, pbeg); |
| } |
| |
| xt_osf_parse_opt(f.opt, &f.opt_num, obuf, sizeof(obuf)); |
| |
| memset(buf, 0, sizeof(buf)); |
| |
| if (del) |
| nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_REMOVE, |
| NLM_F_ACK | NLM_F_REQUEST); |
| else |
| nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_ADD, |
| NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE); |
| |
| nfnl_addattr_l(nmh, sizeof(buf), OSF_ATTR_FINGER, &f, sizeof(struct xt_osf_user_finger)); |
| |
| return nfnl_query(nfnlh, nmh); |
| } |
| |
| static int osf_load_entries(char *path, int del) |
| { |
| FILE *inf; |
| int err = 0, lineno = 0; |
| char buf[1024]; |
| |
| inf = fopen(path, "r"); |
| if (!inf) { |
| ulog_err("Failed to open file '%s'", path); |
| return -1; |
| } |
| |
| while(fgets(buf, sizeof(buf), inf)) { |
| int len, rc; |
| |
| lineno++; |
| |
| if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r') |
| continue; |
| |
| len = strlen(buf) - 1; |
| |
| if (len <= 0) |
| continue; |
| |
| buf[len] = '\0'; |
| |
| rc = osf_load_line(buf, len, del); |
| if (rc && (!del || errno != ENOENT)) { |
| ulog_err("Failed to load line %d", lineno); |
| err = rc; |
| } |
| |
| memset(buf, 0, sizeof(buf)); |
| } |
| |
| fclose(inf); |
| return err; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int ch, del = 0, err; |
| char *fingerprints = NULL; |
| |
| while ((ch = getopt(argc, argv, "f:dh")) != -1) { |
| switch (ch) { |
| case 'f': |
| fingerprints = optarg; |
| break; |
| case 'd': |
| del = 1; |
| break; |
| default: |
| fprintf(stderr, |
| "Usage: %s -f fingerprints [-d]\n", |
| argv[0]); |
| return -1; |
| } |
| } |
| |
| if (!fingerprints) { |
| err = -ENOENT; |
| ulog("Missing fingerprints file argument.\n"); |
| goto err_out_exit; |
| } |
| |
| nfnlh = nfnl_open(); |
| if (!nfnlh) { |
| err = -EINVAL; |
| ulog_err("Failed to create nfnl handler"); |
| goto err_out_exit; |
| } |
| |
| #ifndef NFNL_SUBSYS_OSF |
| #define NFNL_SUBSYS_OSF 5 |
| #endif |
| |
| nfnlssh = nfnl_subsys_open(nfnlh, NFNL_SUBSYS_OSF, OSF_MSG_MAX, 0); |
| if (!nfnlssh) { |
| err = -EINVAL; |
| ulog_err("Faied to create nfnl subsystem"); |
| goto err_out_close; |
| } |
| |
| err = osf_load_entries(fingerprints, del); |
| if (err) |
| goto err_out_close_subsys; |
| |
| nfnl_subsys_close(nfnlssh); |
| nfnl_close(nfnlh); |
| |
| return 0; |
| |
| err_out_close_subsys: |
| nfnl_subsys_close(nfnlssh); |
| err_out_close: |
| nfnl_close(nfnlh); |
| err_out_exit: |
| return err; |
| } |