574 lines
14 KiB
C
574 lines
14 KiB
C
|
/*
|
||
|
* Userland functions missing in linux
|
||
|
* taken from /usr/src/lib/libc/stdtime/time32.c
|
||
|
*/
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h> /* sockaddr_in */
|
||
|
#include <netinet/tcp.h> /* TCP_NODELAY */
|
||
|
#include <sys/uio.h>
|
||
|
#include <unistd.h> /* uint* types */
|
||
|
#include <errno.h>
|
||
|
#include <string.h> /* bzero */
|
||
|
#include <arpa/inet.h> /* htonl */
|
||
|
|
||
|
#ifndef HAVE_NAT
|
||
|
/* dummy nat functions */
|
||
|
void
|
||
|
ipfw_show_nat(int ac, char **av)
|
||
|
{
|
||
|
D("unsupported");
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ipfw_config_nat(int ac, char **av)
|
||
|
{
|
||
|
D("unsupported");
|
||
|
}
|
||
|
#endif /* HAVE_NAT */
|
||
|
|
||
|
#ifdef NEED_STRTONUM
|
||
|
/* missing in linux and windows */
|
||
|
long long int
|
||
|
strtonum(const char *nptr, long long minval, long long maxval,
|
||
|
const char **errstr)
|
||
|
{
|
||
|
long long ret;
|
||
|
int errno_c = errno; /* save actual errno */
|
||
|
|
||
|
errno = 0;
|
||
|
#ifdef TCC
|
||
|
ret = strtol(nptr, (char **)errstr, 0);
|
||
|
#else
|
||
|
ret = strtoll(nptr, (char **)errstr, 0);
|
||
|
#endif
|
||
|
/* We accept only a string that represent exactly a number (ie. start
|
||
|
* and end with a digit).
|
||
|
* FreeBSD version wants errstr==NULL if no error occurs, otherwise
|
||
|
* errstr should point to an error string.
|
||
|
* For our purspose, we implement only the invalid error, ranges
|
||
|
* error aren't checked
|
||
|
*/
|
||
|
if (errno != 0 || nptr == *errstr || **errstr != '\0')
|
||
|
*errstr = "invalid";
|
||
|
else {
|
||
|
*errstr = NULL;
|
||
|
errno = errno_c;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
ishexnumber(int c)
|
||
|
{
|
||
|
return ((c >= '0' && c <= '9') ||
|
||
|
(c >= 'a' && c <= 'f') ||
|
||
|
(c >= 'A' && c <= 'F') );
|
||
|
}
|
||
|
|
||
|
#endif /* NEED_STRTONUM */
|
||
|
|
||
|
#ifdef __linux__
|
||
|
|
||
|
|
||
|
int optreset; /* missing in linux */
|
||
|
|
||
|
/*
|
||
|
* not implemented in linux.
|
||
|
* taken from /usr/src/lib/libc/string/strlcpy.c
|
||
|
*/
|
||
|
size_t
|
||
|
strlcpy(dst, src, siz)
|
||
|
char *dst;
|
||
|
const char *src;
|
||
|
size_t siz;
|
||
|
{
|
||
|
char *d = dst;
|
||
|
const char *s = src;
|
||
|
size_t n = siz;
|
||
|
|
||
|
/* Copy as many bytes as will fit */
|
||
|
if (n != 0 && --n != 0) {
|
||
|
do {
|
||
|
if ((*d++ = *s++) == 0)
|
||
|
break;
|
||
|
} while (--n != 0);
|
||
|
}
|
||
|
|
||
|
/* Not enough room in dst, add NUL and traverse rest of src */
|
||
|
if (n == 0) {
|
||
|
if (siz != 0)
|
||
|
*d = '\0'; /* NUL-terminate dst */
|
||
|
while (*s++)
|
||
|
;
|
||
|
}
|
||
|
|
||
|
return(s - src - 1); /* count does not include NUL */
|
||
|
}
|
||
|
|
||
|
|
||
|
/********************************************************
|
||
|
* modifed by acetcom
|
||
|
*
|
||
|
* move down to remove sysctlbyname() in MacOSX and FreeBSD
|
||
|
*/
|
||
|
|
||
|
/* #endif */ /* __linux__ */
|
||
|
|
||
|
/********************************************************/
|
||
|
|
||
|
|
||
|
#if defined (EMULATE_SYSCTL)
|
||
|
//XXX missing prerequisites
|
||
|
#include <net/if.h> //openwrt
|
||
|
#include <netinet/ip.h> //openwrt
|
||
|
#include <netinet/ip_fw.h>
|
||
|
#include <netinet/ip_dummynet.h>
|
||
|
int do_cmd(int optname, void *optval, uintptr_t optlen);
|
||
|
#endif /* EMULATE_SYSCTL */
|
||
|
|
||
|
/*
|
||
|
* set or get system information
|
||
|
* XXX lock acquisition/serialize calls
|
||
|
*
|
||
|
* we export this as sys/module/ipfw_mod/parameters/___
|
||
|
* This function get or/and set the value of the sysctl passed by
|
||
|
* the name parameter. If the old value is not desired,
|
||
|
* oldp and oldlenp should be set to NULL.
|
||
|
*
|
||
|
* XXX
|
||
|
* I do not know how this works in FreeBSD in the case
|
||
|
* where there are no write permission on the sysctl var.
|
||
|
* We read the value and set return variables in any way
|
||
|
* but returns -1 on write failures, regardless the
|
||
|
* read success.
|
||
|
*
|
||
|
* Since there is no information on types, in the following
|
||
|
* code we assume a length of 4 is a int.
|
||
|
*
|
||
|
* Returns 0 on success, -1 on errors.
|
||
|
*/
|
||
|
int
|
||
|
sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp,
|
||
|
size_t newlen)
|
||
|
{
|
||
|
#if defined (EMULATE_SYSCTL)
|
||
|
/*
|
||
|
* we embed the sysctl request in the usual sockopt mechanics.
|
||
|
* the sockopt buffer il filled with a dn_id with IP_DUMMYNET3
|
||
|
* command, and the special DN_SYSCTL_GET and DN_SYSCTL_SET
|
||
|
* subcommands.
|
||
|
* the syntax of this function is fully compatible with
|
||
|
* POSIX sysctlby name:
|
||
|
* if newp and newlen are != 0 => this is a set
|
||
|
* else if oldp and oldlen are != 0 => this is a get
|
||
|
* to avoid too much overhead in the module, the whole
|
||
|
* sysctltable is returned, and the parsing is done in userland,
|
||
|
* a probe request is done to retrieve the size needed to
|
||
|
* transfer the table, before the real request
|
||
|
* if both old and new params = 0 => this is a print
|
||
|
* this is a special request, done only by main()
|
||
|
* to implement the extension './ipfw sysctl',
|
||
|
* a command that bypasses the normal getopt, and that
|
||
|
* is available on those platforms that use this
|
||
|
* sysctl emulation.
|
||
|
* in this case, a negative oldlen signals that *oldp
|
||
|
* is actually a FILE* to print somewhere else than stdout
|
||
|
*/
|
||
|
|
||
|
int l;
|
||
|
int ret;
|
||
|
struct dn_id* oid;
|
||
|
struct sysctlhead* entry;
|
||
|
char* pstring;
|
||
|
char* pdata;
|
||
|
FILE* fp;
|
||
|
|
||
|
if((oldlenp != NULL) && ((int)*oldlenp < 0))
|
||
|
fp = (FILE*)oldp;
|
||
|
else
|
||
|
fp = stdout;
|
||
|
if(newp != NULL && newlen != 0)
|
||
|
{
|
||
|
//this is a set
|
||
|
l = sizeof(struct dn_id) + sizeof(struct sysctlhead) + strlen(name)+1 + newlen;
|
||
|
oid = malloc(l);
|
||
|
if (oid == NULL)
|
||
|
return -1;
|
||
|
oid->len = l;
|
||
|
oid->type = DN_SYSCTL_SET;
|
||
|
oid->id = DN_API_VERSION;
|
||
|
|
||
|
entry = (struct sysctlhead*)(oid+1);
|
||
|
pdata = (char*)(entry+1);
|
||
|
pstring = pdata + newlen;
|
||
|
|
||
|
entry->blocklen = ((sizeof(struct sysctlhead) + strlen(name)+1 + newlen) + 3) & ~3;
|
||
|
entry->namelen = strlen(name)+1;
|
||
|
entry->flags = 0;
|
||
|
entry->datalen = newlen;
|
||
|
|
||
|
bcopy(newp, pdata, newlen);
|
||
|
bcopy(name, pstring, strlen(name)+1);
|
||
|
|
||
|
ret = do_cmd(IP_DUMMYNET3, oid, (uintptr_t)l);
|
||
|
if (ret != 0)
|
||
|
return -1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//this is a get or a print
|
||
|
l = sizeof(struct dn_id);
|
||
|
oid = malloc(l);
|
||
|
if (oid == NULL)
|
||
|
return -1;
|
||
|
oid->len = l;
|
||
|
oid->type = DN_SYSCTL_GET;
|
||
|
oid->id = DN_API_VERSION;
|
||
|
|
||
|
ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
|
||
|
if (ret != 0)
|
||
|
return -1;
|
||
|
|
||
|
l=oid->id;
|
||
|
free(oid);
|
||
|
oid = malloc(l);
|
||
|
if (oid == NULL)
|
||
|
return -1;
|
||
|
oid->len = l;
|
||
|
oid->type = DN_SYSCTL_GET;
|
||
|
oid->id = DN_API_VERSION;
|
||
|
|
||
|
ret = do_cmd(-IP_DUMMYNET3, oid, (uintptr_t)&l);
|
||
|
if (ret != 0)
|
||
|
return -1;
|
||
|
|
||
|
entry = (struct sysctlhead*)(oid+1);
|
||
|
while(entry->blocklen != 0)
|
||
|
{
|
||
|
pdata = (char*)(entry+1);
|
||
|
pstring = pdata+entry->datalen;
|
||
|
|
||
|
//time to check if this is a get or a print
|
||
|
if(name != NULL && oldp != NULL && *oldlenp > 0)
|
||
|
{
|
||
|
//this is a get
|
||
|
if(strcmp(name,pstring) == 0)
|
||
|
{
|
||
|
//match found, sanity chech on len
|
||
|
if(*oldlenp < entry->datalen)
|
||
|
{
|
||
|
printf("%s error: buffer too small\n",__FUNCTION__);
|
||
|
return -1;
|
||
|
}
|
||
|
*oldlenp = entry->datalen;
|
||
|
bcopy(pdata, oldp, *oldlenp);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//this is a print
|
||
|
if( name == NULL )
|
||
|
goto print;
|
||
|
if ( (strncmp(pstring,name,strlen(name)) == 0) && ( pstring[strlen(name)]=='\0' || pstring[strlen(name)]=='.' ) )
|
||
|
goto print;
|
||
|
else
|
||
|
goto skip;
|
||
|
print:
|
||
|
fprintf(fp, "%s: ",pstring);
|
||
|
switch( entry->flags >> 2 )
|
||
|
{
|
||
|
case SYSCTLTYPE_LONG:
|
||
|
fprintf(fp, "%li ", *(long*)(pdata));
|
||
|
break;
|
||
|
case SYSCTLTYPE_UINT:
|
||
|
fprintf(fp, "%u ", *(unsigned int*)(pdata));
|
||
|
break;
|
||
|
case SYSCTLTYPE_ULONG:
|
||
|
fprintf(fp, "%lu ", *(unsigned long*)(pdata));
|
||
|
break;
|
||
|
case SYSCTLTYPE_INT:
|
||
|
default:
|
||
|
fprintf(fp, "%i ", *(int*)(pdata));
|
||
|
}
|
||
|
if( (entry->flags & 0x00000003) == CTLFLAG_RD )
|
||
|
fprintf(fp, "\t(read only)\n");
|
||
|
else
|
||
|
fprintf(fp, "\n");
|
||
|
skip: ;
|
||
|
}
|
||
|
entry = (struct sysctlhead*)((unsigned char*)entry + entry->blocklen);
|
||
|
}
|
||
|
free(oid);
|
||
|
return 0;
|
||
|
}
|
||
|
//fallback for invalid options
|
||
|
return -1;
|
||
|
|
||
|
#else /* __linux__ */
|
||
|
FILE *fp;
|
||
|
char *basename = "/sys/module/ipfw_mod/parameters/";
|
||
|
char filename[256]; /* full filename */
|
||
|
char *varp;
|
||
|
int ret = 0; /* return value */
|
||
|
int d;
|
||
|
|
||
|
if (name == NULL) /* XXX set errno */
|
||
|
return -1;
|
||
|
|
||
|
/* locate the filename */
|
||
|
varp = strrchr(name, '.');
|
||
|
if (varp == NULL) /* XXX set errno */
|
||
|
return -1;
|
||
|
|
||
|
snprintf(filename, sizeof(filename), "%s%s", basename, varp+1);
|
||
|
|
||
|
/*
|
||
|
* XXX we could open the file here, in rw mode
|
||
|
* but need to check if a file have write
|
||
|
* permissions.
|
||
|
*/
|
||
|
|
||
|
/* check parameters */
|
||
|
if (oldp && oldlenp) { /* read mode */
|
||
|
fp = fopen(filename, "r");
|
||
|
if (fp == NULL) {
|
||
|
fprintf(stderr, "%s fopen error reading filename %s\n", __FUNCTION__, filename);
|
||
|
return -1;
|
||
|
}
|
||
|
if (*oldlenp == 4) {
|
||
|
if (fscanf(fp, "%d", &d) == 1)
|
||
|
memcpy(oldp, &d, *oldlenp);
|
||
|
else
|
||
|
ret = -1;
|
||
|
}
|
||
|
fclose(fp);
|
||
|
}
|
||
|
|
||
|
if (newp && newlen) { /* write */
|
||
|
fp = fopen(filename, "w");
|
||
|
if (fp == NULL) {
|
||
|
fprintf(stderr, "%s fopen error writing filename %s\n", __FUNCTION__, filename);
|
||
|
return -1;
|
||
|
}
|
||
|
if (newlen == 4) {
|
||
|
if (fprintf(fp, "%d", *(int*)newp) < 1)
|
||
|
ret = -1;
|
||
|
}
|
||
|
|
||
|
fclose(fp);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
#endif /* __linux__ */
|
||
|
}
|
||
|
|
||
|
/********************************************************
|
||
|
* modifed by acetcom
|
||
|
*
|
||
|
* move down to remove sysctlbyname in MacOSX and FreeBSD
|
||
|
*/
|
||
|
|
||
|
#endif /* __linux__ */
|
||
|
|
||
|
/********************************************************/
|
||
|
|
||
|
/*
|
||
|
* The following two functions implement getsockopt/setsockopt
|
||
|
* replacements to talk over a TCP socket.
|
||
|
* Because the calls are synchronous, we can run blocking code
|
||
|
* and do not need to play special tricks to be selectable.
|
||
|
* The wire protocol for the emulation is the following:
|
||
|
* REQUEST: n32 req_size, level, optname; u8 data[req_size]
|
||
|
* RESPONSE: n32 resp_size, ret_code; u8 data[resp_size]
|
||
|
* data is only present if ret_code == 0
|
||
|
*
|
||
|
* Return 0 if the message wan sent to the remote
|
||
|
* endpoint, -1 on error.
|
||
|
*
|
||
|
* If the required lenght is greater then the
|
||
|
* available buffer size, -1 is returned and
|
||
|
* optlen is the required lenght.
|
||
|
*/
|
||
|
enum sock_type {GET_SOCKOPT, SET_SOCKOPT};
|
||
|
|
||
|
struct wire_hdr {
|
||
|
uint32_t optlen; /* actual data len */
|
||
|
uint32_t level; /* or error */
|
||
|
uint32_t optname; /* or act len */
|
||
|
uint32_t dir; /* in or out */
|
||
|
};
|
||
|
|
||
|
/* do a complete write of the buffer */
|
||
|
static int
|
||
|
writen(int fd, const char *buf, int len)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (; len > 0; buf += i, len -= i) {
|
||
|
i = write(fd, buf, len);
|
||
|
ND("have %d wrote %d", len, i);
|
||
|
if (i < 0) {
|
||
|
if (errno == EAGAIN)
|
||
|
continue;
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* do a complete read */
|
||
|
static int
|
||
|
readn(int fd, char *buf, int len)
|
||
|
{
|
||
|
int i, pos;
|
||
|
|
||
|
for (pos = 0; pos < len; pos += i) {
|
||
|
i = read(fd, buf + pos, len - pos);
|
||
|
ND("have %d want %d got %d", pos, len, i);
|
||
|
if (i < 0) {
|
||
|
if (errno == EAGAIN)
|
||
|
continue;
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
ND("full read got %d", pos);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
__sockopt2(int s, int level, int optname, void *optval, socklen_t *optlen,
|
||
|
enum sopt_dir dir)
|
||
|
{
|
||
|
struct wire_hdr r;
|
||
|
int len = optlen && optval ? *optlen : 0;
|
||
|
int new_errno;
|
||
|
|
||
|
ND("dir %d optlen %d level %d optname %d", dir, len, level, optname);
|
||
|
/* send request to the server */
|
||
|
r.optlen = htonl(len);
|
||
|
r.level = htonl(level);
|
||
|
r.optname = htonl(optname);
|
||
|
r.dir = htonl(dir);
|
||
|
|
||
|
if (writen(s, (const char *) &r, sizeof(r)))
|
||
|
return -1; /* error writing */
|
||
|
|
||
|
/* send data, if present */
|
||
|
if (len < 0) {
|
||
|
fprintf(stderr, "%s invalid args found\n", __FUNCTION__);
|
||
|
return -1;
|
||
|
} else if (len > 0) {
|
||
|
if (writen(s, optval, len))
|
||
|
return -1; /* error writing */
|
||
|
}
|
||
|
|
||
|
/* read response size and error code */
|
||
|
if (readn(s, (char *)&r, sizeof(r)))
|
||
|
return -1; /* error reading */
|
||
|
len = ntohl(r.optlen);
|
||
|
ND("got header, datalen %d", len);
|
||
|
if (len > 0) {
|
||
|
if (readn(s, optval, len)) {
|
||
|
return -1; /* error reading */
|
||
|
}
|
||
|
}
|
||
|
if (optlen)
|
||
|
*optlen = ntohl(r.optlen); /* actual len */
|
||
|
new_errno = ntohl(r.level);
|
||
|
if (new_errno)
|
||
|
errno = new_errno;
|
||
|
return (new_errno ? -1 : 0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* getsockopt() replacement.
|
||
|
*/
|
||
|
int
|
||
|
getsockopt2(int s, int level, int optname, void *optval,
|
||
|
socklen_t *optlen)
|
||
|
{
|
||
|
return __sockopt2(s, level, optname, optval, optlen, SOPT_GET);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* setsockopt() replacement
|
||
|
*/
|
||
|
int
|
||
|
setsockopt2(int s, int level, int optname, void *optval,
|
||
|
socklen_t optlen)
|
||
|
{
|
||
|
/* optlen not changed, use the local address */
|
||
|
return __sockopt2(s, level, optname, optval, &optlen, SOPT_SET);
|
||
|
}
|
||
|
|
||
|
#ifdef socket
|
||
|
#undef socket /* we want the real one */
|
||
|
#endif
|
||
|
/*
|
||
|
* This function replaces the socket() call to connect to
|
||
|
* the ipfw control socket.
|
||
|
* We actually ignore the paramerers if IPFW_HOST and IPFW_PORT
|
||
|
* are defined.
|
||
|
*/
|
||
|
int
|
||
|
do_connect(const char *addr, int port)
|
||
|
{
|
||
|
int conn_fd;
|
||
|
|
||
|
/* open the socket */
|
||
|
#ifdef NETLINK
|
||
|
|
||
|
struct rtnl_handle rth;
|
||
|
|
||
|
conn_fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||
|
#else
|
||
|
struct sockaddr_in server; /* server address */
|
||
|
const char *s;
|
||
|
|
||
|
conn_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||
|
if (conn_fd < 0) {
|
||
|
perror("socket");
|
||
|
return -1;
|
||
|
}
|
||
|
#endif
|
||
|
#ifndef NETLINK
|
||
|
/* fill the sockaddr structure with server address */
|
||
|
bzero(&server, sizeof(server));
|
||
|
server.sin_family = AF_INET;
|
||
|
|
||
|
/* override the host if set in the environment */
|
||
|
s = getenv("IPFW_HOST");
|
||
|
if (s)
|
||
|
addr = s;
|
||
|
inet_aton(addr, &server.sin_addr);
|
||
|
s = getenv("IPFW_PORT");
|
||
|
if (s && atoi(s) > 0)
|
||
|
port = atoi(s);
|
||
|
server.sin_port = htons(port);
|
||
|
|
||
|
/* connect to the server */
|
||
|
if (connect(conn_fd, (struct sockaddr*) &server, sizeof(server)) < 0) {
|
||
|
perror("connect");
|
||
|
return -1;
|
||
|
}
|
||
|
#ifdef setsockopt /* we want the real one here */
|
||
|
#undef setsockopt
|
||
|
#undef getsockopt
|
||
|
#endif
|
||
|
{
|
||
|
int on = 1, ret;
|
||
|
ret = setsockopt(conn_fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
|
||
|
ND("set TCP_NODELAY %d returns %d", on, ret);
|
||
|
}
|
||
|
if (0)
|
||
|
fprintf(stderr, "connected to %s:%d\n",
|
||
|
inet_ntoa(server.sin_addr), ntohs(server.sin_port));
|
||
|
#endif
|
||
|
return conn_fd;
|
||
|
}
|