mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-21 18:03:54 +03:00
Advanced A LOT on new tunnel server & client. First test passed
This commit is contained in:
parent
865601b3c8
commit
f402dadb0a
@ -33,6 +33,7 @@ import socketserver
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
import select
|
||||
import typing
|
||||
@ -48,6 +49,7 @@ TUNNEL_LISTENING, TUNNEL_OPENING, TUNNEL_PROCESSING, TUNNEL_ERROR = 0, 1, 2, 3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
@ -69,6 +71,10 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
local_port: int = 0,
|
||||
check_certificate: bool = True,
|
||||
) -> None:
|
||||
|
||||
if local_port == 0:
|
||||
local_port = random.randrange(33000, 53000)
|
||||
|
||||
super().__init__(
|
||||
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
||||
)
|
||||
@ -82,7 +88,9 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
self.status = TUNNEL_LISTENING
|
||||
|
||||
if timeout:
|
||||
self.timer = threading.Timer(timeout, ForwardServer.__checkStarted, args=(self,))
|
||||
self.timer = threading.Timer(
|
||||
timeout, ForwardServer.__checkStarted, args=(self,)
|
||||
)
|
||||
self.timer.start()
|
||||
else:
|
||||
self.timer = None
|
||||
@ -116,7 +124,7 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
self.server.current_connections += 1
|
||||
self.server.status = TUNNEL_OPENING
|
||||
|
||||
# If server processing is over time
|
||||
# If server processing is over time
|
||||
if self.server.stoppable:
|
||||
logger.info('Rejected timedout connection try')
|
||||
self.request.close() # End connection without processing it
|
||||
@ -134,7 +142,7 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
|
||||
# If ignore remote certificate
|
||||
if self.server.check_certificate is False:
|
||||
context.check_hostname = False
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
logger.warning('Certificate checking is disabled!')
|
||||
|
||||
@ -149,7 +157,9 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
data = ssl_socket.recv(2)
|
||||
if data != b'OK':
|
||||
data += ssl_socket.recv(128)
|
||||
raise Exception(f'Error received: {data.decode()}') # Notify error
|
||||
raise Exception(
|
||||
f'Error received: {data.decode()}'
|
||||
) # Notify error
|
||||
|
||||
# All is fine, now we can tunnel data
|
||||
self.process(remote=ssl_socket)
|
||||
@ -185,10 +195,17 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
def _run(server: ForwardServer) -> None:
|
||||
logger.debug('Starting forwarder: %s -> %s, timeout: %d', server.server_address, server.remote, server.timeout)
|
||||
logger.debug(
|
||||
'Starting forwarder: %s -> %s, timeout: %d',
|
||||
server.server_address,
|
||||
server.remote,
|
||||
server.timeout,
|
||||
)
|
||||
server.serve_forever()
|
||||
logger.debug('Stoped forwarded %s -> %s', server.server_address, server.remote)
|
||||
logger.debug('Stoped forwarder %s -> %s', server.server_address, server.remote)
|
||||
|
||||
|
||||
def forward(
|
||||
remote: typing.Tuple[str, int],
|
||||
@ -197,6 +214,7 @@ def forward(
|
||||
local_port: int = 0,
|
||||
check_certificate=True,
|
||||
) -> ForwardServer:
|
||||
|
||||
fs = ForwardServer(
|
||||
remote=remote,
|
||||
ticket=ticket,
|
||||
@ -209,8 +227,10 @@ def forward(
|
||||
|
||||
return fs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
log = logging.getLogger()
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
@ -221,4 +241,12 @@ if __name__ == "__main__":
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
fs = forward(('172.27.0.1', 7777), '1'*64, local_port=49999, timeout=10, check_certificate=False)
|
||||
ticket = 'qcdn2jax6tx4nljdyed61hm3iqbld5nf44zxbh9gf355ofw2'
|
||||
|
||||
fs = forward(
|
||||
('172.27.0.1', 7777),
|
||||
ticket,
|
||||
local_port=49999,
|
||||
timeout=60,
|
||||
check_certificate=False,
|
||||
)
|
||||
|
@ -154,7 +154,7 @@ class Handler:
|
||||
"""
|
||||
return self._headers
|
||||
|
||||
def header(self, headerName) -> typing.Optional[str]:
|
||||
def header(self, headerName: str) -> typing.Optional[str]:
|
||||
"""
|
||||
Get's an specific header name from REST request
|
||||
:param headerName: name of header to get
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2020 Virtual Cable S.L.U.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -50,13 +50,13 @@ from uds.core import VERSION as UDS_VERSION
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CLIENT_VERSION = UDS_VERSION
|
||||
REQUIRED_CLIENT_VERSION = '3.0.0'
|
||||
REQUIRED_CLIENT_VERSION = '3.5.0'
|
||||
|
||||
|
||||
# Enclosed methods under /actor path
|
||||
# Enclosed methods under /client path
|
||||
class Client(Handler):
|
||||
"""
|
||||
Processes actor requests
|
||||
Processes Client requests
|
||||
"""
|
||||
authenticated = False # Client requests are not authenticated
|
||||
|
||||
@ -155,7 +155,7 @@ class Client(Handler):
|
||||
return Client.result(result={
|
||||
'script': transportScript,
|
||||
'signature': signature, # It is already on base64
|
||||
'params': codecs.encode(codecs.encode(json.dumps(params), 'bz2'), 'base64').decode(),
|
||||
'params': codecs.encode(codecs.encode(json.dumps(params).encode(), 'bz2'), 'base64').decode(),
|
||||
})
|
||||
except ServiceNotReadyError as e:
|
||||
# Refresh ticket and make this retrayable
|
||||
|
98
server/src/uds/REST/methods/tunnel.py
Normal file
98
server/src/uds/REST/methods/tunnel.py
Normal file
@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import logging
|
||||
import typing
|
||||
from uds.models.user_service import UserService
|
||||
|
||||
from uds import models
|
||||
from uds.REST import Handler
|
||||
from uds.REST import AccessDenied
|
||||
from uds.core.auths.auth import isTrustedSource
|
||||
from uds.core.util import log, net
|
||||
from uds.core.util.stats import events
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Enclosed methods under /tunnel path
|
||||
class Tunnel(Handler):
|
||||
"""
|
||||
Processes tunnel requests
|
||||
"""
|
||||
|
||||
authenticated = False # Client requests are not authenticated
|
||||
|
||||
def get(self) -> typing.MutableMapping[str, typing.Any]:
|
||||
"""
|
||||
Processes get requests, currently none
|
||||
"""
|
||||
logger.debug(
|
||||
'Tunnel parameters for GET: %s from %s', self._args, self._request.ip
|
||||
)
|
||||
|
||||
if (
|
||||
not isTrustedSource(self._request.ip)
|
||||
or len(self._args) > 2
|
||||
or len(self._args[0]) != 48
|
||||
):
|
||||
# Invalid requests
|
||||
raise AccessDenied()
|
||||
|
||||
# Try to get ticket from DB
|
||||
try:
|
||||
user, userService, host, port, _ = models.TicketStore.get_for_tunnel(
|
||||
self._args[0]
|
||||
)
|
||||
start = len(self._args) == 2 # Start requests include source IP request
|
||||
|
||||
if net.ipToLong(self._args[1][:32]) == 0:
|
||||
raise Exception('Invalid from IP')
|
||||
|
||||
data = {}
|
||||
if start:
|
||||
events.addEvent(
|
||||
userService.deployed_service,
|
||||
events.ET_TUNNEL_ACCESS,
|
||||
username=user.pretty_name,
|
||||
srcip=self._args[1],
|
||||
dstip=host,
|
||||
uniqueid=userService.unique_id,
|
||||
)
|
||||
msg = f'User {user.name} started tunnel to {host}:{port} from {self._args[1]}.'
|
||||
log.doLog(user.manager, log.INFO, msg)
|
||||
log.doLog(userService, log.INFO, msg)
|
||||
data = {'host': host, 'port': port}
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
logger.info('Ticket ignored: %s', e)
|
||||
raise AccessDenied()
|
@ -154,6 +154,10 @@ def webLoginRequired(
|
||||
|
||||
return decorator
|
||||
|
||||
# Helper for checking if requests is from trusted source
|
||||
def isTrustedSource(ip: str) -> bool:
|
||||
return net.ipInNetwork(ip, GlobalConfig.TRUSTED_SOURCES.get(True))
|
||||
|
||||
|
||||
# Decorator to protect pages that needs to be accessed from "trusted sites"
|
||||
def trustedSourceRequired(
|
||||
@ -170,7 +174,7 @@ def trustedSourceRequired(
|
||||
Wrapped function for decorator
|
||||
"""
|
||||
try:
|
||||
if not net.ipInNetwork(request.ip, GlobalConfig.TRUSTED_SOURCES.get(True)):
|
||||
if not isTrustedSource(request.ip):
|
||||
return HttpResponseForbidden() # type: ignore
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
|
@ -48,7 +48,7 @@ logger = logging.getLogger(__name__)
|
||||
OT_USERSERVICE, OT_PUBLICATION, OT_DEPLOYED_SERVICE, OT_SERVICE, OT_PROVIDER, OT_USER, OT_GROUP, OT_AUTHENTICATOR, OT_METAPOOL = range(9) # @UndefinedVariable
|
||||
|
||||
# Dict for translations
|
||||
transDict: typing.Dict['Model', int] = {
|
||||
transDict: typing.Dict[typing.Type['Model'], int] = {
|
||||
models.UserService: OT_USERSERVICE,
|
||||
models.ServicePoolPublication: OT_PUBLICATION,
|
||||
models.ServicePool: OT_DEPLOYED_SERVICE,
|
||||
|
@ -146,7 +146,6 @@ class Transport(Module):
|
||||
Helper method to check if transport supports requested operating system.
|
||||
Class method
|
||||
"""
|
||||
logger.debug('Checking suported os %s against %s', osName, cls.supportedOss)
|
||||
return cls.supportedOss.count(osName) > 0
|
||||
|
||||
@classmethod
|
||||
@ -242,7 +241,7 @@ class Transport(Module):
|
||||
script, signature, params = self.getUDSTransportScript(userService, transport, ip, os, user, password, request)
|
||||
logger.debug('Transport script: %s', script)
|
||||
|
||||
return codecs.encode(codecs.encode(script, 'bz2'), 'base64').decode().replace('\n', ''), signature, params
|
||||
return codecs.encode(codecs.encode(script.encode(), 'bz2'), 'base64').decode().replace('\n', ''), signature, params
|
||||
|
||||
def getLink(
|
||||
self,
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2015-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2015-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -32,6 +32,7 @@
|
||||
"""
|
||||
import datetime
|
||||
import time
|
||||
import typing
|
||||
import logging
|
||||
|
||||
import bitarray
|
||||
@ -61,10 +62,10 @@ class CalendarChecker:
|
||||
|
||||
cache = Cache('calChecker')
|
||||
|
||||
def __init__(self, calendar: Calendar):
|
||||
def __init__(self, calendar: Calendar) -> None:
|
||||
self.calendar = calendar
|
||||
|
||||
def _updateData(self, dtime: datetime.datetime):
|
||||
def _updateData(self, dtime: datetime.datetime) -> bitarray.bitarray:
|
||||
logger.debug('Updating %s', dtime)
|
||||
# Else, update the array
|
||||
CalendarChecker.updates += 1
|
||||
@ -124,7 +125,6 @@ class CalendarChecker:
|
||||
return data
|
||||
|
||||
def _updateEvents(self, checkFrom, startEvent=True):
|
||||
|
||||
next_event = None
|
||||
for rule in self.calendar.rules.all():
|
||||
# logger.debug('RULE: start = {}, checkFrom = {}, end'.format(rule.start.date(), checkFrom.date()))
|
||||
@ -141,7 +141,7 @@ class CalendarChecker:
|
||||
|
||||
return next_event
|
||||
|
||||
def check(self, dtime=None):
|
||||
def check(self, dtime=None) -> int:
|
||||
"""
|
||||
Checks if the given time is a valid event on calendar
|
||||
@param dtime: Datetime object to check
|
||||
@ -180,10 +180,9 @@ class CalendarChecker:
|
||||
|
||||
return data[dtime.hour * 60 + dtime.minute]
|
||||
|
||||
def nextEvent(self, checkFrom=None, startEvent=True, offset=None):
|
||||
def nextEvent(self, checkFrom=None, startEvent=True, offset=None) -> typing.Optional[datetime.datetime]:
|
||||
"""
|
||||
Returns next event for this interval
|
||||
Returns a list of two elements. First is datetime of event begining, second is timedelta of duration
|
||||
"""
|
||||
logger.debug('Obtaining nextEvent')
|
||||
if checkFrom is None:
|
||||
@ -215,5 +214,5 @@ class CalendarChecker:
|
||||
|
||||
return next_event
|
||||
|
||||
def debug(self):
|
||||
def debug(self) -> str:
|
||||
return "Calendar checker for {}".format(self.calendar)
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -40,6 +39,7 @@ from uds.core.util import os_detector as OsDetector
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.http import HttpRequest # pylint: disable=ungrouped-imports
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -101,7 +101,7 @@ def extractKey(dictionary: typing.Dict, key: typing.Any, **kwargs) -> str:
|
||||
return value
|
||||
|
||||
|
||||
def checkBrowser(request: 'HttpRequest', browser: str) -> bool:
|
||||
def checkBrowser(request: 'ExtendedHttpRequest', browser: str) -> bool:
|
||||
"""
|
||||
Known browsers right now:
|
||||
ie[version]
|
||||
@ -113,7 +113,7 @@ def checkBrowser(request: 'HttpRequest', browser: str) -> bool:
|
||||
|
||||
for b, requires in _browsers.items():
|
||||
if browser.startswith(b):
|
||||
if request.os.Browser not in requires:
|
||||
if request.os['Browser'] not in requires:
|
||||
return False
|
||||
browser = browser[len(b):] # remove "browser name" from string
|
||||
break
|
||||
@ -132,7 +132,7 @@ def checkBrowser(request: 'HttpRequest', browser: str) -> bool:
|
||||
needs_version = 0
|
||||
|
||||
try:
|
||||
version = int(request.os.Version.split('.')[0])
|
||||
version = int(request.os['Version'].split('.')[0])
|
||||
if needs == '<':
|
||||
return version < needs_version
|
||||
if needs == '>':
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2013-2019 Virtual Cable S.L.
|
||||
# Copyright (c) 2013-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -40,33 +40,43 @@ from uds.models import Provider, Service, ServicePool, Authenticator
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
EventTupleType = typing.Tuple[datetime.datetime, str, str, str, str, int]
|
||||
EventClass = typing.TypeVar('EventClass', Provider, Service, ServicePool, Authenticator)
|
||||
EventClass = typing.Union[Provider, Service, ServicePool, Authenticator]
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.db.models import Model
|
||||
|
||||
# Posible events, note that not all are used by every possible owner type
|
||||
(
|
||||
# Login - logout
|
||||
ET_LOGIN, ET_LOGOUT,
|
||||
ET_LOGIN,
|
||||
ET_LOGOUT,
|
||||
# Service access
|
||||
ET_ACCESS,
|
||||
# Cache performance
|
||||
ET_CACHE_HIT, ET_CACHE_MISS,
|
||||
ET_CACHE_HIT,
|
||||
ET_CACHE_MISS,
|
||||
# Platforms detected
|
||||
ET_PLATFORM,
|
||||
# Plugin downloads
|
||||
ET_PLUGIN_DOWNLOAD,
|
||||
) = range(7)
|
||||
ET_TUNNEL_ACCESS,
|
||||
) = range(8)
|
||||
|
||||
(
|
||||
OT_PROVIDER, OT_SERVICE, OT_DEPLOYED, OT_AUTHENTICATOR,
|
||||
OT_PROVIDER,
|
||||
OT_SERVICE,
|
||||
OT_DEPLOYED,
|
||||
OT_AUTHENTICATOR,
|
||||
) = range(4)
|
||||
|
||||
__transDict = {
|
||||
__transDict: typing.Mapping[typing.Type['Model'], int] = {
|
||||
ServicePool: OT_DEPLOYED,
|
||||
Service: OT_SERVICE,
|
||||
Provider: OT_PROVIDER,
|
||||
Authenticator: OT_AUTHENTICATOR,
|
||||
}
|
||||
|
||||
|
||||
def addEvent(obj: EventClass, eventType: int, **kwargs) -> bool:
|
||||
"""
|
||||
Adds a event stat to specified object
|
||||
@ -81,7 +91,9 @@ def addEvent(obj: EventClass, eventType: int, **kwargs) -> bool:
|
||||
return statsManager().addEvent(__transDict[type(obj)], obj.id, eventType, **kwargs)
|
||||
|
||||
|
||||
def getEvents(obj: EventClass, eventType: int, **kwargs) -> typing.Generator[EventTupleType, None, None]:
|
||||
def getEvents(
|
||||
obj: EventClass, eventType: int, **kwargs
|
||||
) -> typing.Generator[EventTupleType, None, None]:
|
||||
"""
|
||||
Get events
|
||||
|
||||
@ -108,6 +120,15 @@ def getEvents(obj: EventClass, eventType: int, **kwargs) -> typing.Generator[Eve
|
||||
else:
|
||||
owner_id = obj.pk
|
||||
|
||||
for i in statsManager().getEvents(__transDict[type_], eventType, owner_id=owner_id, since=since, to=to):
|
||||
val = (datetime.datetime.fromtimestamp(i.stamp), i.fld1, i.fld2, i.fld3, i.fld4, i.event_type)
|
||||
for i in statsManager().getEvents(
|
||||
__transDict[type_], eventType, owner_id=owner_id, since=since, to=to
|
||||
):
|
||||
val = (
|
||||
datetime.datetime.fromtimestamp(i.stamp),
|
||||
i.fld1,
|
||||
i.fld2,
|
||||
i.fld3,
|
||||
i.fld4,
|
||||
i.event_type,
|
||||
)
|
||||
yield val
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2020 Virtual Cable S.L.U.
|
||||
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
|
@ -40,12 +40,15 @@ from uds.core.managers import cryptoManager
|
||||
from .uuid_model import UUIDModel
|
||||
from .util import getSqlDatetime
|
||||
|
||||
from .user import User
|
||||
from .user_service import UserService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ValidatorType = typing.Callable[[typing.Any], bool]
|
||||
|
||||
SECURED = '#SECURE#' # Just a "different" owner. If used anywhere, it's not important (will not fail), but
|
||||
SECURED = '#SECURE#' # Just a "different" owner. If used anywhere, it's not important (will not fail), but weird enough
|
||||
|
||||
|
||||
class TicketStore(UUIDModel):
|
||||
"""
|
||||
@ -142,7 +145,9 @@ class TicketStore(UUIDModel):
|
||||
data: bytes = t.data
|
||||
|
||||
if secure: # Owner has already been tested and it's not emtpy
|
||||
data = cryptoManager().AESDecrypt(data, typing.cast(str, owner).encode())
|
||||
data = cryptoManager().AESDecrypt(
|
||||
data, typing.cast(str, owner).encode()
|
||||
)
|
||||
|
||||
data = pickle.loads(data)
|
||||
|
||||
@ -174,7 +179,69 @@ class TicketStore(UUIDModel):
|
||||
t.validity = validity
|
||||
t.save(update_fields=['validity', 'stamp'])
|
||||
except TicketStore.DoesNotExist:
|
||||
raise Exception('Does not exists')
|
||||
raise TicketStore.InvalidTicket('Does not exists')
|
||||
|
||||
# Especific methods for tunnel
|
||||
@staticmethod
|
||||
def create_for_tunnel(
|
||||
userService: 'UserService',
|
||||
port: int,
|
||||
host: typing.Optional[str] = None,
|
||||
extra: typing.Optional[typing.Mapping[str, typing.Any]] = None,
|
||||
validity: int = 60 * 60 * 24, # 24 Hours default validity for tunnel tickets
|
||||
) -> str:
|
||||
owner = cryptoManager().randomString(length=8)
|
||||
data = {
|
||||
'u': userService.user.uuid,
|
||||
's': userService.uuid,
|
||||
'h': host,
|
||||
'p': port,
|
||||
'e': extra,
|
||||
}
|
||||
return (
|
||||
TicketStore.create(
|
||||
data=data,
|
||||
validity=validity,
|
||||
owner=owner,
|
||||
secure=True,
|
||||
)
|
||||
+ owner
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_for_tunnel(
|
||||
ticket: str,
|
||||
) -> typing.Tuple[
|
||||
'User',
|
||||
'UserService',
|
||||
typing.Optional[str],
|
||||
int,
|
||||
typing.Optional[typing.Mapping[str, typing.Any]],
|
||||
]:
|
||||
"""
|
||||
Returns the ticket for a tunneled connection
|
||||
The returned value is a tuple:
|
||||
(User, UserService, Host (nullable), Port, Extra Dict)
|
||||
"""
|
||||
try:
|
||||
if len(ticket) != 48:
|
||||
raise Exception(f'Invalid ticket format: {ticket!r}')
|
||||
|
||||
uuid, owner = ticket[:-8], ticket[-8:]
|
||||
data = TicketStore.get(uuid, invalidate=False, owner=owner, secure=True)
|
||||
|
||||
# Now, ensure elements exists, onwershit is fine
|
||||
# if not found any, will raise an execption
|
||||
user = User.objects.get(uuid=data['u'])
|
||||
userService = UserService.objects.get(uuid=data['s'], user=user)
|
||||
host = data['h']
|
||||
|
||||
if not host:
|
||||
host = userService.getInstance().getIp()
|
||||
|
||||
return (user, userService, host, data['p'], data['e'])
|
||||
except Exception as e:
|
||||
raise TicketStore.InvalidTicket(str(e))
|
||||
|
||||
@staticmethod
|
||||
def cleanup() -> None:
|
||||
|
@ -41,8 +41,7 @@ from uds.core import transports
|
||||
# TODO: do this
|
||||
def createADUser():
|
||||
try:
|
||||
from . import AD
|
||||
|
||||
from . import AD # type: ignore
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
@ -372,7 +371,9 @@ class BaseRDPTransport(transports.Transport):
|
||||
user: 'models.User',
|
||||
password: str,
|
||||
) -> typing.Dict[str, str]:
|
||||
return self.processUserPassword(typing.cast('models.UserService', userService), user, password)
|
||||
return self.processUserPassword(
|
||||
typing.cast('models.UserService', userService), user, password
|
||||
)
|
||||
|
||||
def getScript(
|
||||
self, scriptNameTemplate: str, osName: str, params: typing.Dict[str, typing.Any]
|
||||
|
@ -1,7 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2018 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -89,6 +88,27 @@ class TRDPTransport(BaseRDPTransport):
|
||||
tab=gui.TUNNEL_TAB,
|
||||
)
|
||||
|
||||
ticketValidity = gui.NumericField(
|
||||
length=3,
|
||||
label=_('Tunnel ticket validity time (seconds)'),
|
||||
defvalue='7200',
|
||||
minValue=60, # One minute as min
|
||||
maxValue=7*60*60*24, # one week as max
|
||||
order=3,
|
||||
tooltip=_('Maximum validity time for user ticket to allow reconnection'),
|
||||
required=True,
|
||||
tab=gui.TUNNEL_TAB,
|
||||
)
|
||||
|
||||
verifyCertificate = gui.CheckBoxField(
|
||||
label=_('Force SSL certificate verification'),
|
||||
order=23,
|
||||
tooltip=_('If enabled, the certificate of tunnel server will be verified (recommended).'),
|
||||
defvalue=gui.TRUE,
|
||||
tab=gui.TUNNEL_TAB
|
||||
)
|
||||
|
||||
|
||||
useEmptyCreds = BaseRDPTransport.useEmptyCreds
|
||||
fixedName = BaseRDPTransport.fixedName
|
||||
fixedPassword = BaseRDPTransport.fixedPassword
|
||||
@ -152,15 +172,14 @@ class TRDPTransport(BaseRDPTransport):
|
||||
width, height = self.screenSize.value.split('x')
|
||||
depth = self.colorDepth.value
|
||||
|
||||
tunpass = ''.join(
|
||||
random.SystemRandom().choice(string.ascii_letters + string.digits)
|
||||
for _i in range(12)
|
||||
ticket = TicketStore.create_for_tunnel(
|
||||
userService=userService,
|
||||
port=3389,
|
||||
validity=self.ticketValidity.num()
|
||||
)
|
||||
tunuser = TicketStore.create(tunpass)
|
||||
|
||||
sshHost, sshPort = self.tunnelServer.value.split(':')
|
||||
|
||||
logger.debug('Username generated: %s, password: %s', tunuser, tunpass)
|
||||
tunHost, tunPort = self.tunnelServer.value.split(':')
|
||||
|
||||
|
||||
r = RDPFile(
|
||||
width == '-1' or height == '-1', width, height, depth, target=os['OS']
|
||||
@ -202,12 +221,11 @@ class TRDPTransport(BaseRDPTransport):
|
||||
)
|
||||
|
||||
sp = {
|
||||
'tunUser': tunuser,
|
||||
'tunPass': tunpass,
|
||||
'tunHost': sshHost,
|
||||
'tunPort': sshPort,
|
||||
'tunHost': tunHost,
|
||||
'tunPort': tunPort,
|
||||
'tunWait': self.tunnelWait.num(),
|
||||
'ip': ip,
|
||||
'tunChk': self.verifyCertificate.isTrue(),
|
||||
'ticket': ticket,
|
||||
'password': password,
|
||||
'this_server': request.build_absolute_uri('/'),
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ from __future__ import unicode_literals
|
||||
# pylint: disable=import-error, no-name-in-module, too-many-format-args, undefined-variable, invalid-sequence-index
|
||||
import subprocess
|
||||
import re
|
||||
from uds.forward import forward # @UnresolvedImport
|
||||
from uds.tunnel import forward # type: ignore
|
||||
|
||||
from uds import tools # @UnresolvedImport
|
||||
from uds import tools # type: ignore
|
||||
|
||||
# Inject local passed sp into globals for functions
|
||||
globals()['sp'] = sp # type: ignore # pylint: disable=undefined-variable
|
||||
@ -41,9 +41,6 @@ if app is None or fnc is None:
|
||||
''')
|
||||
else:
|
||||
# Open tunnel
|
||||
forwardThread, port = forward(sp['tunHost'], sp['tunPort'], sp['tunUser'], sp['tunPass'], sp['ip'], 3389, waitTime=sp['tunWait'])
|
||||
fs = forward(remote=(sp['tunHost'], int(sp['tunPort'])), ticket=sp['ticket'], timeout=sp['tunWait'], check_certificate=sp['tunChk'])
|
||||
|
||||
if forwardThread.status == 2:
|
||||
raise Exception('Unable to open tunnel')
|
||||
|
||||
fnc(app, port) # @UndefinedVariable
|
||||
fnc(app, fs.server_address[1])
|
||||
|
@ -1 +1 @@
|
||||
cL4uPkvN73tWpwdwNqedaOEHXLqmKlxKcg9iKW+QG3zCpPmbVn8nedzlumLfImLPQm+6ktpBscuIIsN5LyOZVuEnmoKWorLknlp5Jwzvp93gZEgGfCl8Y/YVaauvT4pPF5Pmczgsk/YykoAIHg5nllMHS0EdY0Pt/wAX7VgBN0RI1R4Ya5TeFC0b3NNZMg8kijmEYfSyNagPrzvM59SPOF93+iMMsZXaNiC7Lc0zbphS5QcjsMvALOHMyzl3omPVpIBFh/B8Wccx0svjfOp4Mdpt/tq0B9aGzhdEoaoqo3TNqINaPAVl90yPrfr/JrC7qEGNHaUWKgk5nImKJRyYBDiTFuv2TNMvJjrJ8JFgALsefdYhFM5EAo5i4G0bllEfn8g77NICnSlHGZal+x6oFORsmhRxJYXLDLDmeD/BJnHfhaNNSsy/dO/hBtAkkrIO3Qqot2MBo3ip6O/uOcCCdTNbwLCwlN90VahRzmf0j4irKNz5fdKT+Iox+1WT7980U9g3nyVCYz/Qw1b227WXJJ1NYSLGuVF1UU+8kIZPDxq+2FGmyvjTEKHw5RwMSA9Y5bRTDSZYhv9B0DGQ0NLfVJ9lCgQ9Zy99A6nTzIaqGi6FH7oxA5kMuGLAhfeT6sC3QSXfD9sfq2aKRsJgvqXrKiS79nZbkgijnj8nFSnTur8=
|
||||
EGqTg6L0Bu5sqIA7BEUJgNzEwlQkPXfmdXLJ4iA+mRmXx6Z7QCXTYmxdqXmalUKUy4P2x3DvYMut5sM1BTWBYs6LxlE1CbuzBxEOw4VQHXDeW10Zir6C92IOevMZctrJS2zBNIB6RMddhD7HFwQ7LQ/yorUCClXUszjhcCxaTkqjM3KdbVuA4a+R9KF6gHHKCnjGrQXHuGdXjYm2+CRBWv5GBN57htO0VBEvCIrq7ZM/NzWDjBLlsrbkyUHxUoX3Tq0vXS03F3Gu3cxCP24yfYZoJeAHF4iOzU9XqomAYHvhNFEl7bvZz3ZyAIieT+zJJ+/WtGLjxL+ek8Va7V1ZYw9bWYnY0YyEkccupfoOXBy+phCJvcT6UgsL2dRO3yJma+GwejZAzv0JuDCvRmXN/xTbuSexyjIN7fLTmwT8q3DCA+m1CXXEQxLv9D1v2rhGPQOhwvomMKNRwZP3fi1zwL9d0FkgRnS36cz6+YLSf1dyXBDWK3Ez0vqJlRmLVz4GmVuidEnQ1pigzL3HLh3X32b9bd4nqCdSAVqP4dDcZsAQuf7JWYF0k7fA91ROT8nNEVg0zyN/YZU2Qxcr16fyVq1aBTTsDuEnKe0x0GruBRgEBU6Fr1i4eirgpcD+FddlLPvGUvgyH2lfotTcub+sL/BcgOaRvG7niiysJGfCxkc=
|
@ -43,6 +43,7 @@ import uds.web.util.errors as errors
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from django.http import HttpRequest # pylint: disable=ungrouped-imports
|
||||
from uds.core.util.request import ExtendedHttpRequest
|
||||
from uds.web.forms.LoginForm import LoginForm
|
||||
from uds.models import User
|
||||
|
||||
@ -54,7 +55,7 @@ logger = logging.getLogger(__name__)
|
||||
# (None, NumericError) if errorview redirection
|
||||
# (User, password_string) if all is ok
|
||||
def checkLogin( # pylint: disable=too-many-branches, too-many-statements
|
||||
request: 'HttpRequest',
|
||||
request: 'ExtendedHttpRequest',
|
||||
form: 'LoginForm',
|
||||
tag: typing.Optional[str] = None
|
||||
) -> typing.Tuple[typing.Optional['User'], typing.Any]:
|
||||
|
@ -33,6 +33,7 @@ import socketserver
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
import select
|
||||
import typing
|
||||
@ -48,6 +49,7 @@ TUNNEL_LISTENING, TUNNEL_OPENING, TUNNEL_PROCESSING, TUNNEL_ERROR = 0, 1, 2, 3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
@ -69,6 +71,9 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
local_port: int = 0,
|
||||
check_certificate: bool = True,
|
||||
) -> None:
|
||||
|
||||
local_port = local_port or random.randrange(33000, 53000)
|
||||
|
||||
super().__init__(
|
||||
server_address=(LISTEN_ADDRESS, local_port), RequestHandlerClass=Handler
|
||||
)
|
||||
@ -82,7 +87,9 @@ class ForwardServer(socketserver.ThreadingTCPServer):
|
||||
self.status = TUNNEL_LISTENING
|
||||
|
||||
if timeout:
|
||||
self.timer = threading.Timer(timeout, ForwardServer.__checkStarted, args=(self,))
|
||||
self.timer = threading.Timer(
|
||||
timeout, ForwardServer.__checkStarted, args=(self,)
|
||||
)
|
||||
self.timer.start()
|
||||
else:
|
||||
self.timer = None
|
||||
@ -116,7 +123,7 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
self.server.current_connections += 1
|
||||
self.server.status = TUNNEL_OPENING
|
||||
|
||||
# If server processing is over time
|
||||
# If server processing is over time
|
||||
if self.server.stoppable:
|
||||
logger.info('Rejected timedout connection try')
|
||||
self.request.close() # End connection without processing it
|
||||
@ -134,7 +141,7 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
|
||||
# If ignore remote certificate
|
||||
if self.server.check_certificate is False:
|
||||
context.check_hostname = False
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
logger.warning('Certificate checking is disabled!')
|
||||
|
||||
@ -149,7 +156,9 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
data = ssl_socket.recv(2)
|
||||
if data != b'OK':
|
||||
data += ssl_socket.recv(128)
|
||||
raise Exception(f'Error received: {data.decode()}') # Notify error
|
||||
raise Exception(
|
||||
f'Error received: {data.decode()}'
|
||||
) # Notify error
|
||||
|
||||
# All is fine, now we can tunnel data
|
||||
self.process(remote=ssl_socket)
|
||||
@ -185,10 +194,17 @@ class Handler(socketserver.BaseRequestHandler):
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
|
||||
def _run(server: ForwardServer) -> None:
|
||||
logger.debug('Starting forwarder: %s -> %s, timeout: %d', server.server_address, server.remote, server.timeout)
|
||||
logger.debug(
|
||||
'Starting forwarder: %s -> %s, timeout: %d',
|
||||
server.server_address,
|
||||
server.remote,
|
||||
server.timeout,
|
||||
)
|
||||
server.serve_forever()
|
||||
logger.debug('Stoped forwarded %s -> %s', server.server_address, server.remote)
|
||||
logger.debug('Stoped forwarder %s -> %s', server.server_address, server.remote)
|
||||
|
||||
|
||||
def forward(
|
||||
remote: typing.Tuple[str, int],
|
||||
@ -197,6 +213,7 @@ def forward(
|
||||
local_port: int = 0,
|
||||
check_certificate=True,
|
||||
) -> ForwardServer:
|
||||
|
||||
fs = ForwardServer(
|
||||
remote=remote,
|
||||
ticket=ticket,
|
||||
@ -209,8 +226,10 @@ def forward(
|
||||
|
||||
return fs
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
log = logging.getLogger()
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
@ -221,4 +240,12 @@ if __name__ == "__main__":
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
fs = forward(('172.27.0.1', 7777), '1'*64, local_port=49999, timeout=10, check_certificate=False)
|
||||
ticket = 'qcdn2jax6tx4nljdyed61hm3iqbld5nf44zxbh9gf355ofw2'
|
||||
|
||||
fs = forward(
|
||||
('172.27.0.1', 7777),
|
||||
ticket,
|
||||
local_port=49999,
|
||||
timeout=60,
|
||||
check_certificate=False,
|
||||
)
|
||||
|
@ -73,10 +73,17 @@ def read() -> ConfigurationType:
|
||||
|
||||
uds = cfg['uds']
|
||||
|
||||
# Gets and create secret hash
|
||||
h = hashlib.sha256()
|
||||
h.update(uds.get('secret', '').encode())
|
||||
secret = h.hexdigest()
|
||||
|
||||
# Now load and fix uds server url
|
||||
uds_server = uds['uds_server']
|
||||
if uds_server[:4] != 'http':
|
||||
raise Exception('Invalid url for uds server')
|
||||
if uds_server[-1] == '/':
|
||||
uds_server = uds_server[:-1]
|
||||
|
||||
try:
|
||||
# log size
|
||||
@ -84,7 +91,7 @@ def read() -> ConfigurationType:
|
||||
if logsize[-1] == 'M':
|
||||
logsize = logsize[:-1]
|
||||
return ConfigurationType(
|
||||
pidfile=uds.get('pidfile', '/dev/null'),
|
||||
pidfile=uds.get('pidfile', ''),
|
||||
log_level=uds.get('loglevel', 'ERROR'),
|
||||
log_file=uds.get('logfile', ''),
|
||||
log_size=int(logsize)*1024*1024,
|
||||
@ -96,10 +103,10 @@ def read() -> ConfigurationType:
|
||||
ssl_certificate_key=uds['ssl_certificate_key'],
|
||||
ssl_ciphers=uds.get('ssl_ciphers'),
|
||||
ssl_dhparam=uds.get('ssl_dhparam'),
|
||||
uds_server=uds['uds_server'],
|
||||
uds_server=uds_server,
|
||||
secret=secret,
|
||||
allow=set(uds.get('allow', '127.0.0.1').split(',')),
|
||||
storage=uds['storage']
|
||||
storage=uds.get('storage', '')
|
||||
)
|
||||
except ValueError as e:
|
||||
raise Exception(f'Mandatory configuration file in incorrect format: {e.args[0]}. Please, revise {CONFIGFILE}')
|
||||
|
@ -44,7 +44,7 @@ BUFFER_SIZE = 1024 * 16
|
||||
# Handshake for conversation start
|
||||
HANDSHAKE_V1 = b'\x5AMGB\xA5\x01\x00'
|
||||
# Ticket length
|
||||
TICKET_LENGTH = 64
|
||||
TICKET_LENGTH = 48
|
||||
# Admin password length, (size of an hex sha256)
|
||||
PASSWORD_LENGTH = 64
|
||||
# Bandwidth calc time lapse
|
||||
|
@ -54,7 +54,8 @@ class Proxy:
|
||||
|
||||
@staticmethod
|
||||
def getFromUds(
|
||||
cfg: config.ConfigurationType, ticket: bytes
|
||||
cfg: config.ConfigurationType, ticket: bytes,
|
||||
address: typing.Tuple[str, int]
|
||||
) -> typing.MutableMapping[str, typing.Any]:
|
||||
# Sanity checks
|
||||
if len(ticket) != consts.TICKET_LENGTH:
|
||||
@ -69,14 +70,16 @@ class Proxy:
|
||||
continue # Correctus
|
||||
raise Exception(f'TICKET INVALID (char {i} at pos {n})')
|
||||
|
||||
# Gets the UDS connection data
|
||||
# r = requests.get(f'{cfg.uds_server}/XXXX/ticket')
|
||||
# if not r.ok:
|
||||
# raise Exception(f'TICKET INVALID (check {r.json})')
|
||||
return {
|
||||
'host': ['172.27.1.15', '172.27.0.10'][int(ticket[0]) - 0x30],
|
||||
'port': '3389',
|
||||
}
|
||||
try:
|
||||
url = cfg.uds_server + '/' + ticket.decode() + '/' + address[0]
|
||||
r = requests.get(url, headers={'content-type': 'application/json'})
|
||||
if not r.ok:
|
||||
raise Exception(r.content)
|
||||
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise Exception(f'TICKET COMMS ERROR: {e!s}')
|
||||
|
||||
|
||||
@staticmethod
|
||||
async def doProxy(source, destination, counter: stats.StatsSingleCounter) -> None:
|
||||
@ -155,7 +158,7 @@ class Proxy:
|
||||
|
||||
# Ticket received, now process it with UDS
|
||||
try:
|
||||
result = await curio.run_in_thread(Proxy.getFromUds, self.cfg, ticket)
|
||||
result = await curio.run_in_thread(Proxy.getFromUds, self.cfg, ticket, address)
|
||||
except Exception as e:
|
||||
logger.error('ERROR %s', e.args[0] if e.args else e)
|
||||
await source.sendall(b'ERROR INVALID TICKET')
|
||||
|
@ -1,11 +1,13 @@
|
||||
# Sample DS tunnel configuration
|
||||
|
||||
# Pid file location
|
||||
pidfile = /tmp/udstunnel.pid
|
||||
|
||||
# Log level, valid are DEBUG, INFO, WARN, ERROR. Defaults to ERROR
|
||||
loglevel = DEBUG
|
||||
# Log file, No default
|
||||
logfile = /tmp/tunnel.log
|
||||
# Log file, Defaults to stdout
|
||||
# logfile = /tmp/tunnel.log
|
||||
|
||||
# Max log size before rotating it. Defaults to 32 MB.
|
||||
# The value is in MB. You can include or not the M string at end.
|
||||
logsize = 20M
|
||||
@ -28,8 +30,12 @@ ssl_certificate_key = tests/testing.key
|
||||
ssl_ciphers = ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384
|
||||
ssl_dhparam = /etc/certs/dhparam.pem
|
||||
|
||||
# UDS server location. https NEEDS valid certificate
|
||||
uds_server = http://172.27.0.1:8000
|
||||
# UDS server location. https NEEDS valid certificate if https
|
||||
# Must point to tunnel ticket dispatcher URL, that is under /uds/rest/tunnel/ on tunnel server
|
||||
# Valid examples:
|
||||
# http://www.example.com/uds/rest/tunnel/
|
||||
# https://www.example.com:14333/uds/rest/tunnel/
|
||||
uds_server = http://172.27.0.1:8000/uds/rest/tunnel
|
||||
|
||||
# Secret to get access to admin commands. No default for this.
|
||||
# Admin commands and only allowed from localhost
|
||||
@ -39,6 +45,3 @@ secret = MySecret
|
||||
# List of af allowed admin commands ips (only IPs, no networks or whatever)
|
||||
# defaults to localhost (change if listen address is different from 0.0.0.0)
|
||||
allow = 127.0.0.1
|
||||
|
||||
# Local storage configuration, for stats, etc...
|
||||
storage = .
|
||||
|
@ -157,8 +157,9 @@ def tunnel_main():
|
||||
# Create pid file
|
||||
try:
|
||||
setup_log(cfg)
|
||||
with open(cfg.pidfile, mode='w') as f:
|
||||
f.write(str(os.getpid()))
|
||||
if cfg.pidfile:
|
||||
with open(cfg.pidfile, mode='w') as f:
|
||||
f.write(str(os.getpid()))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f'Tunnel startup error: {e}\n')
|
||||
return
|
||||
@ -234,6 +235,12 @@ def tunnel_main():
|
||||
except Exception as e:
|
||||
logger.info('KILLING child %s: %s', i[2], e)
|
||||
|
||||
try:
|
||||
if cfg.pidfile:
|
||||
os.unlink(cfg.pidfile)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.info('FINISHED')
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user