/* * utils.c - Misc utilities * * Copyright (C) 2013 - 2019, Max Lv * * 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 * . */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #ifndef __MINGW32__ #include #include #include #else #include #endif #include #include #include #include #include "crypto.h" #include "utils.h" #ifdef HAVE_SETRLIMIT #include #include #endif #define INT_DIGITS 19 /* enough for 64 bit integer */ #ifdef LIB_ONLY FILE *logfile; #endif #ifdef HAS_SYSLOG int use_syslog = 0; #endif #ifndef __MINGW32__ void ERROR(const char *s) { char *msg = strerror(errno); LOGE("%s: %s", s, msg); } #endif int use_tty = 1; char * ss_itoa(int i) { /* Room for INT_DIGITS digits, - and '\0' */ static char buf[INT_DIGITS + 2]; char *p = buf + INT_DIGITS + 1; /* points to terminating '\0' */ if (i >= 0) { do { *--p = '0' + (i % 10); i /= 10; } while (i != 0); return p; } else { /* i < 0 */ do { *--p = '0' - (i % 10); i /= 10; } while (i != 0); *--p = '-'; } return p; } int ss_isnumeric(const char *s) { if (!s || !*s) return 0; while (isdigit((unsigned char)*s)) ++s; return *s == '\0'; } int ss_parse_int(const char *s, int min_value, int max_value, int *out) { char *endptr; long value; if (s == NULL || *s == '\0' || out == NULL || min_value > max_value) { return -1; } errno = 0; value = strtol(s, &endptr, 10); if (errno == ERANGE || endptr == s || *endptr != '\0' || value < min_value || value > max_value) { return -1; } *out = (int)value; return 0; } int ss_parse_uint16_port(const char *s, uint16_t *out) { int value; if (out == NULL || ss_parse_int(s, 1, UINT16_MAX, &value) == -1) { return -1; } *out = (uint16_t)value; return 0; } /* * setuid() and setgid() for a specified user. */ int run_as(const char *user) { #ifndef __MINGW32__ if (user[0]) { /* Convert user to a long integer if it is a non-negative number. * -1 means it is a user name. */ long uid = -1; if (ss_isnumeric(user)) { errno = 0; char *endptr; uid = strtol(user, &endptr, 10); if (errno || endptr == user) uid = -1; } #ifdef HAVE_GETPWNAM_R struct passwd pwdbuf, *pwd; memset(&pwdbuf, 0, sizeof(struct passwd)); size_t buflen; int err; for (buflen = 128;; buflen *= 2) { char buf[buflen]; /* variable length array */ /* Note that we use getpwnam_r() instead of getpwnam(), * which returns its result in a statically allocated buffer and * cannot be considered thread safe. */ err = uid >= 0 ? getpwuid_r((uid_t)uid, &pwdbuf, buf, buflen, &pwd) : getpwnam_r(user, &pwdbuf, buf, buflen, &pwd); if (err == 0 && pwd) { /* setgid first, because we may not be allowed to do it anymore after setuid */ if (setgid(pwd->pw_gid) != 0) { LOGE( "Could not change group id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } #ifndef __CYGWIN__ if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) { LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); return 0; } #endif if (setuid(pwd->pw_uid) != 0) { LOGE( "Could not change user id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } break; } else if (err != ERANGE) { if (err) { LOGE("run_as user '%s' could not be found: %s", user, strerror(err)); } else { LOGE("run_as user '%s' could not be found.", user); } return 0; } else if (buflen >= 16 * 1024) { /* If getpwnam_r() seems defective, call it quits rather than * keep on allocating ever larger buffers until we crash. */ LOGE( "getpwnam_r() requires more than %u bytes of buffer space.", (unsigned)buflen); return 0; } /* Else try again with larger buffer. */ } #else /* No getpwnam_r() :-( We'll use getpwnam() and hope for the best. */ struct passwd *pwd; if (!(pwd = uid >= 0 ? getpwuid((uid_t)uid) : getpwnam(user))) { LOGE("run_as user %s could not be found.", user); return 0; } /* setgid first, because we may not allowed to do it anymore after setuid */ if (setgid(pwd->pw_gid) != 0) { LOGE("Could not change group id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) { LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name); return 0; } if (setuid(pwd->pw_uid) != 0) { LOGE("Could not change user id to that of run_as user '%s': %s", pwd->pw_name, strerror(errno)); return 0; } #endif } #else LOGE("run_as(): not implemented in MinGW port"); #endif return 1; } char * ss_strndup(const char *s, size_t n) { char *ret; size_t len = 0; while (len < n && s[len] != '\0') { len++; } ret = ss_malloc(len + 1); memcpy(ret, s, len); ret[len] = '\0'; return ret; } void FATAL(const char *msg) { LOGE("%s", msg); exit(-1); } void * ss_malloc(size_t size) { void *tmp = malloc(size); if (tmp == NULL) exit(EXIT_FAILURE); return tmp; } void * ss_aligned_malloc(size_t size) { int err; void *tmp = NULL; #ifdef HAVE_POSIX_MEMALIGN /* ensure 16 byte alignment */ err = posix_memalign(&tmp, 16, size); #elif __MINGW32__ tmp = _aligned_malloc(size, 16); err = tmp == NULL; #else err = -1; #endif if (err) { return ss_malloc(size); } else { return tmp; } } void * ss_realloc(void *ptr, size_t new_size) { void *new = realloc(ptr, new_size); if (new == NULL) { free(ptr); ptr = NULL; exit(EXIT_FAILURE); } return new; } int ss_is_ipv6addr(const char *addr) { return strcmp(addr, ":") > 0; } /* [cli_short_s] \par `-s ` For clients, set a server endpoint as `HOST:PORT` or `[IPv6]:PORT`; host-only values use the legacy `-p` or configured fallback. Bare IPv6 is never split at its last colon. For server and manager, set the listening address. [cli_short_s] */ /* [cli_short_p] \par `-p ` For clients, set the legacy fallback port for server values without an embedded port. For ss-server, set the listening port. [cli_short_p] */ /* [cli_short_l] \par `-l ` Set the local port number. [cli_short_l] */ /* [cli_short_k] \par `-k ` Set the password. The server and the client should use the same password. [cli_short_k] */ /* [cli_long_password] \par `--password ` Set the password. The server and the client should use the same password. [cli_long_password] */ /* [cli_long_key] \par `--key ` Set the key directly. The key should be encoded with URL-safe Base64. [cli_long_key] */ /* [cli_long_server_url] \par `--server-url ` Take the server address, port, cipher, password and any SIP003 plugin from a single `ss://` URL, as produced by most clients and by *shadowsocks-rust*'s `ssurl`. Both the SIP002 form (`ss://base64(method:password)@host:port/?plugin=...#tag`) and the older `ss://base64(method:password@host:port)` form are accepted. Options given later on the command line override the values taken from the URL. [cli_long_server_url] */ /* [cli_short_m] \par `-m ` Set the cipher. The default is `chacha20-ietf-poly1305`. AEAD cipher names from the source (availability depends on the build): \snippet aead.c cli-aead-ciphers Legacy stream cipher names recognized by the source (disabled in minimal builds; some require backend support): \snippet stream.c cli-stream-ciphers The `2022-blake3-*` ciphers implement Shadowsocks 2022 (SIP022). They require a base64-encoded pre-shared key supplied with `--key` or `--password` (*-k*): 16 bytes for 2022-blake3-aes-128-gcm and 32 bytes for the other 2022 ciphers. Generate a 32-byte key with `openssl rand -base64 32`. Passwords are not stretched into keys for these ciphers. [cli_short_m] */ /* [cli_short_a] \par `-a ` Run as a specific user. [cli_short_a] */ /* [cli_short_f] \par `-f ` Start shadowsocks as a daemon with specific pid file. [cli_short_f] */ /* [cli_short_t] \par `-t ` Set the socket timeout in seconds. The default value is 60. [cli_short_t] */ /* [cli_short_c] \par `-c ` Use a configuration file. Refer to `shadowsocks-c`(8) `CONFIG FILE` section for more details. [cli_short_c] */ /* [cli_short_n] \par `-n ` Specify the maximum number of open files. Requires a platform with setrlimit support. [cli_short_n] */ /* [cli_short_i] \par `-i ` Send outbound traffic through the specified network interface where supported by the platform. [cli_short_i] */ /* [cli_short_b] \par `-b ` Set the local address to bind for the client listener. [cli_short_b] */ /* [cli_short_u] \par `-u` Enable UDP relay. [cli_short_u] */ /* [cli_short_U] \par `-U` Enable UDP relay and disable TCP relay. [cli_short_U] */ /* [cli_short_6] \par `-6` Resolve hostname to IPv6 address first. [cli_short_6] */ /* [cli_long_fast_open] \par `--fast-open` Enable TCP Fast Open where supported by the operating system. [cli_long_fast_open] */ /* [cli_long_reuse_port] \par `--reuse-port` Enable port reuse where supported by the operating system. [cli_long_reuse_port] */ /* [cli_long_acl] \par `--acl ` Enable ACL (Access Control List) and specify config file. [cli_long_acl] */ /* [cli_long_mtu] \par `--mtu ` Specify the MTU of your network interface. [cli_long_mtu] */ /* [cli_long_mptcp] \par `--mptcp` Enable Multipath TCP. Only available with MPTCP enabled Linux kernel. [cli_long_mptcp] */ /* [cli_long_no_delay] \par `--no-delay` Enable TCP_NODELAY. [cli_long_no_delay] */ /* [cli_long_tcp_incoming_sndbuf] \par `--tcp-incoming-sndbuf ` Set TCP send buffer size for incoming connections. [cli_long_tcp_incoming_sndbuf] */ /* [cli_long_tcp_incoming_rcvbuf] \par `--tcp-incoming-rcvbuf ` Set TCP receive buffer size for incoming connections. [cli_long_tcp_incoming_rcvbuf] */ /* [cli_long_tcp_outgoing_sndbuf] \par `--tcp-outgoing-sndbuf ` Set TCP send buffer size for outgoing connections. [cli_long_tcp_outgoing_sndbuf] */ /* [cli_long_tcp_outgoing_rcvbuf] \par `--tcp-outgoing-rcvbuf ` Set TCP receive buffer size for outgoing connections. [cli_long_tcp_outgoing_rcvbuf] */ /* [cli_long_plugin] \par `--plugin ` Enable SIP003 plugin. (Experimental) [cli_long_plugin] */ /* [cli_long_plugin_opts] \par `--plugin-opts ` Set SIP003 plugin options. (Experimental) [cli_long_plugin_opts] */ /* [cli_short_v] \par `-v` Enable verbose mode. [cli_short_v] */ /* [cli_short_h] \par `-h` Print help message. [cli_short_h] */ /* [cli_long_help] \par `--help` Print help message. [cli_long_help] */ /* [cli_short_A] \par `-A` Deprecated one-time authentication option. Exits with an error; use AEAD ciphers instead. [cli_short_A] */ /* [cli_short_S] \par `-S ` Android only: UNIX socket path for traffic statistics. [cli_short_S] */ /* [cli_short_V] \par `-V` Android only: enable VPN socket protection. [cli_short_V] */ /* [cli_short_L] \par `-L ` Destination server address and port for local port forwarding. [cli_short_L] */ /* [cli_short_T] \par `-T` Use TPROXY instead of REDIRECT for TCP traffic. Requires Linux TPROXY support. [cli_short_T] */ /* [cli_short_d] \par `-d ` Configure name servers for the internal c-ares DNS resolver. By default it uses the system resolver configuration. [cli_short_d] */ /* [cli_short_D] \par `-D ` Set the working directory of ss-manager. [cli_short_D] */ /* [cli_long_workdir] \par `--workdir ` Set the working directory of ss-manager (alias for *-D*). [cli_long_workdir] */ /* [cli_long_manager_address] \par `--manager-address
` Set the manager control address: a UNIX domain socket path or an IP address and port. [cli_long_manager_address] */ /* [cli_long_executable] \par `--executable ` Set the executable path of ss-server used by ss-manager. [cli_long_executable] */ /* [cli_long_nftables_sets] \par `--nftables-sets ` Linux builds with USE_NFTABLES only: add malicious IP addresses to nftables sets. Format: `[:][,[:]...]`. [cli_long_nftables_sets] */ /* [cli_long_version] \par `--version` Print the program name and version to standard output and exit successfully. [cli_long_version] */ /* [cli_long_tcp_only] \par `--tcp-only` Enable TCP relay only, overriding the configuration file's mode. The last of `--tcp-only`, `--udp`, and `--udp-only` wins. [cli_long_tcp_only] */ /* [cli_long_ipv4_first] \par `--ipv4-first` Prefer IPv4 DNS results, overriding `ipv6_first` in the configuration file. This is an address preference, not a restriction to IPv4. The last of `--ipv4-first` and `--ipv6-first` wins. [cli_long_ipv4_first] */ static const char * cli_program(void) { #ifdef MODULE_LOCAL return "ss-local"; #elif defined(MODULE_REMOTE) return "ss-server"; #elif defined(MODULE_TUNNEL) return "ss-tunnel"; #elif defined(MODULE_REDIR) return "ss-redir"; #elif defined(MODULE_MANAGER) return "ss-manager"; #else return "shadowsocks-c"; #endif } void cli_version(void) { printf("%s (shadowsocks-c) %s\n", cli_program(), VERSION); } void cli_error(const char *message, int option, const char *token) { fprintf(stderr, "%s: %s", cli_program(), message); /* Report option names, never attached passwords or positional values. */ if (token != NULL && token[0] == '-' && token[1] == '-') { size_t length = 2; while (isalnum((unsigned char)token[length]) || token[length] == '-') length++; if (length > 2 && length < 80) fprintf(stderr, " '%.*s'", (int)length, token); } else if (option > 0 && option < 128 && isalnum((unsigned char)option)) { fprintf(stderr, " '-%c'", option); } fprintf(stderr, ". Try '%s --help'.\n", cli_program()); exit(2); } static void cli_help_option(const char *flags, const char *description) { printf(" %-36s %s\n", flags, description); } void usage(void) { cli_version(); printf("Usage: %s [options]\n\n", cli_program()); puts("Connection:"); #if defined(MODULE_REMOTE) || defined(MODULE_MANAGER) cli_help_option("-s, --listen-address HOST", "Server listening address; may be repeated."); #ifdef MODULE_REMOTE cli_help_option("-p, --listen-port PORT", "Server listening port."); cli_help_option("-b, --outbound-address ADDRESS", "Source address for outbound connections."); #endif #else cli_help_option("-s, --server HOST:PORT", "Remote endpoint; IPv6: [ADDRESS]:PORT. Repeatable."); cli_help_option("-p, --server-port PORT", "Legacy fallback for servers without a port."); cli_help_option("-b, --listen-address ADDRESS", "Local address to bind."); cli_help_option("-l, --listen-port PORT", "Local listening port."); #endif #ifdef MODULE_LOCAL cli_help_option("--server-url URL", "Import an ss:// URL; later options override it."); #endif #ifdef MODULE_TUNNEL cli_help_option("-L, --destination HOST:PORT", "Destination for local port forwarding."); #endif puts("\nConfiguration and credentials:"); cli_help_option("-c, --config FILE", "Read JSON configuration."); cli_help_option("-m, --cipher NAME", "Cipher (default: chacha20-ietf-poly1305)."); cli_help_option("-k, --password SECRET", "Password, or a base64 pre-shared key for AEAD-2022."); #ifndef MODULE_MANAGER cli_help_option("--key BASE64", "Use an explicit base64 key instead of a password."); #endif puts(" AEAD-2022: 2022-blake3-aes-128-gcm, 2022-blake3-aes-256-gcm,"); puts(" 2022-blake3-chacha20-poly1305."); puts("\nTransport and networking:"); cli_help_option("--tcp-only", "TCP only (default); overrides the configured mode."); cli_help_option("-u, --udp", "Enable both TCP and UDP relay."); cli_help_option("-U, --udp-only", "Enable UDP relay only."); cli_help_option("--ipv4-first", "Prefer IPv4 DNS results; overrides configuration."); cli_help_option("-6, --ipv6-first", "Prefer IPv6 DNS results."); cli_help_option("-t, --timeout SECONDS", "Socket timeout (default: 60)."); #ifndef MODULE_REDIR cli_help_option("-i, --interface NAME", "Outbound network interface, where supported."); #endif #if defined(MODULE_REMOTE) || defined(MODULE_MANAGER) cli_help_option("-d, --nameserver ADDRESS", "Name servers for the internal DNS resolver."); #endif #ifdef MODULE_REDIR cli_help_option("-T, --tproxy", "Use TPROXY for TCP; UDP always requires TPROXY."); #endif cli_help_option("--mtu BYTES", "Network MTU (0 selects the default)."); cli_help_option("--fast-open", "Enable TCP Fast Open where supported."); cli_help_option("--reuse-port", "Enable port reuse where supported."); cli_help_option("--no-delay", "Enable TCP_NODELAY."); #if !defined(MODULE_MANAGER) && (!defined(MODULE_REMOTE) || defined(__linux__)) cli_help_option("--mptcp", "Enable Multipath TCP where supported."); #endif #ifndef MODULE_MANAGER cli_help_option("--tcp-incoming-sndbuf BYTES", "Incoming TCP send buffer (0: system default)."); cli_help_option("--tcp-incoming-rcvbuf BYTES", "Incoming TCP receive buffer (0: system default)."); cli_help_option("--tcp-outgoing-sndbuf BYTES", "Outgoing TCP send buffer (0: system default)."); cli_help_option("--tcp-outgoing-rcvbuf BYTES", "Outgoing TCP receive buffer (0: system default)."); #endif puts("\nAccess control and plugins:"); #if defined(MODULE_LOCAL) || defined(MODULE_REMOTE) || defined(MODULE_MANAGER) cli_help_option("--acl FILE", "Access control list."); #endif #if defined(MODULE_REMOTE) && defined(__linux__) && defined(USE_NFTABLES) cli_help_option("--nftables-sets SETS", "Record malicious IPs in [table:]set[,set...] entries."); #endif cli_help_option("--plugin NAME", "SIP003 plugin (requires plugin support in this build)."); cli_help_option("--plugin-opts OPTIONS", "Options passed to the SIP003 plugin."); puts("\nProcess and diagnostics:"); cli_help_option("-a, --user USER", "Run as the specified user."); cli_help_option("-f, --pid-file FILE", "Daemonize and write the process ID to FILE."); #ifdef HAVE_SETRLIMIT cli_help_option("-n, --nofile COUNT", "Maximum number of open files."); #endif #if defined(MODULE_REMOTE) || defined(MODULE_MANAGER) cli_help_option("--manager-address ADDRESS", "Manager UNIX socket path or IP address and port."); #endif #ifdef MODULE_MANAGER cli_help_option("--executable PATH", "ss-server executable used for managed servers."); cli_help_option("-D, --workdir DIRECTORY", "Working directory for managed servers."); #endif #if defined(__ANDROID__) && (defined(MODULE_LOCAL) || defined(MODULE_TUNNEL)) cli_help_option("-V, --vpn", "Enable Android VPN socket protection."); #ifdef MODULE_LOCAL cli_help_option("-S, --stat-path PATH", "Android traffic-statistics socket."); #endif #endif cli_help_option("-v, --verbose", "Enable verbose logging."); cli_help_option("-h, --help", "Show this help and exit."); cli_help_option("--version", "Show the version and exit."); puts("\nOptions apply in command-line order; later mode and address-family flags win."); puts("No positional arguments are accepted. Use --option=value for values starting with '-'."); puts("The obsolete -A option is rejected; use an AEAD cipher instead."); puts("Manual: https://shadowsocks.github.io/shadowsocks-c/"); } void daemonize(const char *path) { #ifndef __MINGW32__ /* Our process ID and Session ID */ pid_t pid, sid; /* Fork off the parent process */ pid = fork(); if (pid < 0) { exit(EXIT_FAILURE); } /* If we got a good PID, then * we can exit the parent process. */ if (pid > 0) { FILE *file = fopen(path, "w"); if (file == NULL) { FATAL("Invalid pid file\n"); } fprintf(file, "%d", (int)pid); fclose(file); exit(EXIT_SUCCESS); } /* Change the file mode mask */ umask(0); /* Open any logs here */ /* Create a new SID for the child process */ sid = setsid(); if (sid < 0) { /* Log the failure */ exit(EXIT_FAILURE); } /* Change the current working directory */ if ((chdir("/")) < 0) { /* Log the failure */ exit(EXIT_FAILURE); } int dev_null = open("/dev/null", O_WRONLY); if (dev_null > 0) { /* Redirect to null device */ dup2(dev_null, STDOUT_FILENO); dup2(dev_null, STDERR_FILENO); } else { /* Close the standard file descriptors */ close(STDOUT_FILENO); close(STDERR_FILENO); } /* Close the standard file descriptors */ close(STDIN_FILENO); #else LOGE("daemonize(): not implemented in MinGW port"); #endif } #ifdef HAVE_SETRLIMIT int set_nofile(int nofile) { struct rlimit limit = { nofile, nofile }; /* set both soft and hard limit */ if (nofile <= 0) { FATAL("nofile must be greater than 0\n"); } if (setrlimit(RLIMIT_NOFILE, &limit) < 0) { if (errno == EPERM) { LOGE( "insufficient permission to change NOFILE, not starting as root?"); return -1; } else if (errno == EINVAL) { LOGE("invalid nofile, decrease nofile and try again"); return -1; } else { LOGE("setrlimit failed: %s", strerror(errno)); return -1; } } return 0; } #endif char * get_default_conf(void) { #ifndef __MINGW32__ static char sysconf[] = "/etc/shadowsocks-libev/config.json"; static char userconf[PATH_MAX]; char *conf_home; conf_home = getenv("XDG_CONFIG_HOME"); if (!conf_home) { // HOME may be unset (e.g. when started by an init system) const char *home = getenv("HOME"); if (home == NULL) { return sysconf; } snprintf(userconf, sizeof(userconf), "%s%s", home, "/.config/shadowsocks-libev/config.json"); } else { snprintf(userconf, sizeof(userconf), "%s%s", conf_home, "/shadowsocks-libev/config.json"); } // Check if the user-specific config exists. if (access(userconf, F_OK) != -1) return userconf; // If not, fall back to the system-wide config. return sysconf; #else return "config.json"; #endif } uint16_t load16_be(const void *s) { const uint8_t *in = (const uint8_t *)s; return ((uint16_t)in[0] << 8) | ((uint16_t)in[1]); } int get_mptcp(int enable) { const char oldpath[] = "/proc/sys/net/mptcp/mptcp_enabled"; if (enable) { // Check if kernel has out-of-tree MPTCP support. if (access(oldpath, F_OK) != -1) return 1; // Otherwise, just use IPPROTO_MPTCP. return -1; } return 0; }