/* * Userland functions missing in linux * taken from /usr/src/lib/libc/stdtime/time32.c */ #include #include #include #include #include /* sockaddr_in */ #include /* TCP_NODELAY */ #include #include /* uint* types */ #include #include /* bzero */ #include /* 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 //openwrt #include //openwrt #include #include 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; }