mirror of
https://github.com/bol-van/zapret.git
synced 2025-05-24 22:32:58 +03:00
history purge
This commit is contained in:
12
tpws/Makefile
Normal file
12
tpws/Makefile
Normal file
@@ -0,0 +1,12 @@
|
||||
CC ?= gcc
|
||||
CFLAGS += -std=c99 -s -O3
|
||||
LIBS = -lz -lcap
|
||||
SRC_FILES = *.c
|
||||
|
||||
all: tpws
|
||||
|
||||
tpws: $(SRC_FILES)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f tpws *.o
|
82
tpws/gzip.c
Normal file
82
tpws/gzip.c
Normal file
@@ -0,0 +1,82 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "gzip.h"
|
||||
|
||||
#define ZCHUNK 16384
|
||||
#define BUFMIN 128
|
||||
#define BUFCHUNK (1024*128)
|
||||
|
||||
int z_readfile(FILE *F,char **buf,size_t *size)
|
||||
{
|
||||
z_stream zs;
|
||||
int r;
|
||||
unsigned char in[ZCHUNK];
|
||||
size_t bufsize;
|
||||
void *newbuf;
|
||||
|
||||
memset(&zs,0,sizeof(zs));
|
||||
|
||||
*buf = NULL;
|
||||
bufsize=*size=0;
|
||||
|
||||
r=inflateInit2(&zs,47);
|
||||
if (r != Z_OK) return r;
|
||||
|
||||
do
|
||||
{
|
||||
zs.avail_in = fread(in, 1, sizeof(in), F);
|
||||
if (ferror(F))
|
||||
{
|
||||
r = Z_ERRNO;
|
||||
goto zerr;
|
||||
}
|
||||
if (!zs.avail_in) break;
|
||||
zs.next_in = in;
|
||||
do
|
||||
{
|
||||
if ((bufsize-*size)<BUFMIN)
|
||||
{
|
||||
bufsize += BUFCHUNK;
|
||||
newbuf = *buf ? realloc(*buf,bufsize) : malloc(bufsize);
|
||||
if (!newbuf)
|
||||
{
|
||||
r = Z_MEM_ERROR;
|
||||
goto zerr;
|
||||
}
|
||||
*buf = newbuf;
|
||||
}
|
||||
zs.avail_out = bufsize - *size;
|
||||
zs.next_out = (unsigned char*)(*buf + *size);
|
||||
r = inflate(&zs, Z_NO_FLUSH);
|
||||
if (r!=Z_OK && r!=Z_STREAM_END) goto zerr;
|
||||
*size = bufsize - zs.avail_out;
|
||||
} while (r==Z_OK && zs.avail_in);
|
||||
} while (r==Z_OK);
|
||||
|
||||
if (*size<bufsize)
|
||||
{
|
||||
// free extra space
|
||||
if (newbuf = realloc(*buf,*size)) *buf=newbuf;
|
||||
}
|
||||
|
||||
inflateEnd(&zs);
|
||||
return Z_OK;
|
||||
|
||||
zerr:
|
||||
inflateEnd(&zs);
|
||||
if (*buf)
|
||||
{
|
||||
free(*buf);
|
||||
*buf = NULL;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool is_gzip(FILE* F)
|
||||
{
|
||||
unsigned char magic[2];
|
||||
bool b = !fseek(F,0,SEEK_SET) && fread(magic, 1, 2, F)==2 && magic[0]==0x1F && magic[1]==0x8B;
|
||||
fseek(F,0,SEEK_SET);
|
||||
return b;
|
||||
}
|
8
tpws/gzip.h
Normal file
8
tpws/gzip.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <zlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
int z_readfile(FILE *F,char **buf,size_t *size);
|
||||
bool is_gzip(FILE* F);
|
112
tpws/hostlist.c
Normal file
112
tpws/hostlist.c
Normal file
@@ -0,0 +1,112 @@
|
||||
#include <stdio.h>
|
||||
#include "hostlist.h"
|
||||
#include "gzip.h"
|
||||
#include "params.h"
|
||||
|
||||
static bool addpool(strpool **hostlist, char **s, char *end)
|
||||
{
|
||||
char *p;
|
||||
|
||||
// advance until eol lowering all chars
|
||||
for (p = *s; p<end && *p && *p!='\r' && *p != '\n'; p++) *p=tolower(*p);
|
||||
if (!StrPoolAddStrLen(hostlist, *s, p-*s))
|
||||
{
|
||||
StrPoolDestroy(hostlist);
|
||||
*hostlist = NULL;
|
||||
return false;
|
||||
}
|
||||
// advance to the next line
|
||||
for (; p<end && (!*p || *p=='\r' || *p=='\n') ; p++);
|
||||
*s = p;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool LoadHostList(strpool **hostlist, char *filename)
|
||||
{
|
||||
char *p, *e, s[256], *zbuf;
|
||||
size_t zsize;
|
||||
int ct = 0;
|
||||
FILE *F;
|
||||
int r;
|
||||
|
||||
if (*hostlist)
|
||||
{
|
||||
StrPoolDestroy(hostlist);
|
||||
*hostlist = NULL;
|
||||
}
|
||||
|
||||
if (!(F = fopen(filename, "rb")))
|
||||
{
|
||||
fprintf(stderr, "Could not open %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_gzip(F))
|
||||
{
|
||||
r = z_readfile(F,&zbuf,&zsize);
|
||||
fclose(F);
|
||||
if (r==Z_OK)
|
||||
{
|
||||
printf("zlib compression detected. uncompressed size : %zu\n", zsize);
|
||||
|
||||
p = zbuf;
|
||||
e = zbuf + zsize;
|
||||
while(p<e)
|
||||
{
|
||||
if (!addpool(hostlist,&p,e))
|
||||
{
|
||||
fprintf(stderr, "Not enough memory to store host list : %s\n", filename);
|
||||
free(zbuf);
|
||||
return false;
|
||||
}
|
||||
ct++;
|
||||
}
|
||||
free(zbuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "zlib decompression failed : result %d\n",r);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("loading plain text list\n");
|
||||
|
||||
while (fgets(s, 256, F))
|
||||
{
|
||||
p = s;
|
||||
if (!addpool(hostlist,&p,p+strlen(p)))
|
||||
{
|
||||
fprintf(stderr, "Not enough memory to store host list : %s\n", filename);
|
||||
fclose(F);
|
||||
return false;
|
||||
}
|
||||
ct++;
|
||||
}
|
||||
fclose(F);
|
||||
}
|
||||
|
||||
printf("Loaded %d hosts from %s\n", ct, filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SearchHostList(strpool *hostlist, const char *host)
|
||||
{
|
||||
if (hostlist)
|
||||
{
|
||||
const char *p = host;
|
||||
bool bInHostList;
|
||||
while (p)
|
||||
{
|
||||
bInHostList = StrPoolCheckStr(hostlist, p);
|
||||
VPRINT("Hostlist check for %s : %s", p, bInHostList ? "positive" : "negative")
|
||||
if (bInHostList) return true;
|
||||
p = strchr(p, '.');
|
||||
if (p) p++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
7
tpws/hostlist.h
Normal file
7
tpws/hostlist.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "strpool.h"
|
||||
|
||||
bool LoadHostList(strpool **hostlist, char *filename);
|
||||
bool SearchHostList(strpool *hostlist, const char *host);
|
43
tpws/params.h
Normal file
43
tpws/params.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <net/if.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "strpool.h"
|
||||
|
||||
enum splithttpreq { split_none = 0, split_method, split_host };
|
||||
|
||||
struct params_s
|
||||
{
|
||||
char bindaddr[64],bindiface[IF_NAMESIZE];
|
||||
bool bind_if6;
|
||||
bool bindll,bindll_force;
|
||||
int bind_wait_ifup,bind_wait_ip,bind_wait_ip_ll;
|
||||
uint8_t proxy_type;
|
||||
bool no_resolve;
|
||||
bool skip_nodelay;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
bool daemon;
|
||||
uint16_t port;
|
||||
int maxconn,maxfiles,max_orphan_time;
|
||||
int local_rcvbuf,local_sndbuf,remote_rcvbuf,remote_sndbuf;
|
||||
|
||||
bool tamper; // any tamper option is set
|
||||
bool hostcase, hostdot, hosttab, hostnospace, methodspace, methodeol, unixeol;
|
||||
int hostpad;
|
||||
char hostspell[4];
|
||||
enum splithttpreq split_http_req;
|
||||
int split_pos;
|
||||
char hostfile[256];
|
||||
char pidfile[256];
|
||||
strpool *hostlist;
|
||||
|
||||
int debug;
|
||||
};
|
||||
|
||||
extern struct params_s params;
|
||||
|
||||
#define _DBGPRINT(format, level, ...) { if (params.debug>=level) printf(format "\n", ##__VA_ARGS__); }
|
||||
#define VPRINT(format, ...) _DBGPRINT(format,1,##__VA_ARGS__)
|
||||
#define DBGPRINT(format, ...) _DBGPRINT(format,2,##__VA_ARGS__)
|
93
tpws/socks.h
Normal file
93
tpws/socks.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#pragma pack(push,1)
|
||||
|
||||
#define S4_CMD_CONNECT 1
|
||||
#define S4_CMD_BIND 2
|
||||
typedef struct
|
||||
{
|
||||
uint8_t ver,cmd;
|
||||
uint16_t port;
|
||||
uint32_t ip;
|
||||
} s4_req;
|
||||
#define S4_REQ_HEADER_VALID(r,l) (l>=sizeof(s4_req) && r->ver==4)
|
||||
#define S4_REQ_CONNECT_VALID(r,l) (S4_REQ_HEADER_VALID(r,l) && r->cmd==S4_CMD_CONNECT)
|
||||
|
||||
#define S4_REP_OK 90
|
||||
#define S4_REP_FAILED 91
|
||||
typedef struct
|
||||
{
|
||||
uint8_t zero,rep;
|
||||
uint16_t port;
|
||||
uint32_t ip;
|
||||
} s4_rep;
|
||||
|
||||
|
||||
|
||||
#define S5_AUTH_NONE 0
|
||||
#define S5_AUTH_GSSAPI 1
|
||||
#define S5_AUTH_USERPASS 2
|
||||
#define S5_AUTH_UNACCEPTABLE 0xFF
|
||||
typedef struct
|
||||
{
|
||||
uint8_t ver,nmethods,methods[255];
|
||||
} s5_handshake;
|
||||
#define S5_REQ_HANDHSHAKE_VALID(r,l) (l>=3 && r->ver==5 && r->nmethods && l>=(2+r->nmethods))
|
||||
typedef struct
|
||||
{
|
||||
uint8_t ver,method;
|
||||
} s5_handshake_ack;
|
||||
|
||||
#define S5_CMD_CONNECT 1
|
||||
#define S5_CMD_BIND 2
|
||||
#define S5_CMD_UDP_ASSOC 3
|
||||
#define S5_ATYP_IP4 1
|
||||
#define S5_ATYP_DOM 3
|
||||
#define S5_ATYP_IP6 4
|
||||
typedef struct
|
||||
{
|
||||
uint8_t ver,cmd,rsv,atyp;
|
||||
union {
|
||||
struct {
|
||||
struct in_addr addr;
|
||||
uint16_t port;
|
||||
} d4;
|
||||
struct {
|
||||
struct in6_addr addr;
|
||||
uint16_t port;
|
||||
} d6;
|
||||
struct {
|
||||
uint8_t len;
|
||||
char domport[255+2]; // max hostname + binary port
|
||||
} dd;
|
||||
};
|
||||
} s5_req;
|
||||
#define S5_REQ_HEADER_VALID(r,l) (l>=4 && r->ver==5)
|
||||
#define S5_IP46_VALID(r,l) (r->atyp==S5_ATYP_IP4 && l>=(4+sizeof(r->d4)) || r->atyp==S5_ATYP_IP6 && l>=(4+sizeof(r->d6)))
|
||||
#define S5_REQ_CONNECT_VALID(r,l) (S5_REQ_HEADER_VALID(r,l) && r->cmd==S5_CMD_CONNECT && (S5_IP46_VALID(r,l) || r->atyp==S5_ATYP_DOM && l>=5 && l>=(5+r->dd.len)))
|
||||
#define S5_PORT_FROM_DD(r,l) (l>=(4+r->dd.len+2) ? ntohs(*(uint16_t*)(r->dd.domport+r->dd.len)) : 0)
|
||||
|
||||
#define S5_REP_OK 0
|
||||
#define S5_REP_GENERAL_FAILURE 1
|
||||
#define S5_REP_NOT_ALLOWED_BY_RULESET 2
|
||||
#define S5_REP_NETWORK_UNREACHABLE 3
|
||||
#define S5_REP_HOST_UNREACHABLE 4
|
||||
#define S5_REP_CONN_REFUSED 5
|
||||
#define S5_REP_TTL_EXPIRED 6
|
||||
#define S5_REP_COMMAND_NOT_SUPPORTED 7
|
||||
#define S5_REP_ADDR_TYPE_NOT_SUPPORTED 8
|
||||
typedef struct
|
||||
{
|
||||
uint8_t ver,rep,rsv,atyp;
|
||||
union {
|
||||
struct {
|
||||
struct in_addr addr;
|
||||
uint16_t port;
|
||||
} d4;
|
||||
};
|
||||
} s5_rep;
|
||||
|
||||
#pragma pack(pop)
|
76
tpws/strpool.c
Normal file
76
tpws/strpool.c
Normal file
@@ -0,0 +1,76 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "strpool.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#undef uthash_nonfatal_oom
|
||||
#define uthash_nonfatal_oom(elt) ut_oom_recover(elt)
|
||||
|
||||
static bool oom=false;
|
||||
static void ut_oom_recover(strpool *elem)
|
||||
{
|
||||
oom=true;
|
||||
}
|
||||
|
||||
// for zero terminated strings
|
||||
bool StrPoolAddStr(strpool **pp,const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = strdup(s)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem );
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// for not zero terminated strings
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen)
|
||||
{
|
||||
strpool *elem;
|
||||
if (!(elem = (strpool*)malloc(sizeof(strpool))))
|
||||
return false;
|
||||
if (!(elem->str = malloc(slen+1)))
|
||||
{
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
memcpy(elem->str,s,slen);
|
||||
elem->str[slen]=0;
|
||||
oom = false;
|
||||
HASH_ADD_KEYPTR( hh, *pp, elem->str, strlen(elem->str), elem );
|
||||
if (oom)
|
||||
{
|
||||
free(elem->str);
|
||||
free(elem);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StrPoolCheckStr(strpool *p,const char *s)
|
||||
{
|
||||
strpool *elem;
|
||||
HASH_FIND_STR( p, s, elem);
|
||||
return elem!=NULL;
|
||||
}
|
||||
|
||||
void StrPoolDestroy(strpool **p)
|
||||
{
|
||||
strpool *elem,*tmp;
|
||||
HASH_ITER(hh, *p, elem, tmp) {
|
||||
free(elem->str);
|
||||
HASH_DEL(*p, elem);
|
||||
free(elem);
|
||||
}
|
||||
*p = NULL;
|
||||
}
|
19
tpws/strpool.h
Normal file
19
tpws/strpool.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
|
||||
//#define HASH_BLOOM 20
|
||||
#define HASH_NONFATAL_OOM 1
|
||||
#define HASH_FUNCTION HASH_BER
|
||||
#include "uthash.h"
|
||||
|
||||
typedef struct strpool {
|
||||
char *str; /* key */
|
||||
UT_hash_handle hh; /* makes this structure hashable */
|
||||
} strpool;
|
||||
|
||||
void StrPoolDestroy(strpool **p);
|
||||
bool StrPoolAddStr(strpool **pp,const char *s);
|
||||
bool StrPoolAddStrLen(strpool **pp,const char *s,size_t slen);
|
||||
bool StrPoolCheckStr(strpool *p,const char *s);
|
222
tpws/tamper.c
Normal file
222
tpws/tamper.c
Normal file
@@ -0,0 +1,222 @@
|
||||
#include "tamper.h"
|
||||
#include "params.h"
|
||||
#include "hostlist.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
char *find_bin(void *data, size_t len, const void *blk, size_t blk_len)
|
||||
{
|
||||
while (len >= blk_len)
|
||||
{
|
||||
if (!memcmp(data, blk, blk_len))
|
||||
return data;
|
||||
data = (char*)data + 1;
|
||||
len--;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// pHost points to "Host: ..."
|
||||
bool find_host(char **pHost,char *buf,size_t bs)
|
||||
{
|
||||
if (!*pHost)
|
||||
{
|
||||
*pHost = find_bin(buf, bs, "\nHost:", 6);
|
||||
if (*pHost)
|
||||
{
|
||||
(*pHost)++;
|
||||
VPRINT("Found Host: at pos %zu",*pHost - buf)
|
||||
}
|
||||
}
|
||||
return !!*pHost;
|
||||
}
|
||||
|
||||
static const char *http_methods[] = { "GET /","POST /","HEAD /","OPTIONS /","PUT /","DELETE /","CONNECT /","TRACE /",NULL };
|
||||
void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos)
|
||||
{
|
||||
char *p, *pp, *pHost = NULL;
|
||||
size_t method_len = 0, pos;
|
||||
const char **method;
|
||||
bool bIsHttp = false, bBypass = false;
|
||||
char bRemovedHostSpace = 0;
|
||||
char Host[128];
|
||||
|
||||
*split_pos=0;
|
||||
|
||||
for (method = http_methods; *method; method++)
|
||||
{
|
||||
method_len = strlen(*method);
|
||||
if (method_len <= *size && !memcmp(segment, *method, method_len))
|
||||
{
|
||||
bIsHttp = true;
|
||||
method_len -= 2; // "GET /" => "GET"
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bIsHttp)
|
||||
{
|
||||
VPRINT("Data block looks like http request start : %s", *method)
|
||||
// cpu saving : we search host only if and when required. we do not research host every time we need its position
|
||||
if (params.hostlist && find_host(&pHost,segment,*size))
|
||||
{
|
||||
p = pHost + 5;
|
||||
while (p < (segment + *size) && (*p == ' ' || *p == '\t')) p++;
|
||||
pp = p;
|
||||
while (pp < (segment + *size) && (pp - p) < (sizeof(Host) - 1) && *pp != '\r' && *pp != '\n') pp++;
|
||||
memcpy(Host, p, pp - p);
|
||||
Host[pp - p] = '\0';
|
||||
VPRINT("Requested Host is : %s", Host)
|
||||
for(p = Host; *p; p++) *p=tolower(*p);
|
||||
bBypass = !SearchHostList(params.hostlist,Host);
|
||||
}
|
||||
if (!bBypass)
|
||||
{
|
||||
if (params.unixeol)
|
||||
{
|
||||
p = pp = segment;
|
||||
while (p = find_bin(p, segment + *size - p, "\r\n", 2))
|
||||
{
|
||||
*p = '\n'; p++;
|
||||
memmove(p, p + 1, segment + *size - p - 1);
|
||||
(*size)--;
|
||||
if (pp == (p - 1))
|
||||
{
|
||||
// probably end of http headers
|
||||
VPRINT("Found double EOL at pos %zu. Stop replacing.", pp - segment)
|
||||
break;
|
||||
}
|
||||
pp = p;
|
||||
}
|
||||
pHost = NULL; // invalidate
|
||||
}
|
||||
if (params.methodeol && (*size+1+!params.unixeol)<=segment_buffer_size)
|
||||
{
|
||||
VPRINT("Adding EOL before method")
|
||||
if (params.unixeol)
|
||||
{
|
||||
memmove(segment + 1, segment, *size);
|
||||
(*size)++;;
|
||||
segment[0] = '\n';
|
||||
if (*split_pos) (*split_pos)++;
|
||||
}
|
||||
else
|
||||
{
|
||||
memmove(segment + 2, segment, *size);
|
||||
*size += 2;
|
||||
segment[0] = '\r';
|
||||
segment[1] = '\n';
|
||||
if (*split_pos) *split_pos += 2;
|
||||
}
|
||||
pHost = NULL; // invalidate
|
||||
}
|
||||
if (params.methodspace && *size<segment_buffer_size)
|
||||
{
|
||||
// we only work with data blocks looking as HTTP query, so method is at the beginning
|
||||
VPRINT("Adding extra space after method")
|
||||
p = segment + method_len + 1;
|
||||
pos = method_len + 1;
|
||||
memmove(p + 1, p, *size - pos);
|
||||
*p = ' '; // insert extra space
|
||||
(*size)++; // block will grow by 1 byte
|
||||
if (pHost) pHost++; // Host: position will move by 1 byte
|
||||
}
|
||||
if ((params.hostdot || params.hosttab) && *size<segment_buffer_size && find_host(&pHost,segment,*size))
|
||||
{
|
||||
p = pHost + 5;
|
||||
while (p < (segment + *size) && *p != '\r' && *p != '\n') p++;
|
||||
if (p < (segment + *size))
|
||||
{
|
||||
pos = p - segment;
|
||||
VPRINT("Adding %s to host name at pos %zu", params.hostdot ? "dot" : "tab", pos)
|
||||
memmove(p + 1, p, *size - pos);
|
||||
*p = params.hostdot ? '.' : '\t'; // insert dot or tab
|
||||
(*size)++; // block will grow by 1 byte
|
||||
}
|
||||
}
|
||||
if (params.hostnospace && find_host(&pHost,segment,*size) && (pHost+5)<(segment+*size) && pHost[5] == ' ')
|
||||
{
|
||||
p = pHost + 6;
|
||||
pos = p - segment;
|
||||
VPRINT("Removing space before host name at pos %zu", pos)
|
||||
memmove(p - 1, p, *size - pos);
|
||||
(*size)--; // block will shrink by 1 byte
|
||||
bRemovedHostSpace = 1;
|
||||
}
|
||||
if (params.hostcase && find_host(&pHost,segment,*size))
|
||||
{
|
||||
VPRINT("Changing 'Host:' => '%c%c%c%c:' at pos %zu", params.hostspell[0], params.hostspell[1], params.hostspell[2], params.hostspell[3], pHost - segment)
|
||||
memcpy(pHost, params.hostspell, 4);
|
||||
}
|
||||
if (params.hostpad && find_host(&pHost,segment,*size))
|
||||
{
|
||||
// add : XXXXX: <padding?[\r\n|\n]
|
||||
char s[8];
|
||||
size_t hsize = params.unixeol ? 8 : 9;
|
||||
size_t hostpad = params.hostpad<hsize ? hsize : params.hostpad;
|
||||
|
||||
if ((hsize+*size)>segment_buffer_size)
|
||||
VPRINT("could not add host padding : buffer too small")
|
||||
else
|
||||
{
|
||||
if ((hostpad+*size)>segment_buffer_size)
|
||||
{
|
||||
hostpad=segment_buffer_size-*size;
|
||||
VPRINT("host padding reduced to %zu bytes : buffer too small", hostpad)
|
||||
}
|
||||
else
|
||||
VPRINT("host padding with %zu bytes", hostpad)
|
||||
|
||||
p = pHost;
|
||||
pos = p - segment;
|
||||
memmove(p + hostpad, p, *size - pos);
|
||||
(*size) += hostpad;
|
||||
while(hostpad)
|
||||
{
|
||||
#define MAX_HDR_SIZE 2048
|
||||
size_t padsize = hostpad > hsize ? hostpad-hsize : 0;
|
||||
if (padsize>MAX_HDR_SIZE) padsize=MAX_HDR_SIZE;
|
||||
// if next header would be too small then add extra padding to the current one
|
||||
if ((hostpad-padsize-hsize)<hsize) padsize+=hostpad-padsize-hsize;
|
||||
snprintf(s,sizeof(s),"%c%04x: ", 'a'+rand()%('z'-'a'+1), rand() & 0xFFFF);
|
||||
memcpy(p,s,7);
|
||||
p+=7;
|
||||
memset(p,'a'+rand()%('z'-'a'+1),padsize);
|
||||
p+=padsize;
|
||||
if (params.unixeol)
|
||||
*p++='\n';
|
||||
else
|
||||
{
|
||||
*p++='\r';
|
||||
*p++='\n';
|
||||
}
|
||||
hostpad-=hsize+padsize;
|
||||
}
|
||||
pHost = NULL; // invalidate
|
||||
}
|
||||
}
|
||||
if (!params.split_pos)
|
||||
{
|
||||
switch (params.split_http_req)
|
||||
{
|
||||
case split_method:
|
||||
*split_pos = method_len - 1 + params.methodeol + (params.methodeol && !params.unixeol);
|
||||
break;
|
||||
case split_host:
|
||||
if (find_host(&pHost,segment,*size))
|
||||
*split_pos = pHost + 6 - bRemovedHostSpace - segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (params.split_pos < *size) *split_pos = params.split_pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
VPRINT("Not acting on this request")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is the only parameter applicable to non-http block (may be https ?)
|
||||
if (params.split_pos && params.split_pos < *size) *split_pos = params.split_pos;
|
||||
}
|
||||
}
|
8
tpws/tamper.h
Normal file
8
tpws/tamper.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
char *find_bin(void *data, size_t len, const void *blk, size_t blk_len);
|
||||
bool find_host(char **pHost,char *buf,size_t bs);
|
||||
void modify_tcp_segment(char *segment,size_t segment_buffer_size,size_t *size,size_t *split_pos);
|
850
tpws/tpws.c
Normal file
850
tpws/tpws.c
Normal file
@@ -0,0 +1,850 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <net/if.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netdb.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <getopt.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "tpws.h"
|
||||
#include "tpws_conn.h"
|
||||
#include "hostlist.h"
|
||||
#include "params.h"
|
||||
|
||||
struct params_s params;
|
||||
|
||||
bool bHup = false;
|
||||
static void onhup(int sig)
|
||||
{
|
||||
printf("HUP received !\n");
|
||||
if (params.hostlist)
|
||||
printf("Will reload hostlist on next request\n");
|
||||
bHup = true;
|
||||
}
|
||||
// should be called in normal execution
|
||||
void dohup()
|
||||
{
|
||||
if (bHup)
|
||||
{
|
||||
if (params.hostlist)
|
||||
{
|
||||
if (!LoadHostList(¶ms.hostlist, params.hostfile))
|
||||
{
|
||||
// what will we do without hostlist ?? sure, gonna die
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
bHup = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int8_t block_sigpipe()
|
||||
{
|
||||
sigset_t sigset;
|
||||
memset(&sigset, 0, sizeof(sigset));
|
||||
|
||||
//Get the old sigset, add SIGPIPE and update sigset
|
||||
if (sigprocmask(SIG_BLOCK, NULL, &sigset) == -1) {
|
||||
perror("sigprocmask (get)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sigaddset(&sigset, SIGPIPE) == -1) {
|
||||
perror("sigaddset");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {
|
||||
perror("sigprocmask (set)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static bool is_interface_online(const char *ifname)
|
||||
{
|
||||
struct ifreq ifr;
|
||||
int sock;
|
||||
|
||||
if ((sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_IP))==-1)
|
||||
return false;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
|
||||
ifr.ifr_name[IFNAMSIZ] = 0;
|
||||
ioctl(sock, SIOCGIFFLAGS, &ifr);
|
||||
close(sock);
|
||||
return !!(ifr.ifr_flags & IFF_UP);
|
||||
}
|
||||
|
||||
|
||||
static void exithelp()
|
||||
{
|
||||
printf(
|
||||
" --bind-addr=<ipv4_addr>|<ipv6_addr>\n"
|
||||
" --bind-iface4=<interface_name>\t; bind to the first ipv4 addr of interface\n"
|
||||
" --bind-iface6=<interface_name>\t; bind to the first ipv6 addr of interface\n"
|
||||
" --bind-linklocal=prefer|force\t; prefer or force ipv6 link local\n"
|
||||
" --bind-wait-ifup=<sec>\t\t; wait for interface to appear and up\n"
|
||||
" --bind-wait-ip=<sec>\t\t; after ifup wait for ip address to appear up to N seconds\n"
|
||||
" --bind-wait-ip-linklocal=<sec>\t; accept only link locals first N seconds then any\n"
|
||||
" --socks\t\t\t; implement socks4/5 proxy instead of transparent proxy\n"
|
||||
" --no-resolve\t\t\t; disable socks5 remote dns ability (resolves are not async, they block all activity)\n"
|
||||
" --local-rcvbuf=<bytes>\n"
|
||||
" --local-sndbuf=<bytes>\n"
|
||||
" --remote-rcvbuf=<bytes>\n"
|
||||
" --remote-sndbuf=<bytes>\n"
|
||||
" --skip-nodelay\t\t\t; do not set TCP_NODELAY option for outgoing connections (incompatible with split options)\n"
|
||||
" --port=<port>\n"
|
||||
" --maxconn=<max_connections>\n"
|
||||
" --maxfiles=<max_open_files>\t; should be at least (X*connections+16), where X=6 in tcp proxy mode, X=4 in tampering mode\n"
|
||||
" --max-orphan-time=<sec>\t; if local leg sends something and closes and remote leg is still connecting then cancel connection attempt after N seconds\n"
|
||||
" --daemon\t\t\t; daemonize\n"
|
||||
" --pidfile=<filename>\t\t; write pid to file\n"
|
||||
" --user=<username>\t\t; drop root privs\n"
|
||||
" --uid=uid[:gid]\t\t; drop root privs\n"
|
||||
" --debug=0|1|2\t\t\t; 0(default)=silent 1=verbose 2=debug\n"
|
||||
"\nTAMPERING:\n"
|
||||
" --hostlist=<filename>\t\t; only act on host in the list (one host per line, subdomains auto apply)\n"
|
||||
" --split-http-req=method|host\n"
|
||||
" --split-pos=<numeric_offset>\t; split at specified pos. invalidates split-http-req.\n"
|
||||
" --hostcase\t\t\t; change Host: => host:\n"
|
||||
" --hostspell\t\t\t; exact spelling of \"Host\" header. must be 4 chars. default is \"host\"\n"
|
||||
" --hostdot\t\t\t; add \".\" after Host: name\n"
|
||||
" --hosttab\t\t\t; add tab after Host: name\n"
|
||||
" --hostnospace\t\t\t; remove space after Host:\n"
|
||||
" --hostpad=<bytes>\t\t; add dummy padding headers before Host:\n"
|
||||
" --methodspace\t\t\t; add extra space after method\n"
|
||||
" --methodeol\t\t\t; add end-of-line before method\n"
|
||||
" --unixeol\t\t\t; replace 0D0A to 0A\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
static void cleanup_params()
|
||||
{
|
||||
if (params.hostlist)
|
||||
{
|
||||
StrPoolDestroy(¶ms.hostlist);
|
||||
params.hostlist = NULL;
|
||||
}
|
||||
}
|
||||
static void exithelp_clean()
|
||||
{
|
||||
cleanup_params();
|
||||
exithelp();
|
||||
}
|
||||
static void exit_clean(int code)
|
||||
{
|
||||
cleanup_params();
|
||||
exit(code);
|
||||
}
|
||||
void parse_params(int argc, char *argv[])
|
||||
{
|
||||
int option_index = 0;
|
||||
int v, i;
|
||||
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
memcpy(params.hostspell, "host", 4); // default hostspell
|
||||
params.maxconn = DEFAULT_MAX_CONN;
|
||||
params.max_orphan_time = DEFAULT_MAX_ORPHAN_TIME;
|
||||
|
||||
const struct option long_options[] = {
|
||||
{ "help",no_argument,0,0 },// optidx=0
|
||||
{ "h",no_argument,0,0 },// optidx=1
|
||||
{ "bind-addr",required_argument,0,0 },// optidx=2
|
||||
{ "bind-iface4",required_argument,0,0 },// optidx=3
|
||||
{ "bind-iface6",required_argument,0,0 },// optidx=4
|
||||
{ "bind-linklocal",required_argument,0,0 },// optidx=5
|
||||
{ "bind-wait-ifup",required_argument,0,0 },// optidx=6
|
||||
{ "bind-wait-ip",required_argument,0,0 },// optidx=7
|
||||
{ "bind-wait-ip-linklocal",required_argument,0,0 },// optidx=8
|
||||
{ "port",required_argument,0,0 },// optidx=9
|
||||
{ "daemon",no_argument,0,0 },// optidx=10
|
||||
{ "user",required_argument,0,0 },// optidx=11
|
||||
{ "uid",required_argument,0,0 },// optidx=12
|
||||
{ "maxconn",required_argument,0,0 },// optidx=13
|
||||
{ "maxfiles",required_argument,0,0 },// optidx=14
|
||||
{ "max-orphan-time",required_argument,0,0 },// optidx=15
|
||||
{ "hostcase",no_argument,0,0 },// optidx=16
|
||||
{ "hostspell",required_argument,0,0 },// optidx=17
|
||||
{ "hostdot",no_argument,0,0 },// optidx=18
|
||||
{ "hostnospace",no_argument,0,0 },// optidx=19
|
||||
{ "hostpad",required_argument,0,0 },// optidx=20
|
||||
{ "split-http-req",required_argument,0,0 },// optidx=21
|
||||
{ "split-pos",required_argument,0,0 },// optidx=22
|
||||
{ "methodspace",no_argument,0,0 },// optidx=23
|
||||
{ "methodeol",no_argument,0,0 },// optidx=24
|
||||
{ "hosttab",no_argument,0,0 },// optidx=25
|
||||
{ "unixeol",no_argument,0,0 },// optidx=26
|
||||
{ "hostlist",required_argument,0,0 },// optidx=27
|
||||
{ "pidfile",required_argument,0,0 },// optidx=28
|
||||
{ "debug",optional_argument,0,0 },// optidx=29
|
||||
{ "local-rcvbuf",required_argument,0,0 },// optidx=30
|
||||
{ "local-sndbuf",required_argument,0,0 },// optidx=31
|
||||
{ "remote-rcvbuf",required_argument,0,0 },// optidx=32
|
||||
{ "remote-sndbuf",required_argument,0,0 },// optidx=33
|
||||
{ "socks",no_argument,0,0 },// optidx=34
|
||||
{ "no-resolve",no_argument,0,0 },// optidx=35
|
||||
{ "skip-nodelay",no_argument,0,0 },// optidx=36
|
||||
{ NULL,0,NULL,0 }
|
||||
};
|
||||
while ((v = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1)
|
||||
{
|
||||
if (v) exithelp_clean();
|
||||
switch (option_index)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
exithelp_clean();
|
||||
break;
|
||||
case 2: /* bind-addr */
|
||||
strncpy(params.bindaddr, optarg, sizeof(params.bindaddr));
|
||||
params.bindaddr[sizeof(params.bindaddr) - 1] = 0;
|
||||
break;
|
||||
case 3: /* bind-iface4 */
|
||||
params.bind_if6=false;
|
||||
strncpy(params.bindiface, optarg, sizeof(params.bindiface));
|
||||
params.bindiface[sizeof(params.bindiface) - 1] = 0;
|
||||
break;
|
||||
case 4: /* bind-iface6 */
|
||||
params.bind_if6=true;
|
||||
strncpy(params.bindiface, optarg, sizeof(params.bindiface));
|
||||
params.bindiface[sizeof(params.bindiface) - 1] = 0;
|
||||
break;
|
||||
case 5: /* bind-linklocal */
|
||||
params.bindll = true;
|
||||
if (!strcmp(optarg, "force"))
|
||||
params.bindll_force=true;
|
||||
else if (strcmp(optarg, "prefer"))
|
||||
{
|
||||
fprintf(stderr, "invalid parameter in bind-linklocal : %s\n",optarg);
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 6: /* bind-wait-ifup */
|
||||
params.bind_wait_ifup = atoi(optarg);
|
||||
break;
|
||||
case 7: /* bind-wait-ip */
|
||||
params.bind_wait_ip = atoi(optarg);
|
||||
break;
|
||||
case 8: /* bind-wait-ip-linklocal */
|
||||
params.bind_wait_ip_ll = atoi(optarg);
|
||||
break;
|
||||
case 9: /* port */
|
||||
i = atoi(optarg);
|
||||
if (i <= 0 || i > 65535)
|
||||
{
|
||||
fprintf(stderr, "bad port number\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.port = (uint16_t)i;
|
||||
break;
|
||||
case 10: /* daemon */
|
||||
params.daemon = true;
|
||||
break;
|
||||
case 11: /* user */
|
||||
{
|
||||
struct passwd *pwd = getpwnam(optarg);
|
||||
if (!pwd)
|
||||
{
|
||||
fprintf(stderr, "non-existent username supplied\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.uid = pwd->pw_uid;
|
||||
params.gid = pwd->pw_gid;
|
||||
break;
|
||||
}
|
||||
case 12: /* uid */
|
||||
params.gid=0x7FFFFFFF; // default git. drop gid=0
|
||||
if (!sscanf(optarg,"%u:%u",¶ms.uid,¶ms.gid))
|
||||
{
|
||||
fprintf(stderr, "--uid should be : uid[:gid]\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 13: /* maxconn */
|
||||
params.maxconn = atoi(optarg);
|
||||
if (params.maxconn <= 0 || params.maxconn > 10000)
|
||||
{
|
||||
fprintf(stderr, "bad maxconn\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 14: /* maxfiles */
|
||||
params.maxfiles = atoi(optarg);
|
||||
if (params.maxfiles < 0)
|
||||
{
|
||||
fprintf(stderr, "bad maxfiles\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 15: /* max-orphan-time */
|
||||
params.max_orphan_time = atoi(optarg);
|
||||
if (params.max_orphan_time < 0)
|
||||
{
|
||||
fprintf(stderr, "bad max_orphan_time\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
break;
|
||||
case 16: /* hostcase */
|
||||
params.hostcase = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 17: /* hostspell */
|
||||
if (strlen(optarg) != 4)
|
||||
{
|
||||
fprintf(stderr, "hostspell must be exactly 4 chars long\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.hostcase = true;
|
||||
memcpy(params.hostspell, optarg, 4);
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 18: /* hostdot */
|
||||
params.hostdot = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 19: /* hostnospace */
|
||||
params.hostnospace = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 20: /* hostpad */
|
||||
params.hostpad = atoi(optarg);
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 21: /* split-http-req */
|
||||
if (!strcmp(optarg, "method"))
|
||||
params.split_http_req = split_method;
|
||||
else if (!strcmp(optarg, "host"))
|
||||
params.split_http_req = split_host;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Invalid argument for split-http-req\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 22: /* split-pos */
|
||||
i = atoi(optarg);
|
||||
if (i)
|
||||
params.split_pos = i;
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Invalid argument for split-pos\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 23: /* methodspace */
|
||||
params.methodspace = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 24: /* methodeol */
|
||||
params.methodeol = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 25: /* hosttab */
|
||||
params.hosttab = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 26: /* unixeol */
|
||||
params.unixeol = true;
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 27: /* hostlist */
|
||||
if (!LoadHostList(¶ms.hostlist, optarg))
|
||||
exit_clean(1);
|
||||
strncpy(params.hostfile,optarg,sizeof(params.hostfile));
|
||||
params.hostfile[sizeof(params.hostfile)-1]='\0';
|
||||
params.tamper = true;
|
||||
break;
|
||||
case 28: /* pidfile */
|
||||
strncpy(params.pidfile,optarg,sizeof(params.pidfile));
|
||||
params.pidfile[sizeof(params.pidfile)-1]='\0';
|
||||
break;
|
||||
case 29:
|
||||
params.debug = optarg ? atoi(optarg) : 1;
|
||||
break;
|
||||
case 30: /* local-rcvbuf */
|
||||
params.local_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 31: /* local-sndbuf */
|
||||
params.local_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 32: /* remote-rcvbuf */
|
||||
params.remote_rcvbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 33: /* remote-sndbuf */
|
||||
params.remote_sndbuf = atoi(optarg)/2;
|
||||
break;
|
||||
case 34: /* socks */
|
||||
params.proxy_type = CONN_TYPE_SOCKS;
|
||||
break;
|
||||
case 35: /* no-resolve */
|
||||
params.no_resolve = true;
|
||||
break;
|
||||
case 36: /* skip-nodelay */
|
||||
params.skip_nodelay = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!params.port)
|
||||
{
|
||||
fprintf(stderr, "Need port number\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
if (params.skip_nodelay && (params.split_http_req || params.split_pos))
|
||||
{
|
||||
fprintf(stderr, "Cannot split with --skip-nodelay\n");
|
||||
exit_clean(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void daemonize()
|
||||
{
|
||||
int pid,fd;
|
||||
|
||||
pid = fork();
|
||||
if (pid == -1)
|
||||
{
|
||||
perror("fork: ");
|
||||
exit(2);
|
||||
}
|
||||
else if (pid != 0)
|
||||
exit(0);
|
||||
|
||||
if (setsid() == -1)
|
||||
exit(2);
|
||||
if (chdir("/") == -1)
|
||||
exit(2);
|
||||
close(STDIN_FILENO);
|
||||
close(STDOUT_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
/* redirect fd's 0,1,2 to /dev/null */
|
||||
open("/dev/null", O_RDWR);
|
||||
/* stdin */
|
||||
fd=dup(0);
|
||||
/* stdout */
|
||||
fd=dup(0);
|
||||
/* stderror */
|
||||
}
|
||||
|
||||
static bool setpcap(cap_value_t *caps,int ncaps)
|
||||
{
|
||||
cap_t capabilities;
|
||||
|
||||
if (!(capabilities = cap_init()))
|
||||
return false;
|
||||
|
||||
if (ncaps && (cap_set_flag(capabilities, CAP_PERMITTED, ncaps, caps, CAP_SET) ||
|
||||
cap_set_flag(capabilities, CAP_EFFECTIVE, ncaps, caps, CAP_SET)))
|
||||
{
|
||||
cap_free(capabilities);
|
||||
return false;
|
||||
}
|
||||
if (cap_set_proc(capabilities))
|
||||
{
|
||||
cap_free(capabilities);
|
||||
return false;
|
||||
}
|
||||
cap_free(capabilities);
|
||||
return true;
|
||||
}
|
||||
static int getmaxcap()
|
||||
{
|
||||
int maxcap = CAP_LAST_CAP;
|
||||
FILE *F = fopen("/proc/sys/kernel/cap_last_cap","r");
|
||||
if (F)
|
||||
{
|
||||
int n=fscanf(F,"%d",&maxcap);
|
||||
fclose(F);
|
||||
}
|
||||
return maxcap;
|
||||
|
||||
}
|
||||
static bool dropcaps()
|
||||
{
|
||||
// must have CAP_SETPCAP at the end. its required to clear bounding set
|
||||
cap_value_t cap_values[] = {CAP_SETPCAP};
|
||||
int capct=sizeof(cap_values)/sizeof(*cap_values);
|
||||
int maxcap = getmaxcap();
|
||||
|
||||
if (setpcap(cap_values, capct))
|
||||
{
|
||||
for(int cap=0;cap<=maxcap;cap++)
|
||||
{
|
||||
if (cap_drop_bound(cap))
|
||||
{
|
||||
fprintf(stderr,"could not drop cap %d\n",cap);
|
||||
perror("cap_drop_bound");
|
||||
}
|
||||
}
|
||||
}
|
||||
// now without CAP_SETPCAP
|
||||
if (!setpcap(cap_values, capct - 1))
|
||||
{
|
||||
perror("setpcap");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool droproot()
|
||||
{
|
||||
if (params.uid || params.gid)
|
||||
{
|
||||
if (prctl(PR_SET_KEEPCAPS, 1L))
|
||||
{
|
||||
perror("prctl(PR_SET_KEEPCAPS): ");
|
||||
return false;
|
||||
}
|
||||
if (setgid(params.gid))
|
||||
{
|
||||
perror("setgid: ");
|
||||
return false;
|
||||
}
|
||||
if (setuid(params.uid))
|
||||
{
|
||||
perror("setuid: ");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return dropcaps();
|
||||
}
|
||||
|
||||
|
||||
static bool writepid(const char *filename)
|
||||
{
|
||||
FILE *F;
|
||||
if (!(F=fopen(filename,"w")))
|
||||
return false;
|
||||
fprintf(F,"%d",getpid());
|
||||
fclose(F);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static bool find_listen_addr(struct sockaddr_storage *salisten, bool bindll, int *if_index)
|
||||
{
|
||||
struct ifaddrs *addrs,*a;
|
||||
bool found=false;
|
||||
|
||||
if (getifaddrs(&addrs)<0)
|
||||
return false;
|
||||
|
||||
a = addrs;
|
||||
while (a)
|
||||
{
|
||||
if (a->ifa_addr)
|
||||
{
|
||||
if (a->ifa_addr->sa_family==AF_INET &&
|
||||
*params.bindiface && !params.bind_if6 && !strcmp(a->ifa_name, params.bindiface))
|
||||
{
|
||||
salisten->ss_family = AF_INET;
|
||||
memcpy(&((struct sockaddr_in*)salisten)->sin_addr, &((struct sockaddr_in*)a->ifa_addr)->sin_addr, sizeof(struct in_addr));
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
// ipv6 links locals are fe80::/10
|
||||
else if (a->ifa_addr->sa_family==AF_INET6
|
||||
&&
|
||||
(!*params.bindiface && bindll ||
|
||||
*params.bindiface && params.bind_if6 && !strcmp(a->ifa_name, params.bindiface))
|
||||
&&
|
||||
(!bindll ||
|
||||
((struct sockaddr_in6*)a->ifa_addr)->sin6_addr.s6_addr[0]==0xFE &&
|
||||
(((struct sockaddr_in6*)a->ifa_addr)->sin6_addr.s6_addr[1] & 0xC0)==0x80))
|
||||
{
|
||||
salisten->ss_family = AF_INET6;
|
||||
memcpy(&((struct sockaddr_in6*)salisten)->sin6_addr, &((struct sockaddr_in6*)a->ifa_addr)->sin6_addr, sizeof(struct in6_addr));
|
||||
if (if_index) *if_index = if_nametoindex(a->ifa_name);
|
||||
found=true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
a = a->ifa_next;
|
||||
}
|
||||
freeifaddrs(addrs);
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool set_ulimit()
|
||||
{
|
||||
FILE *F;
|
||||
int fdmax,fdmin_system,n,cur_lim=0;
|
||||
|
||||
if (!params.maxfiles)
|
||||
{
|
||||
// 4 fds per tamper connection (2 pipe + 2 socket), 6 fds for tcp proxy connection (4 pipe + 2 socket)
|
||||
// additional 1/2 for unpaired remote legs sending buffers
|
||||
// 16 for listen_fd, epoll, hostlist, ...
|
||||
fdmax = (params.tamper ? 4 : 6) * params.maxconn;
|
||||
fdmax += fdmax/2 + 16;
|
||||
}
|
||||
else
|
||||
fdmax = params.maxfiles;
|
||||
fdmin_system = fdmax + 4096;
|
||||
DBGPRINT("set_ulimit : fdmax=%d fdmin_system=%d",fdmax,fdmin_system)
|
||||
|
||||
if (!(F=fopen("/proc/sys/fs/file-max","r")))
|
||||
return false;
|
||||
n=fscanf(F,"%d",&cur_lim);
|
||||
fclose(F);
|
||||
if (!n) return false;
|
||||
DBGPRINT("set_ulimit : current system file-max=%d",cur_lim)
|
||||
if (cur_lim<fdmin_system)
|
||||
{
|
||||
DBGPRINT("set_ulimit : system fd limit is too low. trying to increase")
|
||||
if (!(F=fopen("/proc/sys/fs/file-max","w")))
|
||||
{
|
||||
fprintf(stderr,"set_ulimit : could not open /proc/sys/fs/file-max for write\n");
|
||||
return false;
|
||||
}
|
||||
n=fprintf(F,"%d",fdmin_system);
|
||||
fclose(F);
|
||||
if (!n)
|
||||
{
|
||||
fprintf(stderr,"set_ulimit : could not write to /proc/sys/fs/file-max\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
struct rlimit rlim = {fdmax,fdmax};
|
||||
n=setrlimit(RLIMIT_NOFILE, &rlim);
|
||||
if (n==-1) perror("setrlimit");
|
||||
return n!=-1;
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int listen_fd = -1;
|
||||
int yes = 1, retval = 0;
|
||||
int r;
|
||||
struct sockaddr_storage salisten;
|
||||
socklen_t salisten_len;
|
||||
int ipv6_only=0,if_index=0;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
parse_params(argc, argv);
|
||||
|
||||
memset(&salisten, 0, sizeof(salisten));
|
||||
if (*params.bindiface)
|
||||
{
|
||||
if (params.bind_wait_ifup > 0)
|
||||
{
|
||||
int sec=0;
|
||||
if (!is_interface_online(params.bindiface))
|
||||
{
|
||||
fprintf(stderr,"waiting ifup of %s for up to %d seconds...\n",params.bindiface,params.bind_wait_ifup);
|
||||
do
|
||||
{
|
||||
sleep(1);
|
||||
sec++;
|
||||
}
|
||||
while (!is_interface_online(params.bindiface) && sec<params.bind_wait_ifup);
|
||||
if (sec>=params.bind_wait_ifup)
|
||||
{
|
||||
printf("wait timed out\n");
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(if_index = if_nametoindex(params.bindiface)) && params.bind_wait_ip<=0)
|
||||
{
|
||||
printf("bad iface %s\n",params.bindiface);
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
if (*params.bindaddr)
|
||||
{
|
||||
if (inet_pton(AF_INET, params.bindaddr, &((struct sockaddr_in*)&salisten)->sin_addr))
|
||||
{
|
||||
salisten.ss_family = AF_INET;
|
||||
}
|
||||
else if (inet_pton(AF_INET6, params.bindaddr, &((struct sockaddr_in6*)&salisten)->sin6_addr))
|
||||
{
|
||||
salisten.ss_family = AF_INET6;
|
||||
ipv6_only = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("bad bind addr : %s\n", params.bindaddr);
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (*params.bindiface || params.bindll)
|
||||
{
|
||||
bool found;
|
||||
int sec=0;
|
||||
|
||||
if (params.bind_wait_ip > 0)
|
||||
{
|
||||
fprintf(stderr,"waiting for ip for %d seconds...\n", params.bind_wait_ip);
|
||||
if (params.bindll && !params.bindll_force && params.bind_wait_ip_ll>0)
|
||||
fprintf(stderr,"during the first %d seconds accepting only link locals...\n", params.bind_wait_ip_ll);
|
||||
}
|
||||
|
||||
for(;;)
|
||||
{
|
||||
found = find_listen_addr(&salisten,params.bindll,&if_index);
|
||||
if (found) break;
|
||||
|
||||
if (params.bindll && !params.bindll_force && sec>=params.bind_wait_ip_ll)
|
||||
if (found = find_listen_addr(&salisten,false,&if_index)) break;
|
||||
|
||||
if (sec>=params.bind_wait_ip)
|
||||
break;
|
||||
|
||||
sleep(1);
|
||||
sec++;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
printf("suitable ip address not found\n");
|
||||
goto exiterr;
|
||||
}
|
||||
ipv6_only=1;
|
||||
}
|
||||
else
|
||||
{
|
||||
salisten.ss_family = AF_INET6;
|
||||
// leave sin6_addr zero
|
||||
}
|
||||
}
|
||||
if (salisten.ss_family == AF_INET6)
|
||||
{
|
||||
salisten_len = sizeof(struct sockaddr_in6);
|
||||
((struct sockaddr_in6*)&salisten)->sin6_port = htons(params.port);
|
||||
((struct sockaddr_in6*)&salisten)->sin6_scope_id = if_index;
|
||||
}
|
||||
else
|
||||
{
|
||||
salisten_len = sizeof(struct sockaddr_in);
|
||||
((struct sockaddr_in*)&salisten)->sin_port = htons(params.port);
|
||||
}
|
||||
|
||||
if (params.daemon) daemonize();
|
||||
|
||||
if (*params.pidfile && !writepid(params.pidfile))
|
||||
{
|
||||
fprintf(stderr,"could not write pidfile\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
if ((listen_fd = socket(salisten.ss_family, SOCK_STREAM, 0)) == -1) {
|
||||
perror("socket: ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
if ((salisten.ss_family == AF_INET6) && setsockopt(listen_fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_only, sizeof(ipv6_only)) == -1)
|
||||
{
|
||||
perror("setsockopt (IPV6_ONLY): ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
perror("setsockopt (SO_REUSEADDR): ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
//Mark that this socket can be used for transparent proxying
|
||||
//This allows the socket to accept connections for non-local IPs
|
||||
if (params.proxy_type==CONN_TYPE_TRANSPARENT)
|
||||
{
|
||||
if (setsockopt(listen_fd, SOL_IP, IP_TRANSPARENT, &yes, sizeof(yes)) == -1)
|
||||
{
|
||||
perror("setsockopt (IP_TRANSPARENT): ");
|
||||
goto exiterr;
|
||||
}
|
||||
}
|
||||
|
||||
if (!set_socket_buffers(listen_fd, params.local_rcvbuf, params.local_sndbuf))
|
||||
goto exiterr;
|
||||
if (!params.local_rcvbuf)
|
||||
{
|
||||
// HACK : dont know why but if dont set RCVBUF explicitly RCVBUF of accept()-ed socket can be very large. may be linux bug ?
|
||||
int v,sz;
|
||||
sz=sizeof(int);
|
||||
if (!getsockopt(listen_fd,SOL_SOCKET,SO_RCVBUF,&v,&sz))
|
||||
{
|
||||
v/=2;
|
||||
setsockopt(listen_fd,SOL_SOCKET,SO_RCVBUF,&v,sizeof(int));
|
||||
}
|
||||
}
|
||||
|
||||
if (bind(listen_fd, (struct sockaddr *)&salisten, salisten_len) == -1) {
|
||||
perror("bind: ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
set_ulimit();
|
||||
|
||||
if (!droproot())
|
||||
{
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
fprintf(stderr,"Running as UID=%u GID=%u\n",getuid(),getgid());
|
||||
|
||||
if (listen(listen_fd, BACKLOG) == -1) {
|
||||
perror("listen: ");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//splice() causes the process to receive the SIGPIPE-signal if one part (for
|
||||
//example a socket) is closed during splice(). I would rather have splice()
|
||||
//fail and return -1, so blocking SIGPIPE.
|
||||
if (block_sigpipe() == -1) {
|
||||
fprintf(stderr, "Could not block SIGPIPE signal\n");
|
||||
goto exiterr;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Will listen to port %d\n", params.port);
|
||||
fprintf(stderr, params.proxy_type==CONN_TYPE_SOCKS ? "socks mode\n" : "transparent proxy mode\n");
|
||||
if (!params.tamper) fprintf(stderr, "TCP proxy mode (no tampering)\n");
|
||||
|
||||
signal(SIGHUP, onhup);
|
||||
|
||||
retval = event_loop(listen_fd);
|
||||
|
||||
close(listen_fd);
|
||||
cleanup_params();
|
||||
|
||||
fprintf(stderr, "Will exit\n");
|
||||
|
||||
return retval < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
|
||||
exiterr:
|
||||
if (listen_fd!=-1) close(listen_fd);
|
||||
cleanup_params();
|
||||
return EXIT_FAILURE;
|
||||
}
|
3
tpws/tpws.h
Normal file
3
tpws/tpws.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
void dohup();
|
1381
tpws/tpws_conn.c
Normal file
1381
tpws/tpws_conn.c
Normal file
File diff suppressed because it is too large
Load Diff
95
tpws/tpws_conn.h
Normal file
95
tpws/tpws_conn.h
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/queue.h>
|
||||
#include <time.h>
|
||||
|
||||
#define BACKLOG 10
|
||||
#define MAX_EPOLL_EVENTS 64
|
||||
#define IP_TRANSPARENT 19 //So that application compiles on OpenWRT
|
||||
#define SPLICE_LEN 65536
|
||||
#define DEFAULT_MAX_CONN 512
|
||||
#define DEFAULT_MAX_ORPHAN_TIME 5
|
||||
|
||||
int event_loop(int listen_fd);
|
||||
|
||||
//Three different states of a connection
|
||||
enum{
|
||||
CONN_UNAVAILABLE=0, // connecting
|
||||
CONN_AVAILABLE, // operational
|
||||
CONN_RDHUP, // received RDHUP, only sending unsent buffers. more RDHUPs are blocked
|
||||
CONN_CLOSED // will be deleted soon
|
||||
};
|
||||
typedef uint8_t conn_state_t;
|
||||
|
||||
// data in a send_buffer can be sent in several stages
|
||||
// pos indicates size of already sent data
|
||||
// when pos==len its time to free buffer
|
||||
struct send_buffer
|
||||
{
|
||||
char *data;
|
||||
size_t len,pos;
|
||||
};
|
||||
typedef struct send_buffer send_buffer_t;
|
||||
|
||||
enum{
|
||||
CONN_TYPE_TRANSPARENT=0,
|
||||
CONN_TYPE_SOCKS
|
||||
};
|
||||
typedef uint8_t conn_type_t;
|
||||
|
||||
struct tproxy_conn
|
||||
{
|
||||
bool remote; // false - accepted, true - connected
|
||||
int efd; // epoll fd
|
||||
int fd;
|
||||
int splice_pipe[2];
|
||||
conn_state_t state;
|
||||
conn_type_t conn_type;
|
||||
|
||||
struct tproxy_conn *partner; // other leg
|
||||
time_t orphan_since;
|
||||
|
||||
// socks5 state machine
|
||||
enum {
|
||||
S_WAIT_HANDSHAKE=0,
|
||||
S_WAIT_REQUEST,
|
||||
S_WAIT_CONNECTION,
|
||||
S_TCP
|
||||
} socks_state;
|
||||
uint8_t socks_ver;
|
||||
|
||||
// these value are used in flow control. we do not use ET (edge triggered) polling
|
||||
// if we dont disable notifications they will come endlessly until condition becomes false and will eat all cpu time
|
||||
bool bFlowIn,bFlowOut, bFlowInPrev,bFlowOutPrev, bPrevRdhup;
|
||||
|
||||
// total read,write
|
||||
size_t trd,twr;
|
||||
// number of epoll_wait events
|
||||
unsigned int event_count;
|
||||
|
||||
// connection is either spliced or send/recv
|
||||
// spliced connection have pipe buffering but also can have send_buffer's
|
||||
// pipe buffer comes first, then send_buffer's from 0 to countof(wr_buf)-1
|
||||
// send/recv connection do not have pipe and wr_unsent is meaningless, always 0
|
||||
ssize_t wr_unsent; // unsent bytes in the pipe
|
||||
// buffer 0 : send before split_pos
|
||||
// buffer 1 : send after split_pos
|
||||
// buffer 2 : after RDHUP read all and buffer to the partner
|
||||
// buffer 3 : after HUP read all and buffer to the partner
|
||||
// (2 and 3 should not be filled simultaneously, but who knows what can happen. if we have to refill non-empty buffer its FATAL)
|
||||
// all buffers are sent strictly from 0 to countof(wr_buf)-1
|
||||
// buffer cannot be sent if there is unsent data in a lower buffer
|
||||
struct send_buffer wr_buf[4];
|
||||
|
||||
//Create the struct which contains ptrs to next/prev element
|
||||
TAILQ_ENTRY(tproxy_conn) conn_ptrs;
|
||||
};
|
||||
typedef struct tproxy_conn tproxy_conn_t;
|
||||
|
||||
//Define the struct tailhead (code in sys/queue.h is quite intuitive)
|
||||
//Use tail queue for efficient delete
|
||||
TAILQ_HEAD(tailhead, tproxy_conn);
|
||||
|
||||
|
||||
bool set_socket_buffers(int fd, int rcvbuf, int sndbuf);
|
1217
tpws/uthash.h
Normal file
1217
tpws/uthash.h
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user