/*
* 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 && tmp + 1 && 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;
op = 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';
cnt = snprintf(obuf, sizeof(obuf), "%s,", pbeg);
pbeg = pend + 1;
}
pend = xt_osf_strchr(pbeg, OSFPDEL);
if (pend) {
*pend = '\0';
if (pbeg[0] == '@' || pbeg[0] == '*')
cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg + 1);
else
cnt = snprintf(f.genre, sizeof(f.genre), "%s", pbeg);
pbeg = pend + 1;
}
pend = xt_osf_strchr(pbeg, OSFPDEL);
if (pend) {
*pend = '\0';
cnt = snprintf(f.version, sizeof(f.version), "%s", pbeg);
pbeg = pend + 1;
}
pend = xt_osf_strchr(pbeg, OSFPDEL);
if (pend) {
*pend = '\0';
cnt =
snprintf(f.subtype, sizeof(f.subtype), "%s", pbeg);
pbeg = pend + 1;
}
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_REQUEST);
else
nfnl_fill_hdr(nfnlssh, nmh, 0, AF_UNSPEC, 0, OSF_MSG_ADD, NLM_F_REQUEST | NLM_F_CREATE);
nfnl_addattr_l(nmh, sizeof(buf), OSF_ATTR_FINGER, &f, sizeof(struct xt_osf_user_finger));
return nfnl_talk(nfnlh, nmh, 0, 0, NULL, NULL, NULL);
}
static int osf_load_entries(char *path, int del)
{
FILE *inf;
int err = 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;
if (buf[0] == '#' || buf[0] == '\n' || buf[0] == '\r')
continue;
len = strlen(buf) - 1;
if (len <= 0)
continue;
buf[len] = '\0';
err = osf_load_line(buf, len, del);
if (err)
break;
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 <del rules> -h\n",
argv[0]);
return -1;
}
}
if (!fingerprints) {
err = -ENOENT;
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;
}