9 changed files with 406 additions and 6 deletions
Unified View
Diff Options
-
4CMakeLists.txt
-
8Changes
-
2README.md
-
12config.json
-
2configure.ac
-
8debian/changelog
-
351jconf.c
-
4snap/snapcraft.yaml
-
21test.json
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"server":"la.maxlv.net", |
||||
|
"server_port":1080, |
||||
|
"local_address":"0.0.0.0", |
||||
|
"local_port":1080, |
||||
|
"password":"CCCCCCCC", |
||||
|
"method":"aes-256-gcm", |
||||
|
"timeout":60, |
||||
|
"mode":"tcp_and_udp", |
||||
|
"plugin":"obfs-local", |
||||
|
"plugin_opts":"obfs=http;obfs-host=www.CCCCCCC.com" |
||||
|
} |
@ -0,0 +1,351 @@ |
|||||
|
/* |
||||
|
* jconf.c - Parse the JSON format config file |
||||
|
* |
||||
|
* Copyright (C) 2013 - 2018, Max Lv <max.c.lv@gmail.com> |
||||
|
* |
||||
|
* This file is part of the shadowsocks-libev. |
||||
|
* shadowsocks-libev 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 3 of the License, or |
||||
|
* (at your option) any later version. |
||||
|
* |
||||
|
* shadowsocks-libev 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 shadowsocks-libev; see the file COPYING. If not, see |
||||
|
* <http://www.gnu.org/licenses/>. |
||||
|
*/ |
||||
|
|
||||
|
#include <stdlib.h> |
||||
|
#include <stdio.h> |
||||
|
#include <unistd.h> |
||||
|
#include <string.h> |
||||
|
#include <time.h> |
||||
|
|
||||
|
#include "netutils.h" |
||||
|
#include "utils.h" |
||||
|
#include "jconf.h" |
||||
|
#include "json.h" |
||||
|
#include "string.h" |
||||
|
|
||||
|
#include <libcork/core.h> |
||||
|
|
||||
|
#define check_json_value_type(value, expected_type, message) \ |
||||
|
do { \ |
||||
|
if ((value)->type != (expected_type)) \ |
||||
|
FATAL((message)); \ |
||||
|
} while (0) |
||||
|
|
||||
|
static char * |
||||
|
to_string(const json_value *value) |
||||
|
{ |
||||
|
if (value->type == json_string) { |
||||
|
return ss_strndup(value->u.string.ptr, value->u.string.length); |
||||
|
} else if (value->type == json_integer) { |
||||
|
return strdup(ss_itoa(value->u.integer)); |
||||
|
} else if (value->type == json_null) { |
||||
|
return NULL; |
||||
|
} else { |
||||
|
LOGE("%d", value->type); |
||||
|
FATAL("Invalid config format."); |
||||
|
} |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
free_addr(ss_addr_t *addr) |
||||
|
{ |
||||
|
ss_free(addr->host); |
||||
|
ss_free(addr->port); |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
parse_addr(const char *str_in, ss_addr_t *addr) |
||||
|
{ |
||||
|
if (str_in == NULL) |
||||
|
return; |
||||
|
|
||||
|
int ipv6 = 0, ret = -1, n = 0, len; |
||||
|
char *pch; |
||||
|
char *str = strdup(str_in); |
||||
|
len = strlen(str_in); |
||||
|
|
||||
|
struct cork_ip ip; |
||||
|
if (cork_ip_init(&ip, str) != -1) { |
||||
|
addr->host = str; |
||||
|
addr->port = NULL; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
pch = strchr(str, ':'); |
||||
|
while (pch != NULL) { |
||||
|
n++; |
||||
|
ret = pch - str; |
||||
|
pch = strchr(pch + 1, ':'); |
||||
|
} |
||||
|
|
||||
|
if (n > 1) { |
||||
|
ipv6 = 1; |
||||
|
if (str[ret - 1] != ']') { |
||||
|
ret = -1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (ret == -1) { |
||||
|
if (ipv6) { |
||||
|
addr->host = ss_strndup(str + 1, strlen(str) - 2); |
||||
|
} else { |
||||
|
addr->host = strdup(str); |
||||
|
} |
||||
|
addr->port = NULL; |
||||
|
} else { |
||||
|
if (ipv6) { |
||||
|
addr->host = ss_strndup(str + 1, ret - 2); |
||||
|
} else { |
||||
|
addr->host = ss_strndup(str, ret); |
||||
|
} |
||||
|
if (ret < len - 1) |
||||
|
{ |
||||
|
addr->port = strdup(str + ret + 1); |
||||
|
} else { |
||||
|
addr->port = NULL; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
free(str); |
||||
|
} |
||||
|
|
||||
|
static int |
||||
|
parse_dscp(char *str) |
||||
|
{ |
||||
|
size_t str_len = strlen(str); |
||||
|
|
||||
|
// Pre-defined values (EF, CSx, AFxy) |
||||
|
if (str_len == 2 && strcasecmp(str, "EF") == 0) { |
||||
|
return DSCP_EF; |
||||
|
} |
||||
|
|
||||
|
if (str_len == DSCP_CS_LEN && strncasecmp(str, "CS", 2) == 0) { |
||||
|
if (str[2] >= '0' && str[2] <= '7') { |
||||
|
// CSx = 8x |
||||
|
return (str[2] - '0') << 3; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (str_len == DSCP_AF_LEN && strncasecmp(str, "AF", 2) == 0) { |
||||
|
if (str[2] >= '1' && str[2] <= '4' && str[3] >= '1' && str[3] <= '3') { |
||||
|
// AFxy = 8x + 2y |
||||
|
return ((str[2] - '0') << 3) | ((str[3] - '0') << 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Manual hexadecimal mode (0xYZ) |
||||
|
char *endptr; |
||||
|
int dscp = (int)strtol(str, &endptr, 0); |
||||
|
if (*endptr == '\0' && dscp >= DSCP_MIN && dscp <= DSCP_MAX) { |
||||
|
return dscp; |
||||
|
} |
||||
|
|
||||
|
LOGE("Invalid DSCP value (%s)", str); |
||||
|
return DSCP_DEFAULT; |
||||
|
} |
||||
|
|
||||
|
jconf_t * |
||||
|
read_jconf(const char *file) |
||||
|
{ |
||||
|
static jconf_t conf; |
||||
|
|
||||
|
memset(&conf, 0, sizeof(jconf_t)); |
||||
|
|
||||
|
char *buf; |
||||
|
json_value *obj; |
||||
|
|
||||
|
FILE *f = fopen(file, "rb"); |
||||
|
if (f == NULL) { |
||||
|
FATAL("Invalid config path."); |
||||
|
} |
||||
|
|
||||
|
fseek(f, 0, SEEK_END); |
||||
|
long pos = ftell(f); |
||||
|
fseek(f, 0, SEEK_SET); |
||||
|
|
||||
|
if (pos < 0) { |
||||
|
FATAL("Invalid config path."); |
||||
|
} |
||||
|
|
||||
|
if (pos >= MAX_CONF_SIZE) { |
||||
|
FATAL("Too large config file."); |
||||
|
} |
||||
|
|
||||
|
buf = ss_malloc(pos + 1); |
||||
|
if (buf == NULL) { |
||||
|
FATAL("No enough memory."); |
||||
|
} |
||||
|
|
||||
|
int nread = fread(buf, pos, 1, f); |
||||
|
if (!nread) { |
||||
|
FATAL("Failed to read the config file."); |
||||
|
} |
||||
|
fclose(f); |
||||
|
|
||||
|
buf[pos] = '\0'; // end of string |
||||
|
|
||||
|
json_settings settings = { 0UL, 0, NULL, NULL, NULL }; |
||||
|
char error_buf[512]; |
||||
|
obj = json_parse_ex(&settings, buf, pos, error_buf); |
||||
|
|
||||
|
if (obj == NULL) { |
||||
|
FATAL(error_buf); |
||||
|
} |
||||
|
|
||||
|
if (obj->type == json_object) { |
||||
|
unsigned int i, j; |
||||
|
for (i = 0; i < obj->u.object.length; i++) { |
||||
|
char *name = obj->u.object.values[i].name; |
||||
|
json_value *value = obj->u.object.values[i].value; |
||||
|
if (strcmp(name, "server") == 0) { |
||||
|
if (value->type == json_array) { |
||||
|
for (j = 0; j < value->u.array.length; j++) { |
||||
|
if (j >= MAX_REMOTE_NUM) { |
||||
|
break; |
||||
|
} |
||||
|
json_value *v = value->u.array.values[j]; |
||||
|
char *addr_str = to_string(v); |
||||
|
parse_addr(addr_str, conf.remote_addr + j); |
||||
|
ss_free(addr_str); |
||||
|
conf.remote_num = j + 1; |
||||
|
} |
||||
|
} else if (value->type == json_string) { |
||||
|
parse_addr(to_string(value), conf.remote_addr); |
||||
|
conf.remote_num = 1; |
||||
|
} |
||||
|
} else if (strcmp(name, "port_password") == 0) { |
||||
|
if (value->type == json_object) { |
||||
|
for (j = 0; j < value->u.object.length; j++) { |
||||
|
if (j >= MAX_PORT_NUM) { |
||||
|
break; |
||||
|
} |
||||
|
json_value *v = value->u.object.values[j].value; |
||||
|
if (v->type == json_string) { |
||||
|
conf.port_password[j].port = ss_strndup(value->u.object.values[j].name, |
||||
|
value->u.object.values[j].name_length); |
||||
|
conf.port_password[j].password = to_string(v); |
||||
|
conf.port_password_num = j + 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else if (strcmp(name, "server_port") == 0) { |
||||
|
conf.remote_port = to_string(value); |
||||
|
} else if (strcmp(name, "local_address") == 0) { |
||||
|
conf.local_addr = to_string(value); |
||||
|
} else if (strcmp(name, "local_port") == 0) { |
||||
|
conf.local_port = to_string(value); |
||||
|
} else if (strcmp(name, "password") == 0) { |
||||
|
conf.password = to_string(value); |
||||
|
} else if (strcmp(name, "key") == 0) { |
||||
|
conf.key = to_string(value); |
||||
|
} else if (strcmp(name, "method") == 0) { |
||||
|
conf.method = to_string(value); |
||||
|
} else if (strcmp(name, "timeout") == 0) { |
||||
|
conf.timeout = to_string(value); |
||||
|
} else if (strcmp(name, "user") == 0) { |
||||
|
conf.user = to_string(value); |
||||
|
} else if (strcmp(name, "plugin") == 0) { |
||||
|
conf.plugin = to_string(value); |
||||
|
if (conf.plugin && strlen(conf.plugin) == 0) { |
||||
|
ss_free(conf.plugin); |
||||
|
conf.plugin = NULL; |
||||
|
} |
||||
|
} else if (strcmp(name, "plugin_opts") == 0) { |
||||
|
conf.plugin_opts = to_string(value); |
||||
|
} else if (strcmp(name, "fast_open") == 0) { |
||||
|
check_json_value_type(value, json_boolean, |
||||
|
"invalid config file: option 'fast_open' must be a boolean"); |
||||
|
conf.fast_open = value->u.boolean; |
||||
|
} else if (strcmp(name, "reuse_port") == 0) { |
||||
|
check_json_value_type(value, json_boolean, |
||||
|
"invalid config file: option 'reuse_port' must be a boolean"); |
||||
|
conf.reuse_port = value->u.boolean; |
||||
|
} else if (strcmp(name, "auth") == 0) { |
||||
|
FATAL("One time auth has been deprecated. Try AEAD ciphers instead."); |
||||
|
} else if (strcmp(name, "nofile") == 0) { |
||||
|
check_json_value_type(value, json_integer, |
||||
|
"invalid config file: option 'nofile' must be an integer"); |
||||
|
conf.nofile = value->u.integer; |
||||
|
} else if (strcmp(name, "nameserver") == 0) { |
||||
|
conf.nameserver = to_string(value); |
||||
|
} else if (strcmp(name, "dscp") == 0) { |
||||
|
if (value->type == json_object) { |
||||
|
for (j = 0; j < value->u.object.length; j++) { |
||||
|
if (j >= MAX_DSCP_NUM) { |
||||
|
break; |
||||
|
} |
||||
|
json_value *v = value->u.object.values[j].value; |
||||
|
if (v->type == json_string) { |
||||
|
int dscp = parse_dscp(to_string(v)); |
||||
|
char *port = ss_strndup(value->u.object.values[j].name, |
||||
|
value->u.object.values[j].name_length); |
||||
|
conf.dscp[j].port = port; |
||||
|
conf.dscp[j].dscp = dscp; |
||||
|
conf.dscp_num = j + 1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} else if (strcmp(name, "tunnel_address") == 0) { |
||||
|
conf.tunnel_address = to_string(value); |
||||
|
} else if (strcmp(name, "mode") == 0) { |
||||
|
char *mode_str = to_string(value); |
||||
|
|
||||
|
if (mode_str == NULL) |
||||
|
conf.mode = TCP_ONLY; |
||||
|
else if (strcmp(mode_str, "tcp_only") == 0) |
||||
|
conf.mode = TCP_ONLY; |
||||
|
else if (strcmp(mode_str, "tcp_and_udp") == 0) |
||||
|
conf.mode = TCP_AND_UDP; |
||||
|
else if (strcmp(mode_str, "udp_only") == 0) |
||||
|
conf.mode = UDP_ONLY; |
||||
|
else |
||||
|
LOGI("ignore unknown mode: %s, use tcp_only as fallback", |
||||
|
mode_str); |
||||
|
|
||||
|
ss_free(mode_str); |
||||
|
} else if (strcmp(name, "mtu") == 0) { |
||||
|
check_json_value_type(value, json_integer, |
||||
|
"invalid config file: option 'mtu' must be an integer"); |
||||
|
conf.mtu = value->u.integer; |
||||
|
} else if (strcmp(name, "mptcp") == 0) { |
||||
|
check_json_value_type(value, json_boolean, |
||||
|
"invalid config file: option 'mptcp' must be a boolean"); |
||||
|
conf.mptcp = value->u.boolean; |
||||
|
} else if (strcmp(name, "ipv6_first") == 0) { |
||||
|
check_json_value_type(value, json_boolean, |
||||
|
"invalid config file: option 'ipv6_first' must be a boolean"); |
||||
|
conf.ipv6_first = value->u.boolean; |
||||
|
#ifdef HAS_SYSLOG |
||||
|
} else if (strcmp(name, "use_syslog") == 0) { |
||||
|
check_json_value_type(value, json_boolean, |
||||
|
"invalid config file: option 'use_syslog' must be a boolean"); |
||||
|
use_syslog = value->u.boolean; |
||||
|
#endif |
||||
|
} else if (strcmp(name, "no_delay") == 0) { |
||||
|
check_json_value_type( |
||||
|
value, json_boolean, |
||||
|
"invalid config file: option 'no_delay' must be a boolean"); |
||||
|
conf.no_delay = value->u.boolean; |
||||
|
} else if (strcmp(name, "workdir") == 0) { |
||||
|
conf.workdir = to_string(value); |
||||
|
} else if (strcmp(name, "acl") == 0) { |
||||
|
conf.acl = to_string(value); |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
FATAL("Invalid config file"); |
||||
|
} |
||||
|
|
||||
|
ss_free(buf); |
||||
|
json_value_free(obj); |
||||
|
return &conf; |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
{ |
||||
|
"server":"0.0.0.0", |
||||
|
"port_password": { |
||||
|
"13800": "123456", |
||||
|
"13801": "123456", |
||||
|
"13802": "123456", |
||||
|
"13803": "123456", |
||||
|
"13804": "123456", |
||||
|
"13805": "123456", |
||||
|
"13806": "123456", |
||||
|
"13807": "123456", |
||||
|
"13808": "123456", |
||||
|
"13809": "123456", |
||||
|
"13810": "123456" |
||||
|
}, |
||||
|
"timeout": 300, |
||||
|
"method": "aes-192-gcm", |
||||
|
"mode":"tcp_and_udp", |
||||
|
"fast_open":true, |
||||
|
"reuse_port": true |
||||
|
} |
Write
Preview
Loading…
Cancel
Save