예제 #1
0
    def datagram_received(self, data, addr):
        # addr = ip, port
        packet = PublishPacket.decode(data)
        if packet is not None:
            try:
                data, key, iv = cryptor.decrypt_all(self._password,
                                                    self._method, packet.data)
            except Exception:
                logger.debug('UDP handle_server: decrypt data failed')
                return
            if not data:
                logger.debug('UDP handle_server: data is empty after decrypt')
                return
            header_result = common.parse_header(data)
            if header_result is None:
                logger.error(
                    "can not parse header when handling connection from {}".
                    format(addr))
                return
            addrtype, remote_addr, remote_port, header_length = header_result
            logger.info("udp data to {}:{} from {}:{}".format(
                remote_addr, remote_port, addr[0], addr[1]))

            remote = self._topic_to_remote.get(packet.topic_id)
            if not remote:
                remote = RelayRemoteProtocol(self, packet.topic_id, addr)
                self._topic_to_remote[packet.topic_id] = remote
                self._loop.create_task(
                    self.create_endpoint(remote, common.to_str(remote_addr),
                                         remote_port))

            data = data[header_length:]
            remote.write(data)
예제 #2
0
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
    # 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
예제 #3
0
    def datagram_received(self, data, addr):
        try:
            data, key, iv = cryptor.decrypt_all(self._password, self._method,
                                                data)
        except Exception:
            logger.debug('UDP handle_server: decrypt data failed')
            return
        if not data:
            logger.debug('UDP handle_server: data is empty after decrypt')
            return
        header_result = common.parse_header(data)
        if header_result is None:
            logger.error(
                "can not parse header when handling connection from {0}".
                format(addr))
            return
        addrtype, remote_addr, remote_port, header_length = header_result
        logger.info("udp data to {0}:{1} from {2}:{3}".format(
            remote_addr, remote_port, addr[0], addr[1]))

        client_key = "{0}:{1}".format(addr[0], addr[1])
        remote = self._sessions.get(client_key, None)
        if not remote:
            remote = RelayRemoteProtocol(self, addr)
            self._sessions[client_key] = remote
            self._loop.create_task(
                self.create_endpoint(remote, common.to_str(remote_addr),
                                     remote_port))

        data = data[header_length:]
        remote.write(data)
예제 #4
0
    def handle_publish(self, publish_packet: PublishPacket):

        if not self._approved:
            self._loop.create_task(self.stop())
            return

        data = bytes(publish_packet.data)
        remote = self._topic_to_remote.get(publish_packet.topic_name, None)
        if not publish_packet.retain_flag:
            data = self._encryptor.decrypt(data)
            if remote is None:  # we are in STAGE_ADDR
                if not data:
                    self._write_eof(publish_packet.topic_name)
                    return

                header_result = common.parse_header(data)
                if header_result is None:
                    logging.error(
                        "Can not parse header when handling mqtt client({}) connection{}."
                        .format(publish_packet.topic_name, self._peername))
                    self._write_eof(publish_packet.topic_name)
                    return

                addrtype, remote_addr, remote_port, header_length = header_result
                logging.info(
                    "Connecting to remote {}:{} from mqtt client({}) connection{}."
                    .format(common.to_str(remote_addr), remote_port,
                            publish_packet.topic_name, self._peername))

                remote = RelayRemoteProtocol(self._loop, self,
                                             publish_packet.topic_name)
                self._topic_to_remote[publish_packet.topic_name] = remote
                self._loop.create_task(
                    self.create_connection(remote, common.to_str(remote_addr),
                                           remote_port))

                if len(data) > header_length:
                    remote.write(data[header_length:])
            else:  # now in STAGE_STREAM
                remote.write(data)
        else:
            if remote is not None:
                remote.close(force=True)
예제 #5
0
    def handle_stage_addr(self, data):
        header_result = common.parse_header(data)
        if header_result is None:
            logger.error(
                "can not parse header when handling connection from {0}:{1}".
                format(self._peername[0], self._peername[1]))
            self._transport.close()
            return

        addrtype, remote_addr, remote_port, header_length = header_result
        logger.info('connecting to %s:%d from %s:%d' %
                    (common.to_str(remote_addr), remote_port,
                     self._peername[0], self._peername[1]))

        self._remote = RelayRemoteProtocol(self)
        self._loop.create_task(
            self.create_connection(common.to_str(remote_addr), remote_port))

        self._stage = STAGE_STREAM
        if len(data) > header_length:
            self._remote.write(data[header_length:])
예제 #6
0
def daemon_stop(pid_file):
    import errno
    try:
        with open(pid_file) as f:
            buf = f.read()
            pid = common.to_str(buf)
            if not buf:
                logging.error('not running')
    except IOError as e:
        shell.print_exception(e)
        if e.errno == errno.ENOENT:
            # always exit 0 if we are sure daemon is not running
            logging.error('not running')
            return
        sys.exit(1)
    pid = int(pid)
    if pid > 0:
        try:
            os.kill(pid, signal.SIGTERM)
        except OSError as e:
            if e.errno == errno.ESRCH:
                logging.error('not running')
                # always exit 0 if we are sure daemon is not running
                return
            shell.print_exception(e)
            sys.exit(1)
    else:
        logging.error('pid is not positive: %d', pid)

    # sleep for maximum 10s
    for i in range(0, 200):
        try:
            # query for the pid
            os.kill(pid, 0)
        except OSError as e:
            if e.errno == errno.ESRCH:
                break
        time.sleep(0.05)
    else:
        logging.error('timed out when stopping pid %d', pid)
        sys.exit(1)
    print('stopped')
    os.unlink(pid_file)
예제 #7
0
def check_config(config):
    if config.get('daemon', None) == 'stop':
        # no need to specify configuration for daemon stop
        return

    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:
        logging.error(e)
        sys.exit(2)

    if not config.get('password', None) \
            and not config.get('port_password', None) \
            and not config.get('manager_address'):
        logging.error('password or port_password not specified')
        print_help()
        sys.exit(2)

    if 'local_port' in config:
        config['local_port'] = int(config['local_port'])

    if 'server_port' in config and type(config['server_port']) != list:
        config['server_port'] = int(config['server_port'])

    if 'tunnel_remote_port' in config:
        config['tunnel_remote_port'] = int(config['tunnel_remote_port'])
    if 'tunnel_port' in config:
        config['tunnel_port'] = int(config['tunnel_port'])

    if config.get('local_address', '') in [b'0.0.0.0']:
        logging.warn('warning: local set to listen on 0.0.0.0, it\'s not safe')
    if config.get('server', '') in ['127.0.0.1', 'localhost']:
        logging.warn('warning: server set to listen on %s:%s, are you sure?' %
                     (to_str(config['server']), config['server_port']))
    if (config.get('method', '') or '').lower() == 'table':
        logging.warn('warning: table is not safe; please use a safer cipher, '
                     'like AES-256-CFB')
    if (config.get('method', '') or '').lower() == 'rc4':
        logging.warn('warning: RC4 is not safe; please use a safer cipher, '
                     'like AES-256-CFB')
    if config.get('timeout', 300) < 100:
        logging.warn('warning: your timeout %d seems too short' %
                     int(config.get('timeout')))
    if config.get('timeout', 300) > 600:
        logging.warn('warning: your timeout %d seems too long' %
                     int(config.get('timeout')))
    if config.get('password') in [b'mypassword']:
        logging.error('DON\'T USE DEFAULT PASSWORD! Please change it in your '
                      'config.json!')
        sys.exit(1)
    if config.get('user', None) is not None:
        if os.name != 'posix':
            logging.error('user can be used only on Unix')
            sys.exit(1)
    if config.get('dns_server', None) is not None:
        if type(config['dns_server']) != list:
            config['dns_server'] = to_str(config['dns_server'])
        logging.info('Specified DNS server: %s' % config['dns_server'])

    cryptor.try_cipher(config['password'], config['method'])
예제 #8
0
def get_config():
    global verbose

    logging.basicConfig(level=logging.INFO,
                        format='%(levelname)-s: %(message)s')
    shortopts = 'hd:s:p:k:m:c:t:vqa'
    longopts = ['help', 'pid-file=', 'log-file=', 'workers=',
                    'forbidden-ip=', 'user='******'version']
    try:
        config_path = find_config()
        optlist, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
        for key, value in optlist:
            if key == '-c':
                config_path = value

        if config_path:
            logging.info('loading config from %s' % config_path)
            with open(config_path, 'rb') as f:
                try:
                    config = parse_json_in_str(f.read().decode('utf8'))
                except ValueError as e:
                    logging.error('found an error in config.json: %s',
                                  e.message)
                    sys.exit(1)
        else:
            config = {}

        v_count = 0
        for key, value in optlist:
            if key == '-p':
                config['server_port'] = int(value)
            elif key == '-k':
                config['password'] = to_bytes(value)
            elif key == '-l':
                config['local_port'] = int(value)
            elif key == '-s':
                config['server'] = to_str(value)
            elif key == '-m':
                config['method'] = to_str(value)
            elif key == '-b':
                config['local_address'] = to_str(value)
            elif key == '-v':
                v_count += 1
                # '-vv' turns on more verbose mode
                config['verbose'] = v_count
            elif key == '-t':
                config['timeout'] = int(value)
            elif key == '--workers':
                config['workers'] = int(value)
            elif key == '--user':
                config['user'] = to_str(value)
            elif key == '--forbidden-ip':
                config['forbidden_ip'] = to_str(value).split(',')
            elif key in ('-h', '--help'):
                print_help()
                sys.exit(0)
            elif key == '--version':
                print_version()
                sys.exit(0)
            elif key == '-d':
                config['daemon'] = to_str(value)
            elif key == '--pid-file':
                config['pid-file'] = to_str(value)
            elif key == '--log-file':
                config['log-file'] = to_str(value)
            elif key == '-q':
                v_count -= 1
                config['verbose'] = v_count
    except getopt.GetoptError as e:
        print(e, file=sys.stderr)
        print_help()
        sys.exit(2)

    if not config:
        logging.error('config not specified')
        print_help()
        sys.exit(2)

    config['password'] = to_bytes(config.get('password', b''))
    config['method'] = to_str(config.get('method', 'aes-256-cfb'))
    config['port_password'] = config.get('port_password', None)
    config['timeout'] = int(config.get('timeout', 300))
    config['workers'] = config.get('workers', 1)
    config['pid-file'] = config.get('pid-file', '/var/run/fullyconnect.pid')
    config['log-file'] = config.get('log-file', '/var/log/fullyconnect.log')
    config['verbose'] = config.get('verbose', False)
    config['local_address'] = to_str(config.get('local_address', '127.0.0.1'))
    config['local_port'] = config.get('local_port', 1080)
    config['server_port'] = config.get('server_port', 8388)

    config['tunnel_remote'] = to_str(config.get('tunnel_remote', '8.8.8.8'))
    config['tunnel_remote_port'] = config.get('tunnel_remote_port', 53)
    config['tunnel_port'] = config.get('tunnel_port', 53)

    logging.getLogger('').handlers = []
    logging.addLevelName(VERBOSE_LEVEL, 'VERBOSE')
    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 [%(name)s] %(levelname)-8s %(message)s',
                        datefmt='%Y-%m-%d %H:%M:%S')

    check_config(config)

    return config