def pack_auth_data(self, auth_data, buf): if len(buf) == 0: return b'' if len(buf) > 400: rnd_len = struct.unpack('<H', os.urandom(2))[0] % 512 else: rnd_len = struct.unpack('<H', os.urandom(2))[0] % 1024 data = auth_data data_len = 7 + 4 + 16 + 4 + len(buf) + rnd_len + 4 data = data + struct.pack('<H', data_len) + struct.pack('<H', rnd_len) mac_key = self.server_info.iv + self.server_info.key uid = os.urandom(4) if b':' in to_bytes(self.server_info.protocol_param): try: items = to_bytes(self.server_info.protocol_param).split(b':') self.user_key = self.hashfunc(items[1]).digest() uid = struct.pack('<I', int(items[0])) except: pass if self.user_key is None: self.user_key = self.server_info.key encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc', b'\x00' * 16) data = uid + encryptor.encrypt(data)[16:] data += hmac.new(mac_key, data, self.hashfunc).digest()[:4] check_head = os.urandom(1) check_head += hmac.new(mac_key, check_head, self.hashfunc).digest()[:6] data = check_head + data + os.urandom(rnd_len) + buf data += hmac.new(self.user_key, data, self.hashfunc).digest()[:4] return data
def client_encode(self, buf): if self.has_sent_header: return buf head_size = len(self.server_info.iv) + self.server_info.head_len if len(buf) - head_size > 64: headlen = head_size + random.randint(0, 64) else: headlen = len(buf) headdata = buf[:headlen] buf = buf[headlen:] port = b'' if self.server_info.port != 80: port = b':' + to_bytes(str(self.server_info.port)) body = None hosts = (self.server_info.obfs_param or self.server_info.host) pos = hosts.find("#") if pos >= 0: body = hosts[pos + 1:].replace("\n", "\r\n") body = body.replace("\\n", "\r\n") hosts = hosts[:pos] hosts = hosts.split(',') host = random.choice(hosts) http_head = b"GET /" + self.encode_head(headdata) + b" HTTP/1.1\r\n" http_head += b"Host: " + to_bytes(host) + port + b"\r\n" if body: http_head += body + "\r\n\r\n" else: http_head += b"User-Agent: " + random.choice( self.user_agent) + b"\r\n" http_head += b"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nDNT: 1\r\nConnection: keep-alive\r\n\r\n" self.has_sent_header = True return http_head + buf
def client_udp_pre_encrypt(self, buf): if self.user_key is None: if b':' in to_bytes(self.server_info.protocol_param): try: items = to_bytes( self.server_info.protocol_param).split(':') self.user_key = self.hashfunc(items[1]).digest() self.user_id = struct.pack('<I', int(items[0])) except: pass if self.user_key is None: self.user_id = os.urandom(4) self.user_key = self.server_info.key authdata = os.urandom(3) mac_key = self.server_info.key md5data = hmac.new(mac_key, authdata, self.hashfunc).digest() uid = struct.unpack('<I', self.user_id)[0] ^ struct.unpack( '<I', md5data[:4])[0] uid = struct.pack('<I', uid) rand_len = self.udp_rnd_data_len(md5data, self.random_client) encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4') out_buf = encryptor.encrypt(buf) buf = out_buf + os.urandom(rand_len) + authdata + uid return buf + hmac.new(self.user_key, buf, self.hashfunc).digest()[:1]
def client_udp_post_decrypt(self, buf): if len(buf) <= 8: return (b'', None) if hmac.new(self.user_key, buf[:-1], self.hashfunc).digest()[:1] != buf[-1:]: return (b'', None) mac_key = self.server_info.key md5data = hmac.new(mac_key, buf[-8:-1], self.hashfunc).digest() rand_len = self.udp_rnd_data_len(md5data, self.random_server) encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4') return encryptor.decrypt(buf[:-8 - rand_len])
def client_udp_pre_encrypt(self, buf): if self.user_key is None: if b':' in to_bytes(self.server_info.protocol_param): try: items = to_bytes( self.server_info.protocol_param).split(':') self.user_key = self.hashfunc(items[1]).digest() self.user_id = struct.pack('<I', int(items[0])) except: pass if self.user_key is None: self.user_id = os.urandom(4) self.user_key = self.server_info.key buf += self.user_id return buf + hmac.new(self.user_key, buf, self.hashfunc).digest()[:4]
def write_pid_file(pid_file, pid): import fcntl import stat try: fd = os.open(pid_file, os.O_RDWR | os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR) except OSError as e: shell.print_exception(e) return -1 flags = fcntl.fcntl(fd, fcntl.F_GETFD) assert flags != -1 flags |= fcntl.FD_CLOEXEC r = fcntl.fcntl(fd, fcntl.F_SETFD, flags) assert r != -1 # TODO: use kill(0) for the pid in pid_file if the file exists # There is no platform independent way to implement fcntl(fd, F_SETLK, &fl) # via fcntl.fcntl. So use lockf instead try: fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB, 0, 0, os.SEEK_SET) except IOError: r = os.read(fd, 32) if r: logging.error('already started at pid %s' % common.to_str(r)) else: logging.error('already started') os.close(fd) return -1 os.ftruncate(fd, 0) os.write(fd, common.to_bytes(str(pid))) return 0
def sni(self, url): url = common.to_bytes(url) data = b"\x00" + struct.pack('>H', len(url)) + url data = b"\x00\x00" + struct.pack('>H', len(data) + 2) + struct.pack( '>H', len(data)) + data return data
def server_udp_pre_encrypt(self, buf, uid): if uid in self.server_info.users: user_key = self.server_info.users[uid] else: uid = None if not self.server_info.users: user_key = self.server_info.key else: user_key = self.server_info.recv_iv authdata = os.urandom(7) mac_key = self.server_info.key md5data = hmac.new(mac_key, authdata, self.hashfunc).digest() rand_len = self.udp_rnd_data_len(md5data, self.random_server) encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4') out_buf = encryptor.encrypt(buf) buf = out_buf + os.urandom(rand_len) + authdata return buf + hmac.new(user_key, buf, self.hashfunc).digest()[:1]
def base64_decode(string): def adjust_padding(string): """Adjust to base64 format, i.e. len(string) % 4 == 0.""" missing_padding = len(string) % 4 if missing_padding: string += '=' * (4 - missing_padding) return string string = adjust_padding(string.strip()) return common.to_str(base64.urlsafe_b64decode(common.to_bytes(string)))
def server_encode(self, buf): if self.has_sent_header: return buf header = b'HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Encoding: gzip\r\nContent-Type: text/html\r\nDate: ' header += to_bytes( datetime.datetime.now().strftime('%a, %d %b %Y %H:%M:%S GMT')) header += b'\r\nServer: nginx\r\nVary: Accept-Encoding\r\n\r\n' self.has_sent_header = True return header + buf
def pack_auth_data(self, auth_data, buf): data = auth_data data_len = 12 + 4 + 16 + 4 data = data + (struct.pack('<H', self.server_info.overhead) + struct.pack('<H', 0)) mac_key = self.server_info.iv + self.server_info.key check_head = os.urandom(4) self.last_client_hash = hmac.new(mac_key, check_head, self.hashfunc).digest() check_head += self.last_client_hash[:8] if b':' in to_bytes(self.server_info.protocol_param): try: items = to_bytes(self.server_info.protocol_param).split(b':') self.user_key = items[1] uid = struct.pack('<I', int(items[0])) except: uid = os.urandom(4) else: uid = os.urandom(4) if self.user_key is None: self.user_key = self.server_info.key encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc', b'\x00' * 16) uid = struct.unpack('<I', uid)[0] ^ struct.unpack( '<I', self.last_client_hash[8:12])[0] uid = struct.pack('<I', uid) data = uid + encryptor.encrypt(data)[16:] self.last_server_hash = hmac.new(self.user_key, data, self.hashfunc).digest() data = check_head + data + self.last_server_hash[:4] self.encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'rc4') return data + self.pack_client_data(buf)
def server_udp_post_decrypt(self, buf): mac_key = self.server_info.key md5data = hmac.new(mac_key, buf[-8:-5], self.hashfunc).digest() uid = struct.unpack('<I', buf[-5:-1])[0] ^ struct.unpack( '<I', md5data[:4])[0] uid = struct.pack('<I', uid) if uid in self.server_info.users: user_key = self.server_info.users[uid] else: uid = None if not self.server_info.users: user_key = self.server_info.key else: user_key = self.server_info.recv_iv if hmac.new(user_key, buf[:-1], self.hashfunc).digest()[:1] != buf[-1:]: return (b'', None) rand_len = self.udp_rnd_data_len(md5data, self.random_client) encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(user_key)) + to_bytes(base64.b64encode(md5data)), 'rc4') out_buf = encryptor.decrypt(buf[:-8 - rand_len]) return (out_buf, uid)
def get_cipher(self, password, method, op, iv): password = common.to_bytes(password) m = self._method_info if m[0] > 0: key, iv_ = EVP_BytesToKey(password, m[0], m[1]) else: # key_length == 0 indicates we should use the key directly key, iv = password, b'' iv = iv[:m[1]] if op == 1: # this iv is for cipher not decipher self.cipher_iv = iv[:m[1]] self.cipher_key = key return m[2](method, key, iv, op)
def _update_users(self, protocol_param, acl): if protocol_param is None: protocol_param = self._config['protocol_param'] param = common.to_bytes(protocol_param).split(b'#') if len(param) == 2: user_list = param[1].split(b',') if user_list: for user in user_list: items = user.split(b':') if len(items) == 2: user_int_id = int(items[0]) uid = struct.pack('<I', user_int_id) if acl is not None and user_int_id not in acl: self.del_user(uid) else: passwd = items[1] self.add_user(uid, {'password': passwd})
def __init__(self, cipher_name, key, iv, op): self._ctx = None if not loaded: load_openssl() cipher = libcrypto.EVP_get_cipherbyname(common.to_bytes(cipher_name)) if not cipher: cipher = load_cipher(cipher_name) if not cipher: raise Exception('cipher %s not found in libcrypto' % cipher_name) key_ptr = c_char_p(key) iv_ptr = c_char_p(iv) self._ctx = libcrypto.EVP_CIPHER_CTX_new() if not self._ctx: raise Exception('can not create cipher context') r = libcrypto.EVP_CipherInit_ex(self._ctx, cipher, None, key_ptr, iv_ptr, c_int(op)) if not r: self.clean() raise Exception('can not initialize cipher context')
def base64_encode(string): return common.to_str(base64.urlsafe_b64encode( common.to_bytes(string))).replace('=', '')
def parse_config(is_local, config_=None): global verbose global config global config_path # config = {} # config_path = None logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s') if is_local: # check this is a client or a server. shortopts = 'hd:s:b:p:k:l:m:O:o:G:g:c:t:L:vq' longopts = [ 'help', 'fast-open', 'link', 'pid-file=', 'log-file=', 'user='******'version' ] else: shortopts = 'hd:s:p:k:m:O:o:G:g:c:t:vq' longopts = [ 'help', 'fast-open', 'pid-file=', 'log-file=', 'workers=', 'forbidden-ip=', 'user='******'manager-address=', 'version' ] if config_: config.update(config_) else: args = parse_args()[0] config['password'] = to_bytes(config.get('password', b'')) config['method'] = to_str(config.get('method', 'aes-256-cfb')) config['protocol'] = to_str(config.get('protocol', 'origin')) config['protocol_param'] = to_str(config.get('protocol_param', '')) config['obfs'] = to_str(config.get('obfs', 'plain')) config['obfs_param'] = to_str(config.get('obfs_param', '')) config['port_password'] = config.get('port_password', None) config['additional_ports'] = config.get('additional_ports', {}) config['additional_ports_only'] = config.get('additional_ports_only', False) config['timeout'] = int(config.get('timeout', 300)) config['udp_timeout'] = int(config.get('udp_timeout', 120)) config['udp_cache'] = int(config.get('udp_cache', 64)) config['fast_open'] = config.get('fast_open', False) config['workers'] = config.get('workers', 1) # config['pid-file'] = config.get('pid-file', '/var/run/shadowsocksr.pid') # config['log-file'] = config.get('log-file', '/var/log/shadowsocksr.log') config['pid-file'] = config.get('pid-file', '/tmp/shadowsocksr.pid') config['log-file'] = config.get('log-file', '/tmp/shadowsocksr.log') config['verbose'] = config.get('verbose', False) config['connect_verbose_info'] = config.get('connect_verbose_info', 0) config['local_address'] = to_str(config.get('local_address', '127.0.0.1')) config['local_port'] = config.get('local_port', 1080) if is_local: # FIXME: enable not provide server addr if daemon stop or restart # if config.get('server', None) is None and not args.command and (not args.d or args.d == 'start'): # if config.get('server', None) is None: # logging.error('server addr not specified') # print_local_help() # sys.exit(2) # else: # config['server'] = to_str(config['server']) if 'server' in config: config['server'] = to_str(config['server']) else: config['server'] = to_str(config.get('server', '0.0.0.0')) try: config['forbidden_ip'] = \ IPNetwork(config.get('forbidden_ip', '127.0.0.0/8,::1/128')) except Exception as e: logger.error(e) sys.exit(2) try: config['forbidden_port'] = PortRange( config.get('forbidden_port', '')) except Exception as e: logger.error(e) sys.exit(2) try: config['ignore_bind'] = \ IPNetwork(config.get('ignore_bind', '127.0.0.0/8,::1/128,10.0.0.0/8,192.168.0.0/16')) except Exception as e: logger.error(e) sys.exit(2) config['server_port'] = config.get('server_port', 8388) if config['verbose'] >= 2: level = VERBOSE_LEVEL elif config['verbose'] == 1: level = logging.DEBUG elif config['verbose'] == -1: level = logging.WARN elif config['verbose'] <= -2: level = logging.ERROR else: level = logging.INFO verbose = config['verbose'] logging.basicConfig( level=level, format= '%(asctime)s %(levelname)-8s %(filename)s:%(lineno)s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') check_config(config, is_local) return config
def server_post_decrypt(self, buf): if self.raw_trans: return (buf, False) self.recv_buf += buf out_buf = b'' sendback = False if not self.has_recv_header: if len(self.recv_buf) >= 7 or len(self.recv_buf) in [2, 3]: recv_len = min(len(self.recv_buf), 7) mac_key = self.server_info.recv_iv + self.server_info.key sha1data = hmac.new(mac_key, self.recv_buf[:1], self.hashfunc).digest()[:recv_len - 1] if sha1data != self.recv_buf[1:recv_len]: return self.not_match_return(self.recv_buf) if len(self.recv_buf) < 31: return (b'', False) sha1data = hmac.new(mac_key, self.recv_buf[7:27], self.hashfunc).digest()[:4] if sha1data != self.recv_buf[27:31]: logging.error( '%s data uncorrect auth HMAC-SHA1 from %s:%d, data %s' % (self.no_compatible_method, self.server_info.client, self.server_info.client_port, binascii.hexlify(self.recv_buf))) if len(self.recv_buf) < 31 + self.extra_wait_size: return (b'', False) return self.not_match_return(self.recv_buf) uid = self.recv_buf[7:11] if uid in self.server_info.users: self.user_id = uid self.user_key = self.hashfunc( self.server_info.users[uid]).digest() self.server_info.update_user_func(uid) else: if not self.server_info.users: self.user_key = self.server_info.key else: self.user_key = self.server_info.recv_iv encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc') head = encryptor.decrypt( b'\x00' * 16 + self.recv_buf[11:27] + b'\x00') # need an extra byte or recv empty length = struct.unpack('<H', head[12:14])[0] if len(self.recv_buf) < length: return (b'', False) utc_time = struct.unpack('<I', head[:4])[0] client_id = struct.unpack('<I', head[4:8])[0] connection_id = struct.unpack('<I', head[8:12])[0] rnd_len = struct.unpack('<H', head[14:16])[0] if hmac.new(self.user_key, self.recv_buf[:length - 4], self.hashfunc).digest()[:4] != self.recv_buf[length - 4:length]: logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) return self.not_match_return(self.recv_buf) time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff)) if time_dif < -self.max_time_dif or time_dif > self.max_time_dif: logging.info('%s: wrong timestamp, time_dif %d, data %s' % (self.no_compatible_method, time_dif, binascii.hexlify(head))) return self.not_match_return(self.recv_buf) elif self.server_info.data.insert(self.user_id, client_id, connection_id): self.has_recv_header = True out_buf = self.recv_buf[31 + rnd_len:length - 4] self.client_id = client_id self.connection_id = connection_id else: logging.info( '%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf))) return self.not_match_return(self.recv_buf) self.recv_buf = self.recv_buf[length:] self.has_recv_header = True sendback = True while len(self.recv_buf) > 4: mac_key = self.user_key + struct.pack('<I', self.recv_id) mac = hmac.new(mac_key, self.recv_buf[:2], self.hashfunc).digest()[:2] if mac != self.recv_buf[2:4]: self.raw_trans = True logging.info(self.no_compatible_method + ': wrong crc') if self.recv_id == 0: logging.info(self.no_compatible_method + ': wrong crc') return (b'E' * 2048, False) else: raise Exception('server_post_decrype data error') length = struct.unpack('<H', self.recv_buf[:2])[0] if length >= 8192 or length < 7: self.raw_trans = True self.recv_buf = b'' if self.recv_id == 0: logging.info(self.no_compatible_method + ': over size') return (b'E' * 2048, False) else: raise Exception('server_post_decrype data error') if length > len(self.recv_buf): break if hmac.new(mac_key, self.recv_buf[:length - 4], self.hashfunc).digest()[:4] != self.recv_buf[length - 4:length]: logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) self.raw_trans = True self.recv_buf = b'' if self.recv_id == 0: return (b'E' * 2048, False) else: raise Exception( 'server_post_decrype data uncorrect checksum') self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF pos = common.ord(self.recv_buf[4]) if pos < 255: pos += 4 else: pos = struct.unpack('<H', self.recv_buf[5:7])[0] + 4 out_buf += self.recv_buf[pos:length - 4] self.recv_buf = self.recv_buf[length:] if pos == length - 4: sendback = True if out_buf: self.server_info.data.update(self.user_id, self.client_id, self.connection_id) return (out_buf, sendback)
def parse_args(args_=None): # FIXME: called twice, service, parse_config def args_error( message): # TODO: print help information when invalid arguments nonlocal parser sys.stderr.write('error: %s\n'.format(message)) print('something wrong') parser.print_help() parser = argparse.ArgumentParser( description='A fast tunnel proxy that helps you bypass firewalls.', usage='ssclient <command> [OPTION]', epilog='Online help: <https://github.com/shadowsocks/shadowsocks>') # TODO: add conflicts of -L with others. # default to old version config path, if args.command is set, change it to new version config path parser.add_argument('-s', metavar='SERVER_ADDR', help='server address') parser.add_argument('-p', metavar='SERVER_PORT', help='server port', default='8388') parser.add_argument('-k', metavar='PASSWORD', help='password') parser.add_argument('-m', metavar='METHOD', help='encryption method', default='aes-256-cfb') parser.add_argument('-O', metavar='PROTOCOL', help='protocol', default='http_simple') parser.add_argument('-G', metavar='PROTOCOL_PARAM', help='protocol param', default='') parser.add_argument('-o', metavar='OBFS', help='obfsplugin', default='http_simple') parser.add_argument('-g', metavar='OBFS_PARAM', help='obfs param', default='') subparsers = parser.add_subparsers(title='command title', dest='command', help='sub-commands', metavar='<command>') server_parser = subparsers.add_parser('server', help='xxx') feed_parser = subparsers.add_parser('feed', help='yyy') status_parser = subparsers.add_parser('status', help='show current status') service_parser = subparsers.add_parser('service', help='service operations') config_parser = subparsers.add_parser('config', help='yyy') server_parser.add_argument('subcmd', help='server command') feed_parser.add_argument('--link', help='ssr link') # TODO: if no link, ask later. feed_parser.add_argument('subcmd', help='subscription command') feed_parser.add_argument('--source', help='souurce address') # status_parser.add_argument('subcmd', help='show current status') service_parser.add_argument('subcmd', help='show current status') config_parser.add_argument('subcmd', help='show current status') # config_parser.add_argument('-c', help='path to the import config file') config_parser.add_argument('-o', help='path to the :xport config file') for p in (parser, server_parser, feed_parser, status_parser, config_parser, service_parser): p.add_argument('-b', metavar='LOCAL_ADDR', help='local address', default='127.0.0.1') p.add_argument('-l', metavar='LOCAL_PORT', help='local port', default='1080') p.add_argument('-i', action='store_true', help='start in interactive mode') p.add_argument('-c', metavar='CONFIG', help='path to config file') p.add_argument('-L', metavar='SSR_LINK', help='connect using ssr link') p.add_argument('-t', metavar='TIMEOUT', help='timeout in seconds', default=300) p.add_argument('--fast-open', action='store_true', help='use TCP_FAST_OPEN, requires Linux 3.7+') # p.add_argument('-d', metavar='', help='daemon mode (start/stop/restart)', choices=['start', 'stop', 'restart']) # TODO: change help message about daemon, we don't need stop/restart option now p.add_argument('-d', action='store_true', help='daemon mode (start/stop/restart)') p.add_argument('--version', help='show version information', action='store_true') p.add_argument('--pid-file', metavar='PID_FILE', help='pid file for daemon mode') p.add_argument('--log-file', metavar='LOG_FILE', help='log file daemon mode') p.add_argument('--user', metavar='USER', help='run as user') p.add_argument('--workers', metavar='WORKERS', default=1) p.add_argument('-v', '-vv', action='count', help='verbose mode', default=0) # TODO: remove quiet mode p.add_argument('-q', '-qq', action='count', help='quiet mode') try: if args_: args, extra_args = parser.parse_known_args(args_) else: args, extra_args = parser.parse_known_args() # except argparse.ArgumentParser as e: # we cannot catch argparse.ArgumentParser since it does not inherit from BaseException, # when errors in args is founded, argparse just raise SystemExit, this is horrible, # we really need to handle the exception to stop daemon from exit. except SystemExit as e: # TODO: add subcommand `help`, and `help` message # args = parser.parse_known_args(['-h'])[0] args = parser.parse_known_args(['feed', 'list'])[0] pass global verbose global config config = {} global config_path config_path = None logging.basicConfig(level=logging.INFO, format='%(levelname)-s: %(message)s') if args.version: print_shadowsocks() # sys.exit(0) if args.c: # FIXME: enable default config_path if args.command: # if args.c == 'default': # config_path = find_config(True) # else: config_path = args.c else: # if args.c == 'default': # config_path = find_config(False) # else: config_path = args.c logger.debug('loading config from %s' % config_path) with open(config_path, 'rb') as f: try: config = parse_json_in_str( remove_comment(f.read().decode('utf8'))) except ValueError as e: logger.error('found an error in config.json: %s', str(e)) sys.exit(1) if config_path: config['config_path'] = config_path else: config['config_path'] = find_config(args.command) if args.p: config['server_port'] = int(args.p) if args.k: config['password'] = to_bytes(args.k) if args.l: config['local_port'] = int(args.l) if args.s: config['server'] = to_str(args.s) if args.m: config['method'] = to_str(args.m) if args.O: config['protocol'] = to_str(args.O) if args.o: config['obfs'] = to_str(args.o) if args.G: config['protocol_param'] = to_str(args.G) if args.g: config['obfs_param'] = to_str(args.g) if args.b: config['local_address'] = to_str(args.b) if args.t: config['timeout'] = int(args.t) # FIXME: # if key == '--fast-open': # config['fast_open'] = True if args.workers: config['workers'] = int(args.workers) # FIXME: # if key == '--manager-address': # config['manager_address'] = value if args.user: config['user'] = to_str(args.user) # FIXME: # if key == '--forbidden-ip': # config['forbidden_ip'] = to_str(value) if args.d: config['daemon'] = to_str(args.d) # FIXME: # if key == '--pid-file': # config['pid-file'] = to_str(value) # FIXME: # if key == '--log-file': # config['log-file'] = to_str(value) config['verbose'] = args.v if args.q: config['verbose'] -= 1 if args.L: config_from_ssrlink = decode_ssrlink(args.L) # config.update(config_from_ssrlink) config_from_ssrlink.update(config) config = config_from_ssrlink return (args, extra_args)
def send_data(data_dict): if data_dict: # use compact JSON format (without space) data = common.to_bytes( json.dumps(data_dict, separators=(',', ':'))) self._send_control_data(b'stat: ' + data)
def __init__(self, config, dns_resolver, is_local, stat_callback=None, stat_counter=None): self._config = config if config.get('connect_verbose_info', 0) > 0: common.connect_log = logging.info if is_local: self._listen_addr = config['local_address'] self._listen_port = config['local_port'] self._remote_addr = config['server'] self._remote_port = config['server_port'] else: self._listen_addr = config['server'] self._listen_port = config['server_port'] self._remote_addr = None self._remote_port = None self._dns_resolver = dns_resolver self._password = common.to_bytes(config['password']) self._method = config['method'] self._timeout = config['timeout'] self._is_local = is_local self._udp_cache_size = config['udp_cache'] self._cache = lru_cache.LRUCache( timeout=config['udp_timeout'], close_callback=self._close_client_pair) self._cache_dns_client = lru_cache.LRUCache( timeout=10, close_callback=self._close_client_pair) self._client_fd_to_server_addr = {} #self._dns_cache = lru_cache.LRUCache(timeout=1800) self._eventloop = None self._closed = False self.server_transfer_ul = 0 self.server_transfer_dl = 0 self.server_users = {} self.server_user_transfer_ul = {} self.server_user_transfer_dl = {} if common.to_bytes(config['protocol']) in obfs.mu_protocol(): self._update_users(None, None) self.protocol_data = obfs.obfs(config['protocol']).init_data() self._protocol = obfs.obfs(config['protocol']) server_info = obfs.server_info(self.protocol_data) server_info.host = self._listen_addr server_info.port = self._listen_port server_info.users = self.server_users server_info.protocol_param = config['protocol_param'] server_info.obfs_param = '' server_info.iv = b'' server_info.recv_iv = b'' server_info.key_str = common.to_bytes(config['password']) server_info.key = encrypt.encrypt_key(self._password, self._method) server_info.head_len = 30 server_info.tcp_mss = 1452 server_info.buffer_size = BUF_SIZE server_info.overhead = 0 self._protocol.set_server_info(server_info) self._sockets = set() self._fd_to_handlers = {} self._reqid_to_hd = {} self._data_to_write_to_server_socket = [] self._timeout_cache = lru_cache.LRUCache( timeout=self._timeout, close_callback=self._close_tcp_client) self._bind = config.get('out_bind', '') self._bindv6 = config.get('out_bindv6', '') self._ignore_bind_list = config.get('ignore_bind', []) if 'forbidden_ip' in config: self._forbidden_iplist = config['forbidden_ip'] else: self._forbidden_iplist = None if 'forbidden_port' in config: self._forbidden_portset = config['forbidden_port'] else: self._forbidden_portset = None addrs = socket.getaddrinfo(self._listen_addr, self._listen_port, 0, socket.SOCK_DGRAM, socket.SOL_UDP) if len(addrs) == 0: raise Exception("can't get addrinfo for %s:%d" % (self._listen_addr, self._listen_port)) af, socktype, proto, canonname, sa = addrs[0] server_socket = socket.socket(af, socktype, proto) server_socket.bind((self._listen_addr, self._listen_port)) server_socket.setblocking(False) self._server_socket = server_socket self._stat_callback = stat_callback
def add_user(self, uid, cfg): # user: binstr[4], passwd: str passwd = cfg['password'] self.server_users[uid] = common.to_bytes(passwd)
def server_post_decrypt(self, buf): if self.raw_trans: return (buf, False) self.recv_buf += buf out_buf = b'' sendback = False if not self.has_recv_header: if len(self.recv_buf) >= 12 or len(self.recv_buf) in [7, 8]: recv_len = min(len(self.recv_buf), 12) mac_key = self.server_info.recv_iv + self.server_info.key md5data = hmac.new(mac_key, self.recv_buf[:4], self.hashfunc).digest() if md5data[:recv_len - 4] != self.recv_buf[4:recv_len]: return self.not_match_return(self.recv_buf) if len(self.recv_buf) < 12 + 24: return (b'', False) self.last_client_hash = md5data uid = struct.unpack('<I', self.recv_buf[12:16])[0] ^ struct.unpack( '<I', md5data[8:12])[0] self.user_id_num = uid uid = struct.pack('<I', uid) if uid in self.server_info.users: self.user_id = uid self.user_key = self.server_info.users[uid] self.server_info.update_user_func(uid) else: self.user_id_num = 0 if not self.server_info.users: self.user_key = self.server_info.key else: self.user_key = self.server_info.recv_iv md5data = hmac.new(self.user_key, self.recv_buf[12:12 + 20], self.hashfunc).digest() if md5data[:4] != self.recv_buf[32:36]: logging.error( '%s data uncorrect auth HMAC-MD5 from %s:%d, data %s' % (self.no_compatible_method, self.server_info.client, self.server_info.client_port, binascii.hexlify(self.recv_buf))) if len(self.recv_buf) < 36: return (b'', False) return self.not_match_return(self.recv_buf) self.last_server_hash = md5data encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + self.salt, 'aes-128-cbc') head = encryptor.decrypt( b'\x00' * 16 + self.recv_buf[16:32] + b'\x00') # need an extra byte or recv empty self.client_over_head = struct.unpack('<H', head[12:14])[0] utc_time = struct.unpack('<I', head[:4])[0] client_id = struct.unpack('<I', head[4:8])[0] connection_id = struct.unpack('<I', head[8:12])[0] time_dif = common.int32(utc_time - (int(time.time()) & 0xffffffff)) if time_dif < -self.max_time_dif or time_dif > self.max_time_dif: logging.info('%s: wrong timestamp, time_dif %d, data %s' % (self.no_compatible_method, time_dif, binascii.hexlify(head))) return self.not_match_return(self.recv_buf) elif self.server_info.data.insert(self.user_id, client_id, connection_id): self.has_recv_header = True self.client_id = client_id self.connection_id = connection_id else: logging.info( '%s: auth fail, data %s' % (self.no_compatible_method, binascii.hexlify(out_buf))) return self.not_match_return(self.recv_buf) self.encryptor = encrypt.Encryptor( to_bytes(base64.b64encode(self.user_key)) + to_bytes(base64.b64encode(self.last_client_hash)), 'rc4') self.recv_buf = self.recv_buf[36:] self.has_recv_header = True sendback = True while len(self.recv_buf) > 4: mac_key = self.user_key + struct.pack('<I', self.recv_id) data_len = struct.unpack('<H', self.recv_buf[:2])[0] ^ struct.unpack( '<H', self.last_client_hash[14:16])[0] rand_len = self.rnd_data_len(data_len, self.last_client_hash, self.random_client) length = data_len + rand_len if length >= 4096: self.raw_trans = True self.recv_buf = b'' if self.recv_id == 0: logging.info(self.no_compatible_method + ': over size') return (b'E' * 2048, False) else: raise Exception('server_post_decrype data error') if length + 4 > len(self.recv_buf): break client_hash = hmac.new(mac_key, self.recv_buf[:length + 2], self.hashfunc).digest() if client_hash[:2] != self.recv_buf[length + 2:length + 4]: logging.info('%s: checksum error, data %s' % (self.no_compatible_method, binascii.hexlify(self.recv_buf[:length]))) self.raw_trans = True self.recv_buf = b'' if self.recv_id == 0: return (b'E' * 2048, False) else: raise Exception( 'server_post_decrype data uncorrect checksum') self.recv_id = (self.recv_id + 1) & 0xFFFFFFFF pos = 2 if data_len > 0 and rand_len > 0: pos = 2 + self.rnd_start_pos(rand_len, self.random_client) out_buf += self.encryptor.decrypt(self.recv_buf[pos:data_len + pos]) self.last_client_hash = client_hash self.recv_buf = self.recv_buf[length + 4:] if data_len == 0: sendback = True if out_buf: self.server_info.data.update(self.user_id, self.client_id, self.connection_id) return (out_buf, sendback)
def boundary(self): return to_bytes(''.join([ random.choice( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ) for i in range(32) ]))