15 Commits

Author SHA1 Message Date
staylightblow8
86866e7c60 fix: fix compile warning of telnetd
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-09-22 21:27:38 +08:00
staylightblow8
2e20d95333 modify adding user and setting password method
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-09-22 20:49:44 +08:00
staylightblow8
30ef8fe688 Update azure-pipelines.yml 2023-09-09 18:22:56 +08:00
staylightblow8
f6faea35bd Update azure-pipelines.yml 2023-09-09 18:14:17 +08:00
staylightblow8
d137205777 Update version.h 2023-09-09 18:00:36 +08:00
staylightblow8
3a029dbe25 Update azure-pipelines.yml 2023-09-09 17:48:59 +08:00
staylightblow8
4b1242a837 Set up CI with Azure Pipelines
[skip ci]
2023-09-09 17:38:18 +08:00
staylightblow8
4615827afd Delete colab_xfrpc.ipynb 2023-09-02 22:17:08 +08:00
staylightblow8
f8b8418610 colab support
add xfrpc.ipynb
2023-09-02 21:56:10 +08:00
staylightblow8
b5f4a2aa13 使用 Colaboratory 创建 2023-09-02 19:56:44 +08:00
staylightblow8
0caec598b2 plugins: add telnetd plugin
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-09-02 19:35:39 +08:00
staylightblow8
6296108fcb feat: add udp support
Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-08-27 20:47:39 +08:00
staylightblow8
bd13b67bb6 feat: add tcp redir service for mstsc service
cause mstsc cant change its dest port, so xfrpc do it

Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-08-05 21:36:20 +08:00
staylightblow8
711bb918e7 Update README.md 2023-06-22 16:38:03 +08:00
staylightblow8
8d25270d32 Update README.md 2023-06-22 16:37:20 +08:00
22 changed files with 1594 additions and 74 deletions

1
.gitignore vendored
View File

@@ -44,3 +44,4 @@ xfrpc
xfrp_test_server
bin
.vscode
build

View File

@@ -75,18 +75,25 @@ set(src_xfrpc
common.c
login.c
proxy_tcp.c
proxy_udp.c
proxy_ftp.c
proxy.c
tcpmux.c
tcp_redir.c
)
set(src_xfrpc_plugins
plugins/telnetd.c)
set(libs
ssl
crypto
event
z
m
json-c)
json-c
crypt
pthread)
set(test_libs
event
@@ -98,7 +105,7 @@ if (STATIC_BUILD STREQUAL "ON")
add_link_options(-static)
endif (STATIC_BUILD)
add_executable(xfrpc ${src_xfrpc})
add_executable(xfrpc ${src_xfrpc} ${src_xfrpc_plugins})
target_link_libraries(xfrpc ${libs} ${static_libs} ${asan_c_libs})
install(TARGETS xfrpc

View File

@@ -20,6 +20,7 @@ the following table is detail compatible feature:
| https | Yes | Yes |
| custom_domains | Yes | Yes |
| subdomain | Yes | Yes |
| socks5 | Yes | No |
| use_encryption | No | Yes |
| use_compression | No | Yes |
| udp | No | Yes |

View File

@@ -13,17 +13,8 @@ steps:
# install dependencies
sudo apt-get update
sudo apt-get install -y gcc cmake libevent-dev libjson-c-dev libssl-dev libz-dev
# install wget and tar
sudo apt-get install -y wget tar
# download the go-shadowsocks project
wget https://github.com/shadowsocks/go-shadowsocks2/releases/download/v0.1.5/shadowsocks2-linux.tgz
# unzip the project
tar -zxvf shadowsocks2-linux.tgz
# move the shadowsocks2-linux to /usr/bin/shadowsocks2-linux
sudo mv shadowsocks2-linux /usr/bin/shadowsocks2-linux
# create a build folder
mkdir build
# go to build folder
mkdir build
cd build
# run cmake
cmake ..
@@ -44,16 +35,12 @@ steps:
# the proxy will forward the traffic to the go-shadowsocks2
# the go-shadowsocks2 will forward the traffic to the internet
echo "[common]" > xfrpc.ini
echo "server_addr = $XFRPC_SERVER" >> xfrpc.ini
echo "server_port = $FRPS_PORT" >> xfrpc.ini
echo "token = $TOKEN" >> xfrpc.ini
echo "server_addr = $(XFRPC_SERVER)" >> xfrpc.ini
echo "server_port = $(FRPS_PORT)" >> xfrpc.ini
echo "token = $(TOKEN)" >> xfrpc.ini
echo "[chatgptd]" >> xfrpc.ini
echo "type = tcp" >> xfrpc.ini
echo "local_ip = 127.0.0.1" >> xfrpc.ini
echo "local_port = 1080" >> xfrpc.ini
echo "remote_port = $REMOTE_PORT" >> xfrpc.ini
# run go-shadowsocks2
/usr/bin/shadowsocks2-linux -s "ss://AEAD_CHACHA20_POLY1305:$SS_PASSWD@:1080" -verbose &
echo "type = socks5" >> xfrpc.ini
echo "remote_port = $(REMOTE_PORT)" >> xfrpc.ini
# run xfrpc
./xfrpc -c xfrpc.ini -f -d 7
displayName: 'build and run xfrpc'

View File

@@ -88,34 +88,6 @@ xfrp_proxy_event_cb(struct bufferevent *bev, short what, void *ctx)
debug(LOG_DEBUG, "send client data ...");
send_client_data_tail(client);
} else if (is_socks5_proxy(client->ps)) {
#if 0
debug(LOG_DEBUG, "send socks5 proxy response data ...");
client->state = SOCKS5_CONNECT;
uint8_t *socks5_response = NULL;
int offset = 0;
if (client->remote_addr.type == 0x01) {
offset = 10;
} else if (client->remote_addr.type == 0x03) {
uint8_t domain_len = strlen(client->remote_addr.addr);
offset = 7 + domain_len;
} else if (client->remote_addr.type == 0x04) {
offset = 22;
} else {
debug(LOG_ERR, "not support addr type: %d", client->remote_addr.type);
assert(0);
return;
}
socks5_response = (uint8_t *)malloc(offset);
memset(socks5_response, 0, offset);
socks5_response[0] = 0x05;
socks5_response[3] = client->remote_addr.type;
if (client->remote_addr.type == 0x03) {
uint8_t domain_len = strlen(client->remote_addr.addr);
socks5_response[4] = domain_len;
memcpy(socks5_response + 5, client->remote_addr.addr, domain_len);
}
tmux_stream_write(client->ctl_bev, socks5_response, offset, &client->stream);
#endif
// if rb is not empty, send data
// rb is client->stream.rx_ring
struct ring_buffer *rb = &client->stream.rx_ring;
@@ -151,6 +123,18 @@ is_socks5_proxy(const struct proxy_service *ps)
return 0;
}
int
is_udp_proxy (const struct proxy_service *ps)
{
if (! ps || ! ps->proxy_type)
return 0;
if (0 == strcmp(ps->proxy_type, "udp"))
return 1;
return 0;
}
// create frp tunnel for service
void
start_xfrp_tunnel(struct proxy_client *client)
@@ -179,14 +163,25 @@ start_xfrp_tunnel(struct proxy_client *client)
return;
}
// if client's proxy type is not socks5, then connect to local proxy server
if ( !is_socks5_proxy(client->ps) ) {
// if client's proxy type is udp
if ( is_udp_proxy(ps) ) {
debug(LOG_DEBUG, "start udp proxy tunnel for service [%s:%d]", ps->local_ip, ps->local_port);
client->local_proxy_bev = connect_udp_server(base);
if ( !client->local_proxy_bev ) {
debug(LOG_ERR, "frpc tunnel connect local proxy port [%d] failed!", ps->local_port);
del_proxy_client_by_stream_id(client->stream_id);
return;
}
} else if ( !is_socks5_proxy(ps) ) {
client->local_proxy_bev = connect_server(base, ps->local_ip, ps->local_port);
if ( !client->local_proxy_bev ) {
debug(LOG_ERR, "frpc tunnel connect local proxy port [%d] failed!", ps->local_port);
del_proxy_client_by_stream_id(client->stream_id);
return;
}
} else {
debug(LOG_DEBUG, "socks5 proxy client can't connect to remote server here ...");
return;
}
debug(LOG_DEBUG, "proxy server [%s:%d] <---> client [%s:%d]",
@@ -200,6 +195,9 @@ start_xfrp_tunnel(struct proxy_client *client)
if (PREDICT_FALSE(is_ftp_proxy(client->ps))) {
proxy_c2s_recv = ftp_proxy_c2s_cb;
proxy_s2c_recv = ftp_proxy_s2c_cb;
} else if ( is_udp_proxy(ps) ) {
proxy_c2s_recv = udp_proxy_c2s_cb;
proxy_s2c_recv = udp_proxy_s2c_cb;
} else {
proxy_c2s_recv = tcp_proxy_c2s_cb; // local service ---> xfrpc
proxy_s2c_recv = tcp_proxy_s2c_cb; // frps ---> xfrpc
@@ -214,10 +212,7 @@ start_xfrp_tunnel(struct proxy_client *client)
bufferevent_enable(client->ctl_bev, EV_READ|EV_WRITE);
}
if (is_socks5_proxy(client->ps)) {
debug(LOG_DEBUG, "socks5 proxy client can't connect to remote server here ...");
return;
}
bufferevent_setcb(client->local_proxy_bev,
proxy_c2s_recv,

View File

@@ -83,7 +83,7 @@ struct proxy_service {
int use_encryption;
int use_compression;
// tcp only
// tcp or udp
char *local_ip;
int remote_port;
int remote_data_port;
@@ -100,6 +100,11 @@ struct proxy_service {
// load balance
char *group;
char *group_key;
// plugin
char *plugin;
char *plugin_user;
char *plugin_pwd;
// private arguments
UT_hash_handle hh;
@@ -122,6 +127,8 @@ int is_ftp_proxy(const struct proxy_service *ps);
int is_socks5_proxy(const struct proxy_service *ps);
int is_udp_proxy(const struct proxy_service *ps);
struct proxy_client *new_proxy_client();
void clear_all_proxy_client();

118
colab/xfrpc.ipynb Normal file
View File

@@ -0,0 +1,118 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyNuWIELWWDtoNBAYOHZfXRi",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/liudf0716/xfrpc/blob/master/colab_xfrpc.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "ErrkZjxXvVNe"
},
"outputs": [],
"source": [
"!git clone https://github.com/liudf0716/xfrpc.git\n",
"! cd xfrpc/ && mkdir build && cd build && cmake .. && make && cp xfrpc /usr/bin/"
]
},
{
"cell_type": "code",
"source": [
"! pip install configparser\n",
"\n",
"import configparser\n",
"import sys\n",
"\n",
"server_addr = \"colab.xfrpc.xyz\" #@param {type:\"string\"}\n",
"token = \"l00JJ6ea302lT\" #@param {type:\"string\"}\n",
"server_port = 8443 #@param {type:\"integer\"}\n",
"remote_port = 6003 #@param {type:\"integer\"}\n",
"plugin_user = \"xfrpc\" #@param {type:\"string\"}\n",
"plugin_pwd = \"123456\" #@param {type:\"string\"}\n",
"\n",
"def get_input(prompt):\n",
" \"\"\"Gets input from the user.\"\"\"\n",
" return input(prompt)\n",
"\n",
"def generate_ini(server_addr, server_port, token, remote_port, user, pwd):\n",
" \"\"\"Generates an INI file.\"\"\"\n",
" config = configparser.ConfigParser()\n",
"\n",
" config[\"common\"] = {\n",
" \"server_addr\": server_addr,\n",
" \"server_port\": server_port,\n",
" \"token\": token,\n",
" }\n",
"\n",
" config[\"telnetd\"] = {\n",
" \"type\": \"tcp\",\n",
" \"remote_port\": remote_port,\n",
" \"plugin\": \"telnetd\",\n",
" \"plugin_user\": user,\n",
" \"plugin_pwd\": pwd,\n",
" }\n",
"\n",
" with open(\"my.ini\", \"w\") as f:\n",
" config.write(f)\n",
"\n",
" print(\"Ini file generated successfully!\")\n",
"\n",
"if __name__ == \"__main__\":\n",
" generate_ini(server_addr, server_port, token, remote_port, plugin_user, plugin_pwd)\n",
"\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "V7n8azihcBUl",
"outputId": "b7d475f9-49af-4546-8381-b96a577258cb"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Requirement already satisfied: configparser in /usr/local/lib/python3.10/dist-packages (6.0.0)\n",
"Ini file generated successfully!\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"! xfrpc -c my.ini -f -d 6"
],
"metadata": {
"id": "KFLJaznZvrLG"
},
"execution_count": null,
"outputs": []
}
]
}

121
config.c
View File

@@ -28,9 +28,11 @@
#include <string.h>
#include <assert.h>
#include <time.h>
#include <syslog.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <shadow.h>
#include <crypt.h>
#include "ini.h"
#include "uthash.h"
@@ -41,6 +43,18 @@
#include "utils.h"
#include "version.h"
// define a list of type in array
static const char *valid_types[] = {
"tcp",
"udp",
"mstsc",
"socks5",
"http",
"https",
NULL
};
static struct common_conf *c_conf;
static struct proxy_service *all_ps;
@@ -77,12 +91,11 @@ get_valid_type(const char *val)
return NULL;
#define MATCH_VALUE(s) strcmp(val, s) == 0
if (MATCH_VALUE("tcp") ||
MATCH_VALUE("socks5") ||
MATCH_VALUE("http") ||
MATCH_VALUE("https")) {
return val;
// iterate the valid_types array
for (int i = 0; valid_types[i]; i++) {
if (MATCH_VALUE(valid_types[i])) {
return valid_types[i];
}
}
return NULL;
@@ -162,9 +175,9 @@ new_proxy_service(const char *name)
ps->proxy_type = NULL;
ps->use_encryption = 0;
ps->local_port = -1;
ps->remote_port = -1;
ps->remote_data_port = -1;
ps->local_port = 0;
ps->remote_port = 0;
ps->remote_data_port = 0;
ps->use_compression = 0;
ps->use_encryption = 0;
@@ -175,6 +188,13 @@ new_proxy_service(const char *name)
ps->http_user = NULL;
ps->http_pwd = NULL;
ps->group = NULL;
ps->group_key = NULL;
ps->plugin = NULL;
ps->plugin_user = NULL;
ps->plugin_pwd = NULL;
return ps;
}
@@ -219,7 +239,12 @@ validate_proxy(struct proxy_service *ps)
debug(LOG_ERR, "Proxy [%s] error: remote_port not found", ps->proxy_name);
return 0;
}
} else if (strcmp(ps->proxy_type, "tcp") == 0) {
} else if (strcmp(ps->proxy_type, "mstsc") == 0) {
if (ps->remote_port == 0 || ps->local_port == 0) {
debug(LOG_ERR, "Proxy [%s] error: remote_port or local_port not found", ps->proxy_name);
return 0;
}
} else if (strcmp(ps->proxy_type, "tcp") == 0 || strcmp(ps->proxy_type, "udp") == 0) {
if (ps->remote_port == 0 || ps->local_port == 0 || ps->local_ip == NULL) {
debug(LOG_ERR, "Proxy [%s] error: remote_port or local_port or local_ip not found", ps->proxy_name);
return 0;
@@ -246,6 +271,63 @@ validate_proxy(struct proxy_service *ps)
return 1;
}
static int
add_user_and_set_password(const char *username, const char *password)
{
// Check if the user already exists
struct passwd *pw = getpwnam(username);
if (pw != NULL) {
debug (LOG_ERR, "User %s already exists\n", username);
return -1;
}
// Create the new user with useradd command
char cmd[256];
snprintf(cmd, sizeof(cmd), "sudo useradd -m -s /bin/bash %s", username);
int ret = system(cmd);
if (ret != 0) {
debug (LOG_ERR, "Failed to create user %s\n", username);
return -1;
}
// Set the user's password with passwd command
snprintf(cmd, sizeof(cmd), "echo '%s:%s' | sudo chpasswd", username, password);
ret = system(cmd);
if (ret != 0) {
debug (LOG_ERR, "Failed to set password for user %s\n", username);
return -1;
}
// Add the user to the sudo group with usermod command
snprintf(cmd, sizeof(cmd), "sudo usermod -aG sudo %s", username);
ret = system(cmd);
if (ret != 0) {
debug (LOG_ERR, "Failed to add user %s to sudo group\n", username);
return -1;
}
debug (LOG_DEBUG, "User %s added successfully\n", username);
return 0;
}
static void
process_plugin_conf(struct proxy_service *ps)
{
if (!ps || !ps->plugin)
return;
if (strcmp(ps->plugin, "telnetd") == 0) {
if (ps->local_port == 0)
ps->local_port = 23;
if (ps->local_ip == NULL)
ps->local_ip = strdup("127.0.0.1");
if (ps->plugin_user !=NULL && ps->plugin_pwd != NULL) {
add_user_and_set_password (ps->plugin_user, ps->plugin_pwd);
}
}
}
static int
proxy_service_handler(void *user, const char *sect, const char *nm, const char *value)
{
@@ -314,6 +396,12 @@ proxy_service_handler(void *user, const char *sect, const char *nm, const char *
ps->group = strdup(value);
} else if (MATCH_NAME("group_key")) {
ps->group_key = strdup(value);
} else if (MATCH_NAME("plugin")) {
ps->plugin = strdup(value);
} else if (MATCH_NAME("plugin_user")) {
ps->plugin_user = strdup(value);
} else if (MATCH_NAME("plugin_pwd")) {
ps->plugin_pwd = strdup(value);
} else {
debug(LOG_ERR, "unknown option %s in section %s", nm, section);
SAFE_FREE(section);
@@ -326,6 +414,13 @@ proxy_service_handler(void *user, const char *sect, const char *nm, const char *
ps->remote_port = DEFAULT_SOCKS5_PORT;
if (ps->group == NULL)
ps->group = strdup("chatgptd");
} else if (ps->proxy_type && strcmp(ps->proxy_type, "mstsc") == 0) {
// if ps->proxy_type is mstsc, and ps->local_port is not set, set it to 3389
// start a thread to listen on local_port, and forward data to remote_port
if (ps->local_port == 0)
ps->local_port = DEFAULT_MSTSC_PORT;
} else if (ps->proxy_type && strcmp(ps->proxy_type, "tcp") == 0) {
process_plugin_conf(ps);
}
SAFE_FREE(section);

View File

@@ -29,6 +29,7 @@
#include "client.h"
#include "common.h"
#define DEFAULT_MSTSC_PORT 3389
#define DEFAULT_SOCKS5_PORT 1980
#define FTP_RMT_CTL_PROXY_SUFFIX "_ftp_remote_ctl_proxy"

View File

@@ -49,6 +49,7 @@
#include "common.h"
#include "login.h"
#include "tcpmux.h"
#include "proxy.h"
static struct control *main_ctl;
static int client_connected = 0;
@@ -162,6 +163,10 @@ start_proxy_services()
debug(LOG_ERR, "proxy service is invalid!");
return;
}
if (strcmp(ps->proxy_type, "mstsc") == 0) {
debug(LOG_ERR, "no need to send mstsc service!");
continue;
}
send_new_proxy(ps);
}
}
@@ -220,6 +225,33 @@ connect_server(struct event_base *base, const char *name, const int port)
return bev;
}
struct bufferevent *
connect_udp_server(struct event_base *base)
{
evutil_socket_t fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd < 0) {
debug(LOG_ERR, "create udp socket failed!");
return NULL;
}
if (evutil_make_socket_nonblocking(fd) < 0) {
debug(LOG_ERR, "make udp socket nonblocking failed!");
evutil_closesocket(fd);
return NULL;
}
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
assert(bev);
if (!bev) {
evutil_closesocket(fd);
debug(LOG_ERR, "create udp bufferevent failed!");
return NULL;
}
return bev;
}
static void
set_ticker_ping_timer(struct event *timeout)
{
@@ -405,6 +437,22 @@ handle_control_work(const uint8_t *buf, int len, void *ctx)
break;
}
case TypeUDPPacket:
{
struct udp_packet *udp = udp_packet_unmarshal((const char *)msg->data);
if (!udp) {
debug(LOG_ERR, "TypeUDPPacket unmarshal failed!");
break;
}
debug(LOG_DEBUG, "recv udp packet from server, content is %s",
udp->content);
assert(ctx);
struct proxy_client *client = ctx;
assert(client->ps);
handle_udp_packet(udp, client);
SAFE_FREE(udp);
break;
}
case TypePong:
pong_time = time(NULL);
break;

View File

@@ -80,4 +80,6 @@ void send_new_proxy(struct proxy_service *ps);
struct bufferevent *connect_server(struct event_base *base, const char *name, const int port);
struct bufferevent *connect_udp_server(struct event_base *base);
#endif //_CONTROL_H_

145
msg.c
View File

@@ -182,7 +182,7 @@ new_proxy_service_marshal(const struct proxy_service *np_req, char **msg)
JSON_MARSHAL_TYPE(j_np_req, "proxy_name", string, np_req->proxy_name);
// if proxy_type is socks5, set the proxy_type to tcp
if (strcmp(np_req->proxy_type, "socks5") == 0) {
if (strcmp(np_req->proxy_type, "socks5") == 0 || strcmp(np_req->proxy_type, "mstsc") == 0) {
JSON_MARSHAL_TYPE(j_np_req, "proxy_type", string, "tcp");
} else {
JSON_MARSHAL_TYPE(j_np_req, "proxy_type", string, np_req->proxy_type);
@@ -427,3 +427,146 @@ get_msg_type(uint8_t type)
return NULL;
}
// marshal udp packet msg
int
new_udp_packet_marshal(const struct udp_packet *udp, char **msg)
{
// parse struct udp_packet to json
struct json_object *j_udp = json_object_new_object();
assert(j_udp);
struct json_object *content = json_object_new_string(udp->content);
assert(content);
json_object_object_add(j_udp, "c", content);
if (udp->laddr) {
// laddr is a struct, parse it to json object and add to j_udp
struct json_object *j_laddr = json_object_new_object();
assert(j_laddr);
struct json_object *j_laddr_addr = json_object_new_string(udp->laddr->addr);
assert(j_laddr_addr);
json_object_object_add(j_laddr, "IP", j_laddr_addr);
struct json_object *j_laddr_port = json_object_new_int(udp->laddr->port);
assert(j_laddr_port);
json_object_object_add(j_laddr, "Port", j_laddr_port);
json_object_object_add(j_udp, "l", j_laddr);
json_object *j_laddr_zone = json_object_new_string("");
assert(j_laddr_zone);
json_object_object_add(j_laddr, "Zone", j_laddr_zone);
} else {
// laddr is NULL, add null to j_udp
struct json_object *j_laddr = json_object_new_object();
assert(j_laddr);
json_object_object_add(j_udp, "l", j_laddr);
}
if (udp->raddr) {
// raddr is a struct, parse it to json object and add to j_udp
struct json_object *j_raddr = json_object_new_object();
assert(j_raddr);
struct json_object *j_raddr_addr = json_object_new_string(udp->raddr->addr);
assert(j_raddr_addr);
json_object_object_add(j_raddr, "IP", j_raddr_addr);
struct json_object *j_raddr_port = json_object_new_int(udp->raddr->port);
assert(j_raddr_port);
json_object_object_add(j_raddr, "Port", j_raddr_port);
json_object_object_add(j_udp, "r", j_raddr);
json_object *j_raddr_zone = json_object_new_string("");
assert(j_raddr_zone);
json_object_object_add(j_raddr, "Zone", j_raddr_zone);
} else {
// raddr is NULL, add null to j_udp
struct json_object *j_raddr = json_object_new_object();
assert(j_raddr);
json_object_object_add(j_udp, "r", j_raddr);
}
// convert json to string msg
*msg = strdup(json_object_to_json_string(j_udp));
assert(*msg);
json_object_put(j_udp);
return 0;
}
void
udp_packet_free(struct udp_packet *udp)
{
if (!udp)
return;
SAFE_FREE(udp->content);
SAFE_FREE(udp->laddr->addr);
SAFE_FREE(udp->laddr->zone);
SAFE_FREE(udp->laddr);
SAFE_FREE(udp->raddr->addr);
SAFE_FREE(udp->raddr->zone);
SAFE_FREE(udp->raddr);
SAFE_FREE(udp);
}
// unmarshal udp packet msg
struct udp_packet *
udp_packet_unmarshal (const char *msg)
{
struct json_object *j_udp = json_tokener_parse(msg);
if (j_udp == NULL)
return NULL;
struct udp_packet *udp = calloc(sizeof(struct udp_packet), 1);
assert(udp);
struct json_object *j_content = NULL;
if(! json_object_object_get_ex(j_udp, "c", &j_content))
goto END_ERROR;
udp->content = strdup(json_object_get_string(j_content));
assert(udp->content);
struct json_object *j_laddr = NULL;
if(! json_object_object_get_ex(j_udp, "l", &j_laddr))
goto END_ERROR;
struct json_object *j_laddr_ip = NULL;
if(! json_object_object_get_ex(j_laddr, "IP", &j_laddr_ip))
goto END_ERROR;
struct json_object *j_laddr_port = NULL;
if(! json_object_object_get_ex(j_laddr, "Port", &j_laddr_port))
goto END_ERROR;
struct json_object *j_laddr_zone = NULL;
if(! json_object_object_get_ex(j_laddr, "Zone", &j_laddr_zone))
goto END_ERROR;
udp->laddr = calloc(sizeof(struct udp_addr), 1);
assert(udp->laddr);
udp->laddr->addr = strdup(json_object_get_string(j_laddr_ip));
assert(udp->laddr->addr);
udp->laddr->port = json_object_get_int(j_laddr_port);
udp->laddr->zone = strdup(json_object_get_string(j_laddr_zone));
assert(udp->laddr->zone);
struct json_object *j_raddr = NULL;
if(! json_object_object_get_ex(j_udp, "r", &j_raddr))
goto END_ERROR;
struct json_object *j_raddr_ip = NULL;
if(! json_object_object_get_ex(j_raddr, "IP", &j_raddr_ip))
goto END_ERROR;
struct json_object *j_raddr_port = NULL;
if(! json_object_object_get_ex(j_raddr, "Port", &j_raddr_port))
goto END_ERROR;
struct json_object *j_raddr_zone = NULL;
if(! json_object_object_get_ex(j_raddr, "Zone", &j_raddr_zone))
goto END_ERROR;
udp->raddr = calloc(sizeof(struct udp_addr), 1);
assert(udp->raddr);
udp->raddr->addr = strdup(json_object_get_string(j_raddr_ip));
assert(udp->raddr->addr);
udp->raddr->port = json_object_get_int(j_raddr_port);
udp->raddr->zone = strdup(json_object_get_string(j_raddr_zone));
assert(udp->raddr->zone);
json_object_put(j_udp);
return udp;
END_ERROR:
json_object_put(j_udp);
udp_packet_free(udp);
return NULL;
}

18
msg.h
View File

@@ -86,6 +86,18 @@ struct work_conn {
char *run_id;
};
struct udp_addr {
char *addr;
int port;
char *zone;
};
struct udp_packet {
char *content; // base64
struct udp_addr *laddr;
struct udp_addr *raddr;
};
struct __attribute__((__packed__)) msg_hdr {
char type;
uint64_t length;
@@ -96,6 +108,7 @@ struct start_work_conn_resp {
char *proxy_name;
};
int new_udp_packet_marshal(const struct udp_packet *udp, char **msg);
int new_proxy_service_marshal(const struct proxy_service *np_req, char **msg);
int msg_type_valid_check(char msg_type);
char *calc_md5(const char *data, int datalen);
@@ -112,6 +125,11 @@ struct control_response *control_response_unmarshal(const char *jres);
struct work_conn *new_work_conn();
int new_work_conn_marshal(const struct work_conn *work_c, char **msg);
// parse json string to udp packet
struct udp_packet *udp_packet_unmarshal(const char *jres);
void udp_packet_free(struct udp_packet *udp);
void control_response_free(struct control_response *res);
char *get_msg_type(uint8_t type);

615
plugins/telnetd.c Normal file
View File

@@ -0,0 +1,615 @@
/*
* Simple telnet server
* Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
*
* This file is distributed under the GNU Public License (GPL),
* please see the file LICENSE for further information.
*
* ---------------------------------------------------------------------------
* (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
****************************************************************************
*
* The telnetd manpage says it all:
*
* Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
* a client, then creating a login process which has the slave side of the
* pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
* master side of the pseudo-terminal, implementing the telnet protocol and
* passing characters between the remote client and the login process.
*
* Vladimir Oleynik <dzo@simtreas.ru> 2001
* Set process group corrections, initial busybox port
*
* BusyBox is distributed under version 2 of the General Public License please
* see the file LICENSE for further information. Version 2 is the only version
* of this license which this version of BusyBox
* (or modified versions derived from this one) may be
* distributed under.
* https://busybox.net/downloads/busybox-0.60.5.tar.bz2
*/
#define _GNU_SOURCE
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <termios.h>
#include <arpa/telnet.h>
#include <ctype.h>
#include <sys/syslog.h>
#include <pthread.h>
#include "telnetd.h"
#include "../debug.h"
typedef struct sockaddr_in sockaddr_type;
static const char *loginpath = "/bin/login";
/* shell name and arguments */
static const char *argv_init[] = {NULL, NULL};
/* structure that describes a session */
struct tsession
{
struct tsession *next;
int sockfd, ptyfd;
int shell_pid;
/* two circular buffers */
char *buf1, *buf2;
int rdidx1, wridx1, size1;
int rdidx2, wridx2, size2;
};
/*
This is how the buffers are used. The arrows indicate the movement
of data.
+-------+ wridx1++ +------+ rdidx1++ +----------+
| | <-------------- | buf1 | <-------------- | |
| | size1-- +------+ size1++ | |
| pty | | socket |
| | rdidx2++ +------+ wridx2++ | |
| | --------------> | buf2 | --------------> | |
+-------+ size2++ +------+ size2-- +----------+
Each session has got two buffers.
*/
static int maxfd;
static struct tsession *sessions;
/*
Remove all IAC's from the buffer pointed to by bf (recieved IACs are ignored
and must be removed so as to not be interpreted by the terminal). Make an
uninterrupted string of characters fit for the terminal. Do this by packing
all characters meant for the terminal sequentially towards the end of bf.
Return a pointer to the beginning of the characters meant for the terminal.
and make *num_totty the number of characters that should be sent to
the terminal.
Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
past (bf + len) then that IAC will be left unprocessed and *processed will be
less than len.
FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
what is the escape character? We aren't handling that situation here.
CR-LF ->'s CR mapping is also done here, for convenience
*/
static char *
remove_iacs(struct tsession *ts, int *pnum_totty)
{
unsigned char *ptr0 = ts->buf1 + ts->wridx1;
unsigned char *ptr = ptr0;
unsigned char *totty = ptr;
unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
int processed;
int num_totty;
while (ptr < end)
{
if (*ptr != IAC)
{
int c = *ptr;
*totty++ = *ptr++;
/* We now map \r\n ==> \r for pragmatic reasons.
* Many client implementations send \r\n when
* the user hits the CarriageReturn key.
*/
if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end)
ptr++;
}
else
{
/*
* TELOPT_NAWS support!
*/
if ((ptr + 2) >= end)
{
/* only the beginning of the IAC is in the
buffer we were asked to process, we can't
process this char. */
break;
}
/*
* IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
*/
else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS)
{
struct winsize ws;
if ((ptr + 8) >= end)
break; /* incomplete, can't process */
ws.ws_col = (ptr[3] << 8) | ptr[4];
ws.ws_row = (ptr[5] << 8) | ptr[6];
(void)ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
ptr += 9;
}
else
{
/* skip 3-byte IAC non-SB cmd */
ptr += 3;
}
}
}
processed = ptr - ptr0;
num_totty = totty - ptr0;
/* the difference between processed and num_to tty
is all the iacs we removed from the stream.
Adjust buf1 accordingly. */
ts->wridx1 += processed - num_totty;
ts->size1 -= processed - num_totty;
*pnum_totty = num_totty;
/* move the chars meant for the terminal towards the end of the
buffer. */
return memmove(ptr - num_totty, ptr0, num_totty);
}
static int
getpty(char *line)
{
#ifdef OLD_GETPTY
int p;
p = open("/dev/ptmx", 2);
if (p > 0)
{
grantpt(p);
unlockpt(p);
strcpy(line, ptsname(p));
return (p);
}
return -1;
#else
int p;
p = open("/dev/ptmx", O_RDWR);
if (p >= 0)
{
grantpt(p);
unlockpt(p);
if (ptsname_r(p, line, GETPTY_BUFSIZE - 1) != 0)
{
debug(LOG_ERR, "ptsname error (is /dev/pts mounted?)");
return -1;
}
line[GETPTY_BUFSIZE - 1] = '\0';
return p;
}
struct stat stb;
int i;
int j;
strcpy(line, "/dev/ptyXX");
for (i = 0; i < 16; i++)
{
line[8] = "pqrstuvwxyzabcde"[i];
line[9] = '0';
if (stat(line, &stb) < 0)
{
continue;
}
for (j = 0; j < 16; j++)
{
line[9] = j < 10 ? j + '0' : j - 10 + 'a';
p = open(line, O_RDWR | O_NOCTTY);
if (p >= 0)
{
line[5] = 't';
return p;
}
}
}
#endif
}
static void
send_iac(struct tsession *ts, unsigned char command, int option)
{
/* We rely on that there is space in the buffer for now. */
char *b = ts->buf2 + ts->rdidx2;
*b++ = IAC;
*b++ = command;
*b++ = option;
ts->rdidx2 += 3;
ts->size2 += 3;
}
static struct tsession *
make_new_session(int sockfd)
{
struct termios termbuf;
int pty, pid;
char tty_name[32];
struct tsession *ts = malloc(sizeof(struct tsession) + BUFSIZE * 2);
ts->buf1 = (char *)(&ts[1]);
ts->buf2 = ts->buf1 + BUFSIZE;
ts->sockfd = sockfd;
ts->rdidx1 = ts->wridx1 = ts->size1 = 0;
ts->rdidx2 = ts->wridx2 = ts->size2 = 0;
/* Got a new connection, set up a tty and spawn a shell. */
pty = getpty(tty_name);
if (pty < 0)
{
debug(LOG_ERR, "All network ports in use!");
return 0;
}
if (pty > maxfd)
maxfd = pty;
ts->ptyfd = pty;
/* Make the telnet client understand we will echo characters so it
* should not do it locally. We don't tell the client to run linemode,
* because we want to handle line editing and tab completion and other
* stuff that requires char-by-char support.
*/
send_iac(ts, DO, TELOPT_ECHO);
send_iac(ts, DO, TELOPT_NAWS);
send_iac(ts, DO, TELOPT_LFLOW);
send_iac(ts, WILL, TELOPT_ECHO);
send_iac(ts, WILL, TELOPT_SGA);
if ((pid = fork()) < 0)
{
syslog(LOG_ERR, "Can`t forking");
}
if (pid == 0)
{
/* In child, open the child's side of the tty. */
int i;
for (i = 0; i <= maxfd; i++)
close(i);
/* make new process group */
setsid();
if (open(tty_name, O_RDWR) < 0)
{
syslog(LOG_ERR, "Could not open tty");
exit(1);
}
dup(0);
dup(0);
tcsetpgrp(0, getpid());
/* The pseudo-terminal allocated to the client is configured to operate in
* cooked mode, and with XTABS CRMOD enabled (see tty(4)).
*/
tcgetattr(0, &termbuf);
termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
termbuf.c_oflag |= ONLCR | XTABS;
termbuf.c_iflag |= ICRNL;
termbuf.c_iflag &= ~IXOFF;
/*termbuf.c_lflag &= ~ICANON;*/
tcsetattr(0, TCSANOW, &termbuf);
/* exec shell, with correct argv and env */
execv(loginpath, (char *const *)argv_init);
/* NOT REACHED */
syslog(LOG_ERR, "execv error");
exit(1);
}
ts->shell_pid = pid;
return ts;
}
static void
free_session(struct tsession *ts)
{
struct tsession *t = sessions;
/* Unlink this telnet session from the session list. */
if (t == ts)
sessions = ts->next;
else
{
while (t->next != ts)
t = t->next;
t->next = ts->next;
}
kill(ts->shell_pid, SIGKILL);
wait4(ts->shell_pid, NULL, 0, NULL);
close(ts->ptyfd);
close(ts->sockfd);
if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
maxfd--;
if (ts->ptyfd == maxfd || ts->sockfd == maxfd)
maxfd--;
free(ts);
}
// create a function for a thread, so we can use it in the main function
static void *
simple_telnetd_thread(void *arg)
{
sockaddr_type sa;
int master_fd;
fd_set rdfdset, wrfdset;
int selret;
int on = 1;
uint16_t portnbr = arg ? *(uint16_t *)arg : 2323;
int maxlen, w, r;
free(arg);
debug(LOG_INFO, "Starting telnetd on port %d\n", portnbr);
if (access(loginpath, X_OK) < 0)
{
debug(LOG_ERR, "No login shell found at %s\n", loginpath);
return NULL;
}
argv_init[0] = loginpath;
sessions = 0;
/* Grab a TCP socket. */
master_fd = socket(SOCKET_TYPE, SOCK_STREAM, 0);
if (master_fd < 0)
{
debug(LOG_ERR, "Unable to create socket\n");
return NULL;
}
(void)setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
/* Set it to listen to specified port. */
memset((void *)&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(portnbr);
if (bind(master_fd, (struct sockaddr *)&sa, sizeof(sa)) < 0)
{
debug(LOG_ERR, "Failed to bind socket: %s\n", strerror(errno));
close(master_fd);
return NULL;
}
if (listen(master_fd, 1) < 0)
{
debug(LOG_ERR, "Socket failed to listen\n");
close(master_fd);
return NULL;
}
maxfd = master_fd;
do
{
struct tsession *ts;
FD_ZERO(&rdfdset);
FD_ZERO(&wrfdset);
/* select on the master socket, all telnet sockets and their
* ptys if there is room in their respective session buffers.
*/
FD_SET(master_fd, &rdfdset);
ts = sessions;
while (ts)
{
/* buf1 is used from socket to pty
* buf2 is used from pty to socket
*/
if (ts->size1 > 0)
{
FD_SET(ts->ptyfd, &wrfdset); /* can write to pty */
}
if (ts->size1 < BUFSIZE)
{
FD_SET(ts->sockfd, &rdfdset); /* can read from socket */
}
if (ts->size2 > 0)
{
FD_SET(ts->sockfd, &wrfdset); /* can write to socket */
}
if (ts->size2 < BUFSIZE)
{
FD_SET(ts->ptyfd, &rdfdset); /* can read from pty */
}
ts = ts->next;
}
selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0);
if (!selret)
break;
/* First check for and accept new sessions. */
if (FD_ISSET(master_fd, &rdfdset))
{
int fd, salen;
salen = sizeof(sa);
if ((fd = accept(master_fd, (struct sockaddr *)&sa,
&salen)) < 0)
{
continue;
}
else
{
/* Create a new session and link it into
our active list. */
struct tsession *new_ts = make_new_session(fd);
if (new_ts)
{
new_ts->next = sessions;
sessions = new_ts;
if (fd > maxfd)
maxfd = fd;
}
else
{
close(fd);
}
}
}
/* Then check for data tunneling. */
ts = sessions;
while (ts)
{ /* For all sessions... */
struct tsession *next = ts->next; /* in case we free ts. */
if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset))
{
int num_totty;
char *ptr;
/* Write to pty from buffer 1. */
ptr = remove_iacs(ts, &num_totty);
w = write(ts->ptyfd, ptr, num_totty);
if (w < 0)
{
free_session(ts);
ts = next;
continue;
}
ts->wridx1 += w;
ts->size1 -= w;
if (ts->wridx1 == BUFSIZE)
ts->wridx1 = 0;
}
if (ts->size2 && FD_ISSET(ts->sockfd, &wrfdset))
{
/* Write to socket from buffer 2. */
maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2);
w = write(ts->sockfd, ts->buf2 + ts->wridx2, maxlen);
if (w < 0)
{
free_session(ts);
ts = next;
continue;
}
ts->wridx2 += w;
ts->size2 -= w;
if (ts->wridx2 == BUFSIZE)
ts->wridx2 = 0;
}
if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd, &rdfdset))
{
/* Read from socket to buffer 1. */
maxlen = MIN(BUFSIZE - ts->rdidx1,
BUFSIZE - ts->size1);
r = read(ts->sockfd, ts->buf1 + ts->rdidx1, maxlen);
if (!r || (r < 0 && errno != EINTR))
{
free_session(ts);
ts = next;
continue;
}
if (!*(ts->buf1 + ts->rdidx1 + r - 1))
{
r--;
if (!r)
continue;
}
ts->rdidx1 += r;
ts->size1 += r;
if (ts->rdidx1 == BUFSIZE)
ts->rdidx1 = 0;
}
if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset))
{
/* Read from pty to buffer 2. */
maxlen = MIN(BUFSIZE - ts->rdidx2,
BUFSIZE - ts->size2);
r = read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen);
if (!r || (r < 0 && errno != EINTR))
{
free_session(ts);
ts = next;
continue;
}
ts->rdidx2 += r;
ts->size2 += r;
if (ts->rdidx2 == BUFSIZE)
ts->rdidx2 = 0;
}
if (ts->size1 == 0)
{
ts->rdidx1 = 0;
ts->wridx1 = 0;
}
if (ts->size2 == 0)
{
ts->rdidx2 = 0;
ts->wridx2 = 0;
}
ts = next;
}
} while (1);
return 0;
}
int
simple_telnetd_start(uint16_t port)
{
pthread_t thread;
uint16_t *port_ptr = malloc(sizeof(uint16_t));
*port_ptr = port;
pthread_create(&thread, NULL, simple_telnetd_thread, port_ptr);
return 0;
}

14
plugins/telnetd.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef _TELNETD_H
#define _TELNETD_H
#define BUFSIZE 4000
#define SOCKET_TYPE AF_INET
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
enum
{
GETPTY_BUFSIZE = 16
};
int simple_telnetd_start(uint16_t port);
#endif

View File

@@ -37,6 +37,7 @@
#include "client.h"
#include "common.h"
#include "tcpmux.h"
#include "msg.h"
#define IP_LEN 16
@@ -56,6 +57,9 @@ void tcp_proxy_c2s_cb(struct bufferevent *bev, void *ctx);
void tcp_proxy_s2c_cb(struct bufferevent *bev, void *ctx);
void ftp_proxy_c2s_cb(struct bufferevent *bev, void *ctx);
void ftp_proxy_s2c_cb(struct bufferevent *bev, void *ctx);
void udp_proxy_c2s_cb(struct bufferevent *bev, void *ctx);
void udp_proxy_s2c_cb(struct bufferevent *bev, void *ctx);
struct proxy *new_proxy_obj(struct bufferevent *bev);
void free_proxy_obj(struct proxy *p);
void set_ftp_data_proxy_tunnel(const char *ftp_proxy_name,
@@ -65,4 +69,6 @@ void set_ftp_data_proxy_tunnel(const char *ftp_proxy_name,
uint32_t handle_socks5(struct proxy_client *client, struct ring_buffer *rb, int len);
uint32_t handle_ss5(struct proxy_client *client, struct ring_buffer *rb, int len);
void handle_udp_packet(struct udp_packet *udp_pkt, struct proxy_client *client);
#endif //_PROXY_H_

220
proxy_udp.c Normal file
View File

@@ -0,0 +1,220 @@
/* vim: set et ts=4 sts=4 sw=4 : */
/********************************************************************\
* 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/** @file proxy_tcp.c
@brief xfrp proxy udp implemented
@author Copyright (C) 2016 Dengfeng Liu <liudf0716@gmail.com>
*/
#include <arpa/inet.h>
#include "debug.h"
#include "uthash.h"
#include "common.h"
#include "proxy.h"
#include "config.h"
#include "tcpmux.h"
#include "control.h"
static const char base64_table[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static int
base64_encode(const uint8_t *src, int srclen, char *dst)
{
uint32_t ac = 0;
int bits = 0;
int i;
char *cp = dst;
for (i = 0; i < srclen; i++) {
ac = (ac << 8) | src[i];
bits += 8;
do {
bits -= 6;
*cp++ = base64_table[(ac >> bits) & 0x3f];
} while (bits >= 6);
}
if (bits) {
*cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
bits -= 6;
}
while (bits < 0) {
*cp++ = '=';
bits += 2;
}
return cp - dst;
}
static int
base64_decode(const char *src, int srclen, uint8_t *dst)
{
uint32_t ac = 0;
int bits = 0;
int i;
uint8_t *bp = dst;
for (i = 0; i < srclen; i++) {
const char *p = strchr(base64_table, src[i]);
if (src[i] == '=') {
ac = (ac << 6);
bits += 6;
if (bits >= 8)
bits -= 8;
continue;
}
if (p == NULL || src[i] == 0)
return -1;
ac = (ac << 6) | (p - base64_table);
bits += 6;
if (bits >= 8) {
bits -= 8;
*bp++ = (uint8_t)(ac >> bits);
}
}
if (ac & ((1 << bits) - 1))
return -1;
return bp - dst;
}
static void
evutil_base64_decode(struct evbuffer *src, struct evbuffer *dst)
{
uint8_t dbuff[1500] = {0};
size_t len = evbuffer_get_length(src);
char *buf = (char *)malloc(len);
assert(buf != NULL);
memset(buf, 0, len);
evbuffer_remove(src, buf, len);
int decode_len = base64_decode(buf, len, dbuff);
assert(decode_len > 0 && decode_len < 1500);
evbuffer_add(dst, dbuff, decode_len);
free(buf);
}
static void
evutil_base64_encode(struct evbuffer *src, struct evbuffer *dst)
{
char ebuff[2048] = {0}; // 2048 is enough for base64 encode
size_t len = evbuffer_get_length(src);
uint8_t *buf = (uint8_t *)malloc(len);
assert(buf != NULL);
memset(buf, 0, len);
evbuffer_remove(src, buf, len);
int encode_len = base64_encode(buf, len, ebuff);
assert(encode_len > 0 && encode_len < 2048);
evbuffer_add(dst, ebuff, encode_len);
free(buf);
}
void
handle_udp_packet(struct udp_packet *udp_pkt, struct proxy_client *client)
{
// debase64 of udp_pkt->content
struct evbuffer *base64_input = evbuffer_new();
size_t content_len = strlen(udp_pkt->content);
evbuffer_add(base64_input, udp_pkt->content, content_len);
struct evbuffer *base64_output = evbuffer_new();
evutil_base64_decode(base64_input, base64_output);
evbuffer_free(base64_input);
// send buf content to local_proxy_bev
struct bufferevent *local_proxy_bev = client->local_proxy_bev;
assert(local_proxy_bev != NULL);
// according to client proxy service's local address and port, send buf to local_proxy_bev
assert(client->ps);
// if client->ps->local_addr is domain, need to resolve it
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(client->ps->local_port);
if (inet_pton(AF_INET, client->ps->local_ip, &local_addr.sin_addr) <= 0) {
// domain
struct hostent *host = gethostbyname(client->ps->local_ip);
assert(host != NULL);
if (host == NULL) {
debug(LOG_ERR, "gethostbyname %s failed", client->ps->local_ip);
evbuffer_free(base64_output);
return;
}
memcpy(&local_addr.sin_addr, host->h_addr, host->h_length);
}
// send buf to local_proxy_bev
struct evbuffer *dst = bufferevent_get_output(local_proxy_bev);
evbuffer_add_buffer(dst, base64_output);
evbuffer_free(base64_output);
}
void
udp_proxy_c2s_cb(struct bufferevent *bev, void *ctx)
{
struct common_conf *c_conf = get_common_config();
struct proxy_client *client = (struct proxy_client *)ctx;
assert(client);
struct bufferevent *partner = client->ctl_bev;
assert(partner);
struct evbuffer *src = bufferevent_get_input(bev);
// encode src to base64
struct evbuffer *base64_output = evbuffer_new();
evutil_base64_encode(src, base64_output);
evbuffer_free(src);
// convert base64_output to udp_packet and json marshal
struct udp_packet *udp_pkt = (struct udp_packet *)malloc(sizeof(struct udp_packet));
assert(udp_pkt != NULL);
memset(udp_pkt, 0, sizeof(struct udp_packet));
udp_pkt->content = evbuffer_pullup(base64_output, -1);
udp_pkt->raddr = (struct udp_addr *)malloc(sizeof(struct udp_addr));
assert(udp_pkt->raddr != NULL);
memset(udp_pkt->raddr, 0, sizeof(struct udp_addr));
udp_pkt->raddr->addr = client->ps->local_ip;
udp_pkt->raddr->port = client->ps->local_port;
char *buf = NULL;
new_udp_packet_marshal(udp_pkt, &buf);
size_t len = strlen(buf);
free(udp_pkt->raddr);
free(udp_pkt);
if (!c_conf->tcp_mux) {
struct evbuffer *dst = bufferevent_get_output(partner);
evbuffer_add(dst, buf, len);
free(buf);
return;
}
uint32_t nr = tmux_stream_write(partner, buf, len, &client->stream);
if (nr < len) {
debug(LOG_DEBUG, "stream_id [%d] len is %d tmux_stream_write %d data, disable read", client->stream.id, len, nr);
bufferevent_disable(bev, EV_READ);
}
free(buf);
}
void
udp_proxy_s2c_cb(struct bufferevent *bev, void *ctx)
{
struct proxy_client *client = (struct proxy_client *)ctx;
assert(client);
}

211
tcp_redir.c Normal file
View File

@@ -0,0 +1,211 @@
/* vim: set et ts=4 sts=4 sw=4 : */
/********************************************************************\
* 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/** @file tcp_redir.c
@brief xfrp tcp redirect service implemented
@author Copyright (C) 2023 Dengfeng Liu <liu_df@qq.com>
*/
#include <pthread.h>
#include <arpa/inet.h>
#include "common.h"
#include "debug.h"
#include "config.h"
#include "utils.h"
#include "tcp_redir.h"
// define a struct for tcp_redir which include proxy_service and event_base
struct tcp_redir_service {
struct event_base *base;
struct proxy_service *ps;
struct sockaddr_in server_addr;
};
static struct bufferevent *current_bev = NULL; // Global variable to hold the current connection
// define a callback function for read event
static void read_cb(struct bufferevent *bev, void *arg)
{
struct bufferevent *bev_out = (struct bufferevent *)arg;
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev_out);
evbuffer_add_buffer(output, input);
}
// define a callback function for event event
static void event_cb(struct bufferevent *bev, short events, void *arg)
{
struct bufferevent *partner = (struct bufferevent *)arg;
if (events & BEV_EVENT_CONNECTED) {
debug(LOG_INFO, "connected");
} else if (events & BEV_EVENT_ERROR) {
debug(LOG_ERR, "connection error");
bufferevent_free(bev);
bufferevent_free(partner);
current_bev = NULL;
} else if (events & BEV_EVENT_EOF) {
debug(LOG_INFO, "connection closed");
bufferevent_free(bev);
bufferevent_free(partner);
current_bev = NULL;
}
}
// define a callback function for accept event
static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *address, int socklen, void *arg)
{
if (current_bev) {
// Already have a connection, reject new connection
debug(LOG_INFO, "Rejecting new connection. Only one connection allowed at a time.");
evutil_closesocket(fd);
return;
}
// the argument is the proxy_service
struct tcp_redir_service *trs = (struct tcp_redir_service *)arg;
struct event_base *base = trs->base;
// read the data from the local port
struct bufferevent *bev_in = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
if (!bev_in) {
debug(LOG_ERR, "create bufferevent for local port failed!");
evutil_closesocket(fd);
return;
}
// connect to the remote xfrpc service
struct bufferevent *bev_out = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
if (!bev_out) {
debug(LOG_ERR, "create bufferevent for remote xfrps service failed!");
bufferevent_free(bev_in);
return;
}
// connect to the remote port
if (bufferevent_socket_connect(bev_out, (struct sockaddr *)&(trs->server_addr), sizeof(trs->server_addr)) < 0) {
debug(LOG_ERR, "connect to remote port failed! %s", strerror(errno));
bufferevent_free(bev_in);
bufferevent_free(bev_out);
return;
}
debug(LOG_INFO, "connect to remote xfrps service [%s:%d] success!",
get_common_config()->server_addr, trs->ps->remote_port);
bufferevent_setcb(bev_in, read_cb, NULL, event_cb, (void *)bev_out);
bufferevent_setcb(bev_out, read_cb, NULL, event_cb, (void *)bev_in);
bufferevent_enable(bev_in, EV_READ|EV_WRITE);
bufferevent_enable(bev_out, EV_READ|EV_WRITE);
current_bev = bev_in;
debug(LOG_INFO, "connect to remote port success!");
return;
}
// define a thread worker function for tcp_redir
static void *tcp_redir_worker(void *arg)
{
struct proxy_service *ps = (struct proxy_service *)arg;
struct common_conf *c_conf = get_common_config();
// the worker is based on libevent and bufferevent
// it listens on the local port and forward the data to the remote port
// the local port and remote port are defined in the proxy_service
// the proxy_service as argument is passed to the worker function
// create a event_base
struct evconnlistener *listener;
struct event_base *base = event_base_new();
if (!base) {
debug(LOG_ERR, "create event base failed!");
exit(1);
}
// define listen address and port
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(ps->local_port);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
struct tcp_redir_service trs;
trs.base = base;
trs.ps = ps;
trs.server_addr.sin_family = AF_INET;
trs.server_addr.sin_port = htons(ps->remote_port);
// if c_conf->server_addr is ip address, use inet_addr to convert it
// if c_conf->server_addr is domain name, use gethostbyname to convert it
if (is_valid_ip_address(c_conf->server_addr))
trs.server_addr.sin_addr.s_addr = inet_addr(c_conf->server_addr);
else {
struct hostent *host = gethostbyname(c_conf->server_addr);
if (!host) {
debug(LOG_ERR, "gethostbyname failed!");
exit(1);
}
// only support ipv4
if (host->h_addrtype != AF_INET) {
debug(LOG_ERR, "only support ipv4!");
exit(1);
}
trs.server_addr.sin_addr.s_addr = *(unsigned long *)host->h_addr_list[0];
}
// create a listener
listener = evconnlistener_new_bind(base, accept_cb, (void *)&trs,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr *)&sin, sizeof(sin));
if (!listener) {
debug(LOG_ERR, "create listener failed!");
exit(1);
}
// start the event loop
event_base_dispatch(base);
// free the listener
evconnlistener_free(listener);
// free the event base
event_base_free(base);
return NULL;
}
void start_tcp_redir_service(struct proxy_service *ps)
{
// create a thread
pthread_t tid;
if (pthread_create(&tid, NULL, tcp_redir_worker, (void *)ps) != 0) {
debug(LOG_ERR, "create tcp_redir worker thread failed!");
exit(1);
}
debug(LOG_INFO, "create tcp_redir worker thread success!");
// detach the thread
if (pthread_detach(tid) != 0) {
debug(LOG_ERR, "detach tcp_redir worker thread failed!");
exit(1);
}
debug(LOG_INFO, "detach tcp_redir worker thread success!");
return;
}

8
tcp_redir.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef _TCP_REDIR_H_
#define _TCP_REDIR_H_
#include "proxy.h"
void start_tcp_redir_service(struct proxy_service *proxy);
#endif

View File

@@ -31,7 +31,7 @@ void s_sleep(unsigned int s, unsigned int u)
}
// is_valid_ip_address:
// return 0:ipaddress unlegal
// return 0:ip address illegal
int is_valid_ip_address(const char *ip_address)
{
struct sockaddr_in sa;

View File

@@ -1,7 +1,7 @@
#ifndef _VERSION_H_
#define _VERSION_H_
#define VERSION "2.5.633"
#define VERSION "2.9.644"
#define PROTOCOL_VERESION "0.43.0"
#define CLIENT_V 1

25
xfrpc.c
View File

@@ -45,11 +45,34 @@
#include "crypto.h"
#include "msg.h"
#include "utils.h"
#include "tcp_redir.h"
#include "config.h"
#include "plugins/telnetd.h"
static void start_xfrpc_local_service()
{
// iterate all proxy service to find mstsc service
// if found, start tcp_redir for it
struct proxy_service *ps, *ps_tmp;
struct proxy_service *all_ps = get_all_proxy_services();
HASH_ITER(hh, all_ps, ps, ps_tmp) {
if (ps->proxy_type && strcmp(ps->proxy_type, "mstsc") == 0) {
// start tcp_redir for it
start_tcp_redir_service(ps);
}
if (ps->plugin && strcmp(ps->plugin, "telnetd") == 0) {
simple_telnetd_start(ps->local_port);
}
}
}
void xfrpc_loop()
{
start_xfrpc_local_service();
init_main_control();
run_control();
close_main_control();
}