diff --git a/apps/settings/api/public.py b/apps/settings/api/public.py index ab313cd4f..f2628a5d4 100644 --- a/apps/settings/api/public.py +++ b/apps/settings/api/public.py @@ -4,13 +4,14 @@ from rest_framework.permissions import AllowAny from common.permissions import IsValidUserOrConnectionToken from common.utils import get_logger, lazyproperty +from common.utils.timezone import local_now from jumpserver.utils import has_valid_xpack_license, get_xpack_license_info from .. import serializers from ..utils import get_interface_setting_or_default logger = get_logger(__name__) -__all__ = ['PublicSettingApi', 'OpenPublicSettingApi'] +__all__ = ['PublicSettingApi', 'OpenPublicSettingApi', 'ServerInfoApi'] class OpenPublicSettingApi(generics.RetrieveAPIView): @@ -55,3 +56,13 @@ class PublicSettingApi(OpenPublicSettingApi): # 提前把异常爆出来 values[name] = getattr(settings, name) return values + + +class ServerInfoApi(generics.RetrieveAPIView): + permission_classes = (IsValidUserOrConnectionToken,) + serializer_class = serializers.ServerInfoSerializer + + def get_object(self): + return { + "CURRENT_TIME": local_now(), + } diff --git a/apps/settings/serializers/public.py b/apps/settings/serializers/public.py index 8429ccd48..3d61bd98c 100644 --- a/apps/settings/serializers/public.py +++ b/apps/settings/serializers/public.py @@ -3,7 +3,9 @@ from rest_framework import serializers -__all__ = ['PublicSettingSerializer', 'PrivateSettingSerializer'] +__all__ = [ + 'PublicSettingSerializer', 'PrivateSettingSerializer', 'ServerInfoSerializer' +] class PublicSettingSerializer(serializers.Serializer): @@ -50,3 +52,7 @@ class PrivateSettingSerializer(PublicSettingSerializer): TICKETS_ENABLED = serializers.BooleanField() CONNECTION_TOKEN_REUSABLE = serializers.BooleanField() + + +class ServerInfoSerializer(serializers.Serializer): + CURRENT_TIME = serializers.DateTimeField() diff --git a/apps/settings/urls/api_urls.py b/apps/settings/urls/api_urls.py index 5cfc3bb36..ef94d02ba 100644 --- a/apps/settings/urls/api_urls.py +++ b/apps/settings/urls/api_urls.py @@ -23,4 +23,5 @@ urlpatterns = [ path('logo/', api.SettingsLogoApi.as_view(), name='settings-logo'), path('public/', api.PublicSettingApi.as_view(), name='public-setting'), path('public/open/', api.OpenPublicSettingApi.as_view(), name='open-public-setting'), + path('server-info/', api.ServerInfoApi.as_view(), name='server-info'), ] diff --git a/apps/settings/utils/__init__.py b/apps/settings/utils/__init__.py index 0927bde18..8983df3ad 100644 --- a/apps/settings/utils/__init__.py +++ b/apps/settings/utils/__init__.py @@ -1,7 +1,8 @@ # coding: utf-8 -# +# from .ldap import * from .common import * from .ping import * from .telnet import * +from .nmap import * diff --git a/apps/settings/utils/nmap.py b/apps/settings/utils/nmap.py new file mode 100644 index 000000000..6e4282678 --- /dev/null +++ b/apps/settings/utils/nmap.py @@ -0,0 +1,60 @@ +import time +import nmap + +from IPy import IP + +from common.utils.timezone import local_now_display + + +def generate_ips(ip_string): + # 支持的格式 + # 192.168.1.1-12 | 192.168.1.1-192.168.1.12 | 192.168.1.0/30 | 192.168.1.1 + ip_list = ip_string.split('-') + ips = [] + try: + if len(ip_list) == 2: + start_ip, end_ip = ip_list + if ip_list[1].find('.') == -1: + end_ip = start_ip[:start_ip.rindex('.') + 1] + end_ip + for ip in range(IP(start_ip).int(), IP(end_ip).int() + 1): + ips.extend(IP(ip)) + else: + ips.extend(IP(ip_list[0])) + except Exception: + ips = [] + return ips + + +def once_nmap(nm, ip, ports, timeout, display): + nmap_version = '.'.join(map(lambda x: str(x), nm.nmap_version())) + display(f'Starting Nmap {nmap_version} at {local_now_display()} for {ip}') + try: + is_ok = True + nm.scan(ip, arguments='-sS -sU -F', ports=ports, timeout=timeout) + tcp_port = nm[ip].get('tcp', {}) + udp_port = nm[ip].get('udp', {}) + display(f'PORT\tSTATE\tSERVICE') + for port, info in tcp_port.items(): + display(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}") + for port, info in udp_port.items(): + display(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}") + except Exception: + is_ok = False + display(f'Nmap scan report for {ip} error.') + return is_ok + + +def verbose_nmap(dest_ip, dest_port=None, timeout=None, display=print): + dest_port = ','.join(list(dest_port)) if dest_port else None + + ips = generate_ips(dest_ip) + nm = nmap.PortScanner() + success_num, start_time = 0, time.time() + display(f'[Summary] Nmap: {len(ips)} IP addresses were scanned') + for ip in ips: + ok = once_nmap(nm, str(ip), dest_port, timeout, display) + if ok: + success_num += 1 + display('') + display(f'[Done] Nmap: {len(ips)} IP addresses ({success_num} hosts up) ' + f'scanned in {round(time.time() - start_time, 2)} seconds') diff --git a/apps/settings/utils/ping.py b/apps/settings/utils/ping.py index 7b4f0a2a4..cb9e5e544 100644 --- a/apps/settings/utils/ping.py +++ b/apps/settings/utils/ping.py @@ -128,30 +128,37 @@ def ping(dest_addr, timeout, psize, flag=0): return delay -def verbose_ping(dest_addr, timeout=2, count=5, psize=64, display=None): +def verbose_ping(dest_ip, timeout=2, count=5, psize=64, display=None): """ Send `count' ping with `psize' size to `dest_addr' with the given `timeout' and display the result. """ - ip = lookup_domain(dest_addr) + ip = lookup_domain(dest_ip) if not ip: return if display is None: display = print - display("PING %s (%s): 56 data bytes" % (dest_addr, ip)) + error_count = 0 + display("PING %s (%s): 56 data bytes" % (dest_ip, ip)) for i in range(count): try: - delay = ping(dest_addr, timeout, psize) + delay = ping(dest_ip, timeout, psize) except socket.gaierror as e: display("failed. (socket error: '%s')" % str(e)) + error_count += 1 break if delay is None: display("Request timeout for icmp_seq %i" % i) + error_count += 1 else: - delay = delay * 1000 + delay *= 1000 display("64 bytes from %s: icmp_seq=0 ttl=115 time=%.3f ms" % (ip, delay)) time.sleep(1) + display(f'--- {dest_ip} ping statistics ---') + display(f'{count} packets transmitted, ' + f'{count - error_count} packets received, ' + f'{(error_count / count) * 100}% packet loss') print() diff --git a/apps/settings/utils/telnet.py b/apps/settings/utils/telnet.py index 22a1cf619..9cb0a2c0b 100644 --- a/apps/settings/utils/telnet.py +++ b/apps/settings/utils/telnet.py @@ -18,21 +18,21 @@ def telnet(dest_addr, port_number=23, timeout=10): return True, output.decode('utf-8', 'ignore') -def verbose_telnet(dest_addr, port_number=23, timeout=10, display=None): +def verbose_telnet(dest_ip, dest_port=23, timeout=10, display=None): if display is None: display = print - ip = lookup_domain(dest_addr) + ip = lookup_domain(dest_ip) if not ip: return - msg = 'Trying %s (%s:%s)' % (dest_addr, ip, port_number) + msg = 'Trying %s (%s:%s)' % (dest_ip, ip, dest_port) display(msg) try: - is_connective, resp = telnet(dest_addr, port_number, timeout) + is_connective, resp = telnet(dest_ip, dest_port, timeout) if is_connective: template = 'Connected to {0} {1}.\r\n{2}Connection closed by foreign host.' else: template = 'telnet: connect to {0} {1} {2}\r\ntelnet: Unable to connect to remote host' - msg = template.format(dest_addr, port_number, resp) + msg = template.format(dest_ip, dest_port, resp) except Exception as e: msg = 'Error: %s' % e display(msg) diff --git a/apps/settings/ws.py b/apps/settings/ws.py index 9e248536f..f5752e2a1 100644 --- a/apps/settings/ws.py +++ b/apps/settings/ws.py @@ -7,7 +7,7 @@ from channels.generic.websocket import JsonWebsocketConsumer from common.db.utils import close_old_connections from common.utils import get_logger -from .utils import verbose_ping, verbose_telnet +from .utils import verbose_ping, verbose_telnet, verbose_nmap logger = get_logger(__name__) @@ -24,27 +24,28 @@ class ToolsWebsocket(JsonWebsocketConsumer): def send_msg(self, msg): self.send_json({'msg': msg + '\r\n'}) - def imitate_ping(self, dest_addr, timeout=3, count=5, psize=64): + def imitate_ping(self, dest_ip, timeout=3, count=5, psize=64): """ - Send `count' ping with `psize' size to `dest_addr' with + Send `count' ping with `psize' size to `dest_ip' with the given `timeout' and display the result. """ - logger.info('receive request ping {}'.format(dest_addr)) - verbose_ping(dest_addr, timeout, count, psize, display=self.send_msg) + logger.info('receive request ping {}'.format(dest_ip)) + verbose_ping(dest_ip, timeout, count, psize, display=self.send_msg) - def imitate_telnet(self, dest_addr, port_num=23, timeout=10): - logger.info('receive request telnet {}'.format(dest_addr)) - verbose_telnet(dest_addr, port_num, timeout, display=self.send_msg) + def imitate_telnet(self, dest_ip, dest_port=23, timeout=10): + logger.info('receive request telnet {}'.format(dest_ip)) + verbose_telnet(dest_ip, dest_port, timeout, display=self.send_msg) + + def imitate_nmap(self, dest_ip, dest_port=None, timeout=None): + logger.info('receive request nmap {}'.format(dest_ip)) + verbose_nmap(dest_ip, dest_port, timeout, display=self.send_msg) def receive(self, text_data=None, bytes_data=None, **kwargs): data = json.loads(text_data) - tool_type = data.get('tool_type', 'Ping') - dest_addr = data.get('dest_addr') - if tool_type == 'Ping': - self.imitate_ping(dest_addr) - else: - port_num = data.get('port_num') - self.imitate_telnet(dest_addr, port_num) + tool_type = data.pop('tool_type', 'Ping') + + tool_func = getattr(self, f'imitate_{tool_type.lower()}') + tool_func(**data) self.close() def disconnect(self, code):