diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7af6f2b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python Debugger: Current File", + "type": "debugpy", + "request": "launch", + "program": "./main.py", + "console": "integratedTerminal" + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 6e485a0..113f700 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Astatin3 +Copyright (c) 2023 Astatin3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4f2ba91..1ec5b55 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ -# Auto-Shodanner - A tool to scrape Shodan +# Polyboard + +### A very dynamic dashboard, with many features + +Polyboard is basically just a fancy dashboard template, but with a very extensible module system, and security features! + +[Read the wiki!](https://github.com/Astatin3/Modulator/wiki) + +![alt text](https://raw.githubusercontent.com/Astatin3/images/main/Modulator-1.png) diff --git a/data/creds.json b/data/creds.json new file mode 100644 index 0000000..79f73b9 --- /dev/null +++ b/data/creds.json @@ -0,0 +1,23 @@ +[ + { + "created": 1713223622034, + "id": "C9Be3ffc8bD4AAd5", + "passwordUpdated": 1713223622034, + "permGroups": [ + "Users" + ], + "sha256passwordhash": "3F76939A78A3A229B680159FE77E396034407EB5D6C3C6D478C83C9C89022085", + "username": "User" + }, + { + "created": 1713223622034, + "id": "067A14d766fDa6D8", + "passwordUpdated": 1713223622034, + "permGroups": [ + "Users", + "Admins" + ], + "sha256passwordhash": "FE851A5602AC9769E24B4D09B24298877AB0C0BA4D9AE1DEBF1CD11935A46299", + "username": "Admin" + } +] \ No newline at end of file diff --git a/data/ssl.crt b/data/ssl.crt new file mode 100644 index 0000000..3549987 --- /dev/null +++ b/data/ssl.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDWzCCAkMCFERTJjsw8qFSMc10A9A3SmqMHowpMA0GCSqGSIb3DQEBCwUAMGkx +CzAJBgNVBAYTAklEMSUwIwYDVQQKDBxMb2NhbCBEaWdpdGFsIENlcnQgQXV0aG9y +aXR5MRUwEwYDVQQLDAx3d3cuY2EubG9jYWwxHDAaBgNVBAMME1NlbGYtU2lnbmVk +IFJvb3QgQ0EwIBcNMjQwMzIwMTkxOTMzWhgPMjEyNDAyMjUxOTE5MzNaMGkxCzAJ +BgNVBAYTAklEMSUwIwYDVQQKDBxMb2NhbCBEaWdpdGFsIENlcnQgQXV0aG9yaXR5 +MRUwEwYDVQQLDAx3d3cuY2EubG9jYWwxHDAaBgNVBAMME1NlbGYtU2lnbmVkIFJv +b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxc2AUbcE9UDvQ +ba+WV3YY2TDrgodsv0FTAbCKQBgfqYcZroXVrYoyK9/8zOQ8k6lQT7fUOCAmjUCQ +77BKwFXnAZUeJc5ctnKYS9KZIW3JRA3Nc14tTXjRi+Xe1FMLTDWBE/1bYP4To56Y +e1+xq0I9Ojv0oUS4JYjFO9YWM1bZCBZsbUzv93zyY4v3P/Bjk02vGfsSqGZ5WmAc +q7Kz2luYfgmQ0X8h/ZYidGw9azGQEEu53VqGT1xlw4XHF/FxsUg0918JH1gzrBc4 +yl1bWxV1TN8Ph5xLoeF8s0wTZgGLErn54ZwVvSZ8cqUTROv251PqXMu54z23HwxP +YjcddRNhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFAI/9cxxZqq95xs2WmnyUov +ks9lCJloON3g40cL4As+fTiTwsXA8ZEOiGex6OQ+A3/5hChpP/A95i43bba2QbK7 +7eoQhMp7SEj5ybOhIhW0gUsyT449Pp4nl/tnylzyXKTnumPfpl3zmL1nlUadxNDV +7s4roFlNEfLM5ybEfYvjueo5lZecS/U07Ttqs7FM+YAtwx48b/a+wXGCSBbXlImb +Ar+Yy2jEEY+XARdzqns1HHJLaoi3y4K47pozXTfAHpE2GzBVLhWOxqpRGrxzO8X7 +sa4cVhB15/brMFwBvnhUHi+fogrbVCEEvnzqj+aKQ7ISWATGCrirtV20Uw7jktA= +-----END CERTIFICATE----- diff --git a/data/ssl.key b/data/ssl.key new file mode 100644 index 0000000..f0c255e --- /dev/null +++ b/data/ssl.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxc2AUbcE9UDvQ +ba+WV3YY2TDrgodsv0FTAbCKQBgfqYcZroXVrYoyK9/8zOQ8k6lQT7fUOCAmjUCQ +77BKwFXnAZUeJc5ctnKYS9KZIW3JRA3Nc14tTXjRi+Xe1FMLTDWBE/1bYP4To56Y +e1+xq0I9Ojv0oUS4JYjFO9YWM1bZCBZsbUzv93zyY4v3P/Bjk02vGfsSqGZ5WmAc +q7Kz2luYfgmQ0X8h/ZYidGw9azGQEEu53VqGT1xlw4XHF/FxsUg0918JH1gzrBc4 +yl1bWxV1TN8Ph5xLoeF8s0wTZgGLErn54ZwVvSZ8cqUTROv251PqXMu54z23HwxP +YjcddRNhAgMBAAECggEAJWEFUyhPdUfqYZxJSWUBBnjxuhpTxo9/Bior8uNPcZP3 +VmyR2pHks27Ujt7mEPCV0MoKEhUEiLpmaGFlBEoBjlih/ai/EH6KIxSNtx34j1Xb +hnSP2QEhuxo6ALc/CvkxqA/65X3H54Emzj3myXkmydjzejNiU1vstIdHMIP9/ldA ++ghiEv8MUPjheGa1M++Tyc0z0VP45wsaPKa10soku89IvgRs0sA+N6188iWJPeac +yvi9nYXDiSVDF/2bI20cXSVKapt15BUXHzWBjEFS99r+nD4H7IQfUU3+jMsVuwmz +9ZlIIAVG82d02pLS94FsK5fa54p0PEg4tidU4kGB8QKBgQDZhTe78VDh3mzeA9n5 +Yxd/1XeGBBhDkxILeIwk1YlQ7Ii6cM6jIOZDt5ci3/5jBUvqPGQc7eq0iQRflANj +UOwe70fkRpNbkcZRDH/8/AgyhwjJDuyNUVzXegCoSa1zlNgoQ/T1qyLFL9ep1ApD +STn2lAIbPpPdTYde25K4e8+9pQKBgQDQ14hrROlp/dsA7hKnaulnf62EPA4s9/ky +uos/u7bNLj+DD6u5XF0MSRUqqbAFiRUSS+CFE9omQonZbDOO8X8y1g6U0PBy858P +4T/1kDVJPBPJ6XNtSEPRewFq3J/AYXXyd7ktUYYq6mOHsuAitd3Ou87bvM0EMrGa +KN+XFIAKDQKBgQCw/B/ZIAQ7g4r5KhEVRXc9YCccOAC2Gtg31SHSZpyP56VobZj0 +SjSRLLQggDivwQN/1xtuHnc15bZVPk1zZch5cx//sRz9CKNgFtectETHN/ACB86Z +PXZZLL+ULj6fKKCoQoLx0Qk9gCvt1sVy8gXjh5IfMM+G5SocGHRM/xCMsQKBgBom +QrCbylY5MQjmxnMsEdmhxQo/ss6ypgNSFEmAqZz7Y3x3o9rr08LMC9hxstaemYLW ++V/wYmpT/oq436PQXUryPpUnrFPYaVxFqgqHagSnfxrTHl7ao5NBQaYtxdsC2Q/p +Kcig4pnRC3FcVnBCMWnHXllpRIp4BoD1CToTmQypAoGBALg2BbzMgg4pCIbC/cVw +H2Con5VRYc+uyOvoxiiXNJmhfkg9i5bYXtDFMFBAM0N2NDABIRgSZboSKWg+Bne9 +CBFkN67+Q5mlNxBmvfZVEqZypxyReVrlijpNjG3tBayB2zonH1PLyl/ciKNGhFK8 +/abtipqo94ece5zPnBXVHywv +-----END PRIVATE KEY----- diff --git a/exclude.conf b/exclude.conf new file mode 100644 index 0000000..767b494 --- /dev/null +++ b/exclude.conf @@ -0,0 +1,1450 @@ +# https://github.com/arschlochnop/masscan-exclude/blob/master/exclude.conf + +# http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml + +# http://tools.ietf.org/html/rfc5735 +# "This" network +0.0.0.0/8 +# Private networks +10.0.0.0/8 +# Carrier-grade NAT - RFC 6598 +100.64.0.0/10 +# Host loopback +127.0.0.0/8 +# Link local +169.254.0.0/16 +# Private networks +172.16.0.0/12 +# IETF Protocol Assignments +192.0.0.0/24 +# DS-Lite +192.0.0.0/29 +# NAT64 +192.0.0.170/32 +# DNS64 +192.0.0.171/32 +# Documentation (TEST-NET-1) +192.0.2.0/24 +# 6to4 Relay Anycast +192.88.99.0/24 +# Private networks +192.168.0.0/16 +# Benchmarking +198.18.0.0/15 +# Documentation (TEST-NET-2) +198.51.100.0/24 +# Documentation (TEST-NET-3) +203.0.113.0/24 +# Reserved +240.0.0.0/4 +# Limited Broadcast +255.255.255.255/32 +#Caltech IPs reported JC on 20150612 +131.215.0.0/16 +134.4.0.0/16 +192.12.19.0/24 +192.31.43.0/24 +192.41.208.0/24 +192.43.243.0/24 +192.54.249.0/24 + +#Received: from elbmasnwh002.us-ct-eb01.gdeb.com ([153.11.13.41] +# helo=ebsmtp.gdeb.com) by mx1.gd-ms.com with esmtp (Exim 4.76) (envelope-from +# ) id 1VS55c-0004qL-0F for support@erratasec.com; Fri, 04 +# Oct 2013 09:06:40 -0400 +#To: +#CC: +#Subject: Scanning and Probing our network +#From: Robert Mandes +#Date: Fri, 4 Oct 2013 09:06:36 -0400 +# +#Stop scanning and probing our network, 153.11.0.0/16. We are a defense +#contractor and report to Federal law enforcement authorities when scans +#and probes are directed at our network. I assume you don't want to be +#part of that report. Please permanently remove our network range from +#your current and future research. +# +#Thank you +# +#Robert Mandes +#Information Security Officer +#General Dynamics +#Electric Boat +# +#C 860-625-0605 +#P 860-433-1553 + +153.11.0.0/16 + +#Date: Mon, 7 Oct 2013 17:25:41 -0700 +#Subject: Re: please stop the attack to our router +#From: Di Li +# +#Make sure you stop the scan immediately, that's not OK for any company or +#organization scan our network at all. +# +#If you fail to do that we will block whole traffic from ASN 10439, and we +#will fail a police report after that. +# +#Let me know when you stop, since we still receive the attack from you, and +#by the way your scan are not going anywhere, it's was dropped from our edge +#since the first 5 scan +# +#Oct 7 17:17:32:I:SNMP: Auth. failure, intruder IP: 209.126.230.72 +#... +#Oct 7 16:55:27:I:SNMP: Auth. failure, intruder IP: 209.126.230.72 +# +#Di + +4.53.201.0/24 +5.152.179.0/24 +8.12.162.0-8.12.164.255 +8.14.84.0/22 +8.14.145.0-8.14.147.255 +8.17.250.0-8.17.252.255 +23.27.0.0/16 +23.231.128.0/17 +37.72.172.0/23 +38.72.200.0/22 +50.93.192.0-50.93.197.255 +50.115.128.0/20 +50.117.0.0/17 +50.118.128.0/17 +63.141.222.0/24 +64.62.253.0/24 +64.92.96.0/19 +64.145.79.0/24 +64.145.82.0/23 +64.158.146.0/23 +65.49.24.0/24 +65.49.93.0/24 +65.162.192.0/22 +66.79.160.0/19 +66.160.191.0/24 +68.68.96.0/20 +69.46.64.0/19 +69.176.80.0/20 +72.13.80.0/20 +72.52.76.0/24 +74.82.43.0/24 +74.82.160.0/19 +74.114.88.0/22 +74.115.0.0/24 +74.115.2.0/24 +74.115.4.0/24 +74.122.100.0/22 +75.127.0.0/24 +103.251.91.0/24 +108.171.32.0/24 +108.171.42.0/24 +108.171.52.0/24 +108.171.62.0/24 +118.193.78.0/23 +130.93.16.0/23 +136.0.0.0/16 +142.111.0.0/16 +142.252.0.0/16 +146.82.55.93 +149.54.136.0/21 +149.54.152.0/21 +166.88.0.0/16 +172.252.0.0/16 +173.245.64.0/19 +173.245.194.0/23 +173.245.220.0/22 +173.252.192.0/18 +178.18.16.0/22 +178.18.26.0-178.18.29.255 +183.182.22.0/24 +192.92.114.0/24 +192.155.160.0/19 +192.177.0.0/16 +192.186.0.0/18 +192.249.64.0/20 +192.250.240.0/20 +194.110.214.0/24 +198.12.120.0-198.12.122.255 +198.144.240.0/20 +199.33.120.0/24 +199.33.124.0/22 +199.48.147.0/24 +199.68.196.0/22 +199.127.240.0/21 +199.187.168.0/22 +199.188.238.0/23 +199.255.208.0/24 +203.12.6.0/24 +204.13.64.0/21 +204.16.192.0/21 +204.19.238.0/24 +204.74.208.0/20 +205.159.189.0/24 +205.164.0.0/18 +205.209.128.0/18 +206.108.52.0/23 +206.165.4.0/24 +208.77.40.0/21 +208.80.4.0/22 +208.123.223.0/24 +209.51.185.0/24 +209.54.48.0/20 +209.107.192.0/23 +209.107.210.0/24 +209.107.212.0/24 +211.156.110.0/23 +216.83.33.0-216.83.49.255 +216.83.51.0-216.83.63.255 +216.151.183.0/24 +216.151.190.0/23 +216.172.128.0/19 +216.185.36.0/24 +216.218.233.0/24 +216.224.112.0/20 + +#Received: from [194.77.40.242] (HELO samba.agouros.de) +# for abuse@erratasec.com; Sat, 12 Oct 2013 09:55:35 -0500 +#Received: from rumba.agouros.de (rumba-internal [192.168.8.1]) by +# samba.agouros.de (Postfix) with ESMTPS id 9055FBAD1D for +# ; Sat, 12 Oct 2013 16:55:32 +0200 (CEST) +#Received: from rumba.agouros.de (localhost [127.0.0.1]) by rumba.agouros.de +# (Postfix) with ESMTP id 7B5DD206099 for ; Sat, 12 Oct +# 2013 16:55:32 +0200 (CEST) +#Received: from localhost.localdomain (localhost [127.0.0.1]) by +# rumba.agouros.de (Postfix) with ESMTP id 5FBC420601D for +# ; Sat, 12 Oct 2013 16:55:32 +0200 (CEST) +#To: +#Subject: Loginattempts from Your net +#Message-ID: <20131012145532.5FBC420601D@rumba.agouros.de> +#Date: Sat, 12 Oct 2013 16:55:32 +0200 +#From: +# +#The address 209.126.230.72 from Your network tried to log in to +#our network using Port 22 (1)/tcp. Below You will find a listing of the dates and +#times the incidents occured as well as the attacked IP-Addresses. +#This is a matter of concern for us and continued tries might result in +#legal action. If the machine was victim to a hack take it offline, repair +#the damage and use better protection next time. +#The times included are in Central European (Summer) Time. +#Date Sourceip port destips +# +#07.10.2013 22:34:40 CEST 209.126.230.72 22 194.77.40.242 (1) +#08.10.2013 01:44:15 CEST 209.126.230.72 22 194.77.40.246 (1) +# +#Regards, +#Konstantin Agouros + +194.77.40.242 +194.77.40.246 + +#Received: from [165.160.9.58] (HELO mx2.cscinfo.com) +#X-Virus-Scanned: amavisd-new at cscinfo.com +#Received: from mx2.cscinfo.com ([127.0.0.1]) by localhost +# (plmail02.wil.csc.local [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id +# GGQ7EiQaK2P0 for ; Wed, 30 Oct 2013 09:26:00 -0400 +# (EDT) +#Received: from casarray.cscinfo.com (pwmailch02.cscinfo.com [172.20.53.94]) by +# mx2.cscinfo.com (Postfix) with ESMTPS id 4BA5E58170 for +# ; Wed, 30 Oct 2013 09:26:00 -0400 (EDT) +#Received: from PWMAILM02.cscinfo.com ([169.254.7.52]) by +# PWMAILCH02.cscinfo.com ([172.20.53.94]) with mapi id 14.02.0247.003; Wed, 30 +# Oct 2013 09:26:00 -0400 +#From: "Derksen, Bill" +#Subject: Unauthorized Scanning +#Date: Wed, 30 Oct 2013 13:25:59 +0000 +#Message-ID: <1F80316A0C861F40A9A88F18465F138E01EF885F@PWMAILM02.cscinfo.com> +#x-originating-ip: [172.31.252.72] +# +#We have detected unauthorized activity from your systems on our public netw= +#ork. Please suspend scanning of our networks immediately. +# +#Our network block is 165.160/16 +# +#Further scanning will result in reports of unauthorized activity being file= +#d with law enforcement agencies. +# +#Corporation Service Company +# +# +# +#________________________________ +# +#NOTICE: This e-mail and any attachments is intended only for use by the add= +#ressee(s) named herein and may contain legally privileged, proprietary or c= +#onfidential information. If you are not the intended recipient of this e-ma= +#il, you are hereby notified that any dissemination, distribution or copying= +# of this email, and any attachments thereto, is strictly prohibited. If you= +# receive this email in error please immediately notify me via reply email o= +#r at (800) 927-9800 and permanently delete the original copy and any copy o= +#f any e-mail, and any printout. + +165.160.0.0/16 + +## These were grabbed from http://www.waraxe.us/ftopict-346.html +## They need to be cleaned up, reformatted, and verified +# +# Army Information Systems Center +6.0.0.0/8 +# US Defense Information Systems Agency +21.0.0.0/8 +# Defense Information Systems Agency +22.0.0.0/8 +# Comcast Cable Communications, Inc. I believe this is for residential customers. +24.8.0.0/15 +24.0.0.0/12 +# Roadrunner, USA. Time Warner Cable Internet LLC +24.88.0.0/16 +# James Cable, LLC +24.112.0.0/17 +# Hostmaster Liberty Global +24.132.0.0/23 +24.132.2.255/24 +# Optimum Online +24.188.0.0/14 +## Defense Information Systems Agency +#26.0.0.0 +## Defense Information Systems Agency +#29.0.0.0 +## Defense Information Systems Agency +#30.0.0.0 +## Joint Tactical Command +#49.0.0.0 +## Joint Tactical Command +#50.0.0.0 +## DaimlerChrysler AG +#53.0.0.0 +## Army National Guard Bureau +#55.0.0.0 +#62.0.0.1 - 62.30.255.255 +## Commundo +#62.52.0.0 +## Debitel +#62.52.0.0 +## lahm (9 Online French) +#62.62.0.0 +## AddCOM +#62.69.140.0 +## I-net +#62.96.0.0 +## Freeserve, UK +#62.137.0.0 +## bis 62.255.0.0 T-Online +#62.150.0.0 +## Erotik-Welt +#62.156.0.0 +## 1 & 1 +#62.157.0.0 +## [T-Online, Germany +#62.158. +## Planet Intercom +#62.180.0.0 +## Adelphia, USA +#64.9. 0 +## Darwin Networks Community, USA +#64.44. 0 +## (FBI's honeypot) +#64.224.0 +## (FBI's honeypot) +#64.225.0 +## (FBI's honeypot) +#64.226.0 +## lahm +#80.0.0.0 +## lahm +#81.0.0.0 +## University of Delaware +#128.4.0.0 +## University of Maryland +#128.8.0.0 +## Purdue University +#128.10.0.0 +## Yahoo, Inc. +#128.11.22.0 - 128.11.22.255 +## Yahoo, Inc. +#128.11.23.0 - 128.11.23.255 +## Yahoo, Inc. (NETBLK-FOUR11DS559-68-19) +#128.11.68.0 - 128.11.68.255 +## Yahoo, Inc. (NETBLK-FOUR11DS216-69-19) +#128.11.69.0 - 128.11.69.255 +## Yahoo, Inc. (NETBLK-FOUR11DS698-70-19) +#128.11.70.0 - 128.11.70.255 +## Yahoo, Inc. (NETBLK-FOUR11DS699-71-20) +#128.11.71.0 - 128.11.71.255 +## FortuneCity Ltd. +#128.11.96.32 - 128.11.96.63 +## Yale University (NET-YALE-NET) +#128.36.0.0 +## Army Yuma Proving Ground +#128.37.0.0 +## Naval Surface Warfare Center (NET-NSWC-NET) +#128.38.0.0 +## Norwegian Telecommunications Administration (NET-NTANET) +#128.39.0.0 +## University College London (NET-UCL-ETHERNET) +#128.40.0.0 +## University College London (NET-CL-CS-SERVICE) +#128.41.0.0 +## Rice University (NET-RICE-NET) +#128.42.0.0 +## Defence Researc +#128.43.0.0 +## Army Communications Electronics Command (NET-TACTNET) +#128.47.0.0 +## Department of Defense (NET-COINS) +#128.50.0.0 +## Department of Defense (NET-COINSTNET) +#128.51.0.0 +## U.S. Naval Academy (NET-USNA-NET) +#128.56.0.0 +## Army Ballistics Research Laboratory (NET-BRL-SUBNET) +#128.63.0.0 +## Army Communications Electronics Command (CECOM) (NET-CECOMNET) +#128.80.0.0 +## Defence Evaluation and Research Agency (NET-DERA-UK) +#128.98.0.0 +## Northrop Grumman Corporation - Automation Sciences Laboratory (NET-NORTHROP-NET) +#128.99.0.0 +## University of Toronto Computing and Communications (NET-TORONTO) +#128.100.0.0 +## Unversity of Minnesota (NET-UMN-NET) +#128.101.0.0 +## NASA Ames Research Center +#128.102.0.0 +## Harvard University (NET-HARVA +#128.103.0.0 +## NASA Headquarters +#128.149.0.0 +## National Science Foundation (NET-NSF-LAN) +#128.150.0.0 +## University of Rochester (NET-UR-NET) +#128.151.0.0 +## Hughes Electronics (NET- HAC-ENET) +#128.152.0.0 +## Clarkson University (NET-CLARKSON) +#128.153.0.0 +## NASA Wallops Flight Facility (NET-WFF-NET) +#128.154.0.0 +## NASA Langley Research Center (NET-LARC-NET) +#128.155.0.0 +## NASA Lewis Network Control Center (NET- LERC) +#128.156.0.0 +## NASA Johnson Space Center (NET-JSC-NET) +#128.157.0.0 +## NASA Ames Research Center (NET-MSFC-NET) +#128.158.0.0 +## NASA Ames Research Center (NET-KSC-NET) +#128.159.0.0 +## Naval Research Laboratory (NET- SSCNET) +#128.160.0.0 +## NASA Ames Research Center (NET-NSN-NET) +#128.161.0.0 +## NASA Goddard Space Flight Center (NET-GSFC) +#128.183.0.0 +## Army Belvoir Reasearch and Development Center +#128.190.0.0 +## MacDill Air Force Base (NET-CC-PRNET) +#128.216.0.0 +## NASA Kennedy Space Center (NET-NASA-KSC-OIS) +#128.217.0.0 +## U.S. Air Force Academy (NET-USAFA-NET +#128.236.0.0 +## University of Leeds (NET-LEEDS) +#129.11.0.0 +## University of Kent at Canterbury (NET-UKC) +#129.12.0.0 +#129.13.0.0 # University of Karlsruhe +## Wright-Patterson Air Force Base +#129.48.0.0 +## NASA Marshall Space Flight Center (NET-PSCN) +#129.50.0.0 +## Patrick Air Force Base (NET-NS) PAFB) +#129.51.0.0 +## Wright-Patterson Air Force Base +#129.52.0.0 +## Vandenberg Air Force Base +#129.54.0.0 +## University of Stuttgart (NET-NI-STG-NET) +#129.69.0.0 +## University of Bielefeld +#129.70.0.0 +## Siemens +#129.73.0.0 +## Air Force Institute of Technology +#129.92.0.0 +## NASA Ames Research Center +#129.99.0.0 +## Army Armament Research Development and Engineering Center +#129.139.0.0 +## NASA/Johnson Space Center (NET-NASA-JSCSSE) +#129.163.0.0 +## NASA IVV (NET-E) NASA-IVV) +#129.164.0.0 +## NASA Goddard Space Flight Center (NET-NASA-GSFCSSE) +#129.165.0.0 +## NASA - John F. Kennedy Space Center (NET-NASA-JFKSSE) +#129.166.0.0 +## NASA Marshall Space Flight Center (NET-NASA-MSFCSSE) +#129.167.0.0 +## NASA Lewis Research Center (NET-NASA-LRCSSE) +#129.168.0.0 +## Leibniz-Rechenzentrum der Bayerischen Akademie +#129.187.0.0 +## Air Force Flight Test Center +#129.198.0.0 +## Army Ballistics Research Laboratory +#129.209.0.0 +## University of Dortmund +#129.217.0.0 +## U.S. Army Corps of Engineers +#129.229.0.0 +## Fraunhofer Institut f?r Produktionstechnik u. Automatisierung +#129.233.0.0 +## United States Air Force Academy +#129.251.0.0 +# +## NASA Johnson Space Center +#130.40.0.0 +## University of Hannover +#130.75.0.0 +## Netherlands Energy Research Foundation ECN +#130.112.0.0 +## Army Aberdeen Proving Ground Installation Support Activity (NET-APGNET) +#130.114.0.0 +## Erasmus University Rotterdam +#130.115.0.0 +## Massey University +#130.123.0.0 +## Freie Universitaet Berlin +#130.133.0.0 +# +## U.S.Army Corps of Engineers +#130.165.0.0 +## NASA Headquarters +#130.167.0.0 +## Max-Planck-Institut fur Plasmaphysik +#130.183.0.0 +## UUNet +#130.211.0.0 +## Gothenburg University (NET-GU-NET) +#130.241.0.0 +## Boeing Military Aircraft +#130.247.0.0 +## Mather Air Force Base +#131.3.0.0 - 131.3.255.255 +## Langley Air Force Base +#131.6.0.0 +## Barksdale Air Force Base +#131.10.0.0 +## Sheppard Air Force Base +#131.17.0.0 +## Hahn Air Base (NET-HAHNNET) +#131.21.0.0 +## Keesler Air Force Base +#131.22.0.0 +## Patrick Air Force Base +#131.25.0.0 +## Fairchild Air Force Base +#131.35.0.0 +## Yokota Air Base +#131.36.0.0 +## Elmendorf Air Force Base +#131.37.0.0 +## Hickam Air Force Base +#131.38.0.0 +## Bergstrom Air Force Base +#131.40.0.0 +## Randolph Air Force Base +#131.44.0.0 +## Andersen Air Force Base +#131.47.0.0 +## Davis-Monthan Air Force Base +#131.50.0.0 +## Air Force Concentrator Network +#131.54.0.0 +## McConnell Air Force Base +#131.61.0.0 +## Norton Air Force Base +#131.62.0.0 +## Army Information Systems Command - Aberdeen +#131.92.0.0 +## NASA/Michoud Assembly Facility +#131.110.0.0 +## United States Naval Academy +#131.121.0.0 +## United States Naval Academy +#131.122.0.0 +## [Eindhoven University, Netherlands +#131.155. +## Deutsches Elektronen Synchrotron +#131.169.0.0 +## Nijmegen University, Netherlands +#131.174. 0 +## European Space Operations Center +#131.176.0.0 +## NASA Headquarters +#131.182.0.0 +## University Erlangen Nuremberg +#131.188.0.0 +## UUNet +#131.211.0.0 +## University of Bonn +#131.220.0.0 +## University College, Australian Defense Force Academy +#131.236.0.0 +## University Kaiserslautern +#131.246.0.0 +## [Technological Institute, Israel +#132.68. +## ??? +#132.95.0.0 - 132.108.0.0 +## Universitaet Tuebingen +#134.2.0.0 +## The Pentagon (NET-PENTNET) +#134.11.0.0 +## NASA Ames Research Center +#134.12.0.0 +## University of Fribourg +#134.21.0.0 +## Army Information Systems Command-ATCOM +#134.78.0.0 +## Army Information Systems Command +#134.80.0.0 +## Uni Duisburg +#134.91.0.0 +## Uni K?ln +#134.95.0.0 +## NASA/Johnson Space Center +#134.118.0.0 +## Ruhr-Universitaet Bochum +#134.147.0.0 +## University of Mannheim +#134.155.0.0 +## Army Engineer Waterways Experiment Station +#134.164.0.0 +## Universitaet Giessen +#134.176.0.0 +## U.S. Army Aberdeen Test Center +#134.194.0.0 +## Navy Regional Data Automation Center +#134.229.0.0 +## Navy Regional Data Automation Center +#134.230.0.0 +## U.S. Military Academy +#134.240.0.0 +## Universitaet Kiel +#134.245.0.0 +## European Centre for Medium-Range Weather Forecasts +#136.156.0.0 +## Deutsches Klimarechenzentrum +#136.172.0.0 +## NASA Research Network +#136.178.0.0 +## Universitaet Trier +#136.199.0.0 +## Whiteman Air Force Base +#137.1.0.0 +## George Air Force Base +#137.2.0.0 +## Little Rock Air Force Base +#137.3.0.0 +## Air Force Concentrator Network +#137.5.0.0 +## Air Force Concentrator Network +#137.6.0.0 +## Air Force Concentrator Network +#137.12.0.0 +## Siemens Nixdorf Information Systems +#137.223.0.0 +## Aachen University of Technology +#137.226.0.0 +## Air Force Materiel Command +#137.240.0.0 +## Air Force Logistics Command +#137.242.0.0 +## University of Augsburg (NET-AUX) +#137.250.0.0 +## Fraunhofer-Institut f?r Arbeitswirtschaft und Organisation +#137.251.0.0 +## Air Force Systems Command +#138.13.0.0 +## Army Information Systems Command +#138.27.0.0 +## National Space Development Agency of Japan +#138.30.0.0 +## Mainz +#138.61.0.0 +## NASA Headquarters +#138.76.0.0 +## NASA Information and Electronic Systems Laboratory +#138.115.0.0 +## Navy Computers and Telecommunications Station +#138.136.0.0 - 138.136.255.255 +## Navy Regional Data Automation Center +#138.137.0.0 +## Marine Corps Air Station +#138.139.0.0 +## Navy Regional Data Automation Center +#138.140.0.0 +## Navy Regional Data Automation Center +#138.141.0.0 +## Navy Regional Data Automation Center +#138.142.0.0 +## Navy Regional Data Automation Center +#138.143.0.0 +## Marine Corps Central Design & Prog. Activity +#138.156.0.0 +## Marine Corps Central Design & Prog. Activity +#138.158.0.0 +## Marine Corps Central Design & Prog. Activity +#138.168.0.0 +## NASA/Yellow Creek +#138.193.0.0 +## University of Dortmund +#139.2.0.0 +## EUnet Deutschland GmbH +#139.4.0.0 +## University of Dortmund +#139.5.0.0 +## Fachhochschule K?ln +#139.6.0.0 +## Mannesmann Mobilfunk GmbH +#139.7.0.0 +## S & P Media GmbH +#139.8.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## University of Dortmund (NETBLK-UNIDO11) +#139.10.0.0 - 139.16.0.0 +## Universit?t der Bundeswehr Hamburg +#139.11.0.0 +## Fachhochschule Wilhelmshaven +#139.13.0.0 +## Universit?t Koblenz-Landau +#139.14.0.0 +## Robert Bosch GmbH +#139.15.0.0 - 139.15.255.255 +## Geo-Forschungs-Zentrum Potsdam +#139.17.0.0 +## Universit?t Leipzig (NET-UNILE-LAN) +#139.18.0.0 +## Max-Planck-Institut f?r Informatik +#139.19.0.0 +## European Regional Internet Registry/RIPE NCC +#139.20.0.0 - 139.20.255.255 +## European Regional Internet Registry/RIPE NCC +#139.20.0.0 - 139.29.255.255 +## Siemens Business Services +#139.21.0.0 +## Siemens Business Services +#139.22.0.0 +## Siemens Business Services +#139.23.0.0 +## Siemens Business Services +#139.25.0.0 +## Thyssen Stahl AG +#139.27.0.0 - 139.27.255.255 +## Bremer Vulkan AG +#139.28.0.0 +## University of Rostock +#139.30.0.0 +## Alfred Wegener Institute for Polar and Marine Research +#139.75.0.0 +## NASA Lewis Research Center +#139.88.0.0 +## Front end portal of a security network filtering hundreds of client subscription IPs. If you find a vulnerable pub, IIS, SQL, or 0nix -- LEAVE IT ALONE +#139.142.0.0 +## Army Information Systems Command +#139.161.0.0 +## NASA/Johnson Space Center +#139.169.0.0 +## NASA Ames Research Center +#139.229.0.0 +## HQ US Army Medical Research and Development Command +#140.139.0.0 +## Marine Biological Laboratory +#140.239.81.184 - 140.239.81.191 +## Fast nur Deutsche Firmen +#141.0.0.0 - 141.255.255.255 +## United States Army Information Systems Command (NET-CRLSLBKS-NET) +#144.99.0.0 +## Army Information Systems Command (NET-NCMBRLNDNET) +#144.109.0.0 +## Headquarters, Third United States Army (NET-AC2IS) +#144.143.0.0 +## Headquarters, Third United States Army (NET-AC2IS1) +#144.144.0.0 +## Commander, Army Information Systems Center (NET-FTIRWIN1 ) +#144.146.0.0 +## Commander, Army Information Systems Center (NET-FTIRWIN2 ) +#144.147.0.0 +## United States Army Information Services Command-Campbell (NET-CAMPBELL-GW1) +#144.192.0.0 +## Sprintlink, USA +#144.232. 0 +## Defense Intelligence Agency (NET-DODIIS1) +#144.233.0.0 +## Defense Intelligence Agency (NET-DODIIS2) +#144.234.0.0 +## Defense Intelligence Agency (NET-DODIIS3) +#144.235.0.0 +## Defense Intelligence Agency (NET-DODIIS4) +#144.236.0.0 +## Defense Intelligence Agency (NET-DODIIS5) +#144.237.0.0 +## Defense Intelligence Agency (NET-DODIIS6) +#144.238.0.0 +## Defense Intelligence Agency (NET-DODIIS7) +#144.239.0.0 +## Defense Intelligence Agency (NET-DODIIS +#144.240.0.0 +## Defense Intelligence Agency (NET-DODIIS9) +#144.241.0.0 +## Defense Intelligence Agency (NET-DODIIS10) +#144.242.0.0 +## SUPSHIP, Groton, U.S.N. (NET-SOSGNET) +#144.247.0.0 +## Air Products and Chemicals Incorporated (NET-APCI-NET) +#144.249.0.0 +## U.S. Army LABCOM (NET-LABCOM-ETDL) +#144.252.0.0 +## Niederlande, Frankreich und Deutschland +#145.0.0.0 - 145.255.255.255 +## Army Information Systems Software Center +#147.103.0.0 +## Army Information Systems Software Center +#147.104.0.0 +## Army Information Systems Command +#147.164.0.0 +## Army Information Systems Command +#147.165.0.0 +## Army Information Systems Command +#147.198.0.0 +## Army Information Systems Command +#147.199.0.0 +## Army Information Systems Command +#147.238.0.0 +## US Army Tank-Automotive Command +#147.240.0.0 +## [ContacTel, Czech Republic +#147.251. +## Fachhochschule Aachen (NET-HAC-LAN) +#149.201.0.0 +## Siemens Nixdorf Informationssysteme AG +#149.202.0.0 - 149.202.255.255 +## European Regional Internet Registry/RIPE NCC +#149.202.0.0 - 149.204.255.255 +## Medizinische Akademie Magdeburg +#149.203.0.0 - 149.203.255.255 +## Alcanet International Deutschland GmbH +#149.204.0.0 - 149.204.255.255 +## FH Merseburg +#149.205.0.0 +## Thyssen Stahl AG +#149.206.0.0 - 149.206.255.255 +## European Regional Internet Registry/RIPE NCC +#149.206.0.0 - 149.251.255.255 +## Telefunken Systemtechnik Ulm +#149.207.0.0 +## Thyssen Stahl AG +#149.211.0.0 - 149.211.255.255 +## Siemens-Nixdorf Informationssystemer A/S +#149.212.0.0 +## Degussa AG +#149.216.0.0 - 149.216.255.255 +## Max-Planck-Institut f?r Kernphysik +#149.217.0.0 +## Hirschmann GmbH & Co +#149.218.0.0 +## Westdeutscher Rundfunk Koeln +#149.219.0.0 +## Forschungszentrum Rossendorf +#149.220.0.0 +## Rheinisch Bergische Presse-Data GmbH +#149.221.0.0 +## Fachhochschule Kiel +#149.222.0.0 - 149.222.255.255 +## Quantum Software GmbH +#149.232.0.0 - 149.232.255.255 +## Lahmeyer International GmbH +#149.233.0.0 +## Schering AG Berlin +#149.234.0.0 - 149.234.255.255 +## iXOS Software GmbH +#149.235.0.0 +## Bruker Analytische Messtechnik GmbH +#149.236.0.0 +## Wiechers & Partner Datentechnik GmbH +#149.237.0.0 - 149.237.255.255 +## ZF Friedrichshafen AG +#149.238.0.0 - 149.238.255.255 +## European Regional Internet Registry/RIPE NCC +#149.239.0.0 - 149.239.255.255 +## Carl Schenk AG +#149.240.0.0 - 149.240.255.255 +## Koerber AG +#149.242.0.0 - 149.242.255.255 +## ERNO Raumfahrttechnik +#149.243.0.0 +## Knorr-Bremse AG, M?nchen +#149.244.0.0 +## Siemens AG, OeN NKE A2 +#149.246.0.0 +## TA Triumph-Adler AG +#149.248.0.0 +## Hoechst AG, Frankfurt +#149.250.0.0 +## Electricite de France - Direction Etudes Recherche +#149.251.0.0 +## USDOE, NV Operations Office +#149.252.0.0 +## DIRM/ITD +#149.253.0.0 +## Mercury Personal Communications, Ltd. +#149.254.0.0 - 149.254.255.255 +## Kanadischer Provider +#149.99.130.0- 149.99.150.0 +## Arcor +#145.253.0.0 +## MSN +#149.225.0.0 +## NASA Goodard Space Flight Center +#150.144.0.0 +## Army Information Systems Command +#150.149.0.0 +## Korea Aerospace Research Institute +#150.197.0.0 +## IUnet (NET-IUNET-BNET3) +#151.3.0.0 - 151.3.255.255 +## Compuserv +#152.203.0.0 +## AOL +#152.166.0.0 +## AOL +#152.170.0.0 +## AOL +#152.172.0.0 +## AOL +#152.205.0.0 +## AOL +#152.207.0.0 +## Deutsches Forschungsnetz +#153.92.0.0 +## Bundesanstalt f?r Wasserbau +#153.93.0.0 +## Transtec AG +#153.94.0.0 +## Fraunhofer Institut f?r Informations- und Datenverarbeitung IITB +#153.96.0.0 +## Fraunhofer Institut f?r Informations- und Datenverarbeitung IITB +#153.97.0.0 +## Eurocontrol Karlsruhe +#153.98.0.0 +## European Regional Internet Registry/RIPE NCC +#153.99.0.0 - 153.99.255.255 +## RWE Energie AG +#153.100.0.0 +## European Regional Internet Registry/RIPE NCC +#153.101.0.0 - 153.101.255.255 +## American Forces Information (NET-AFISHQ-NET1) +#155.7.0.0 +## U.S. ArmyFort Gordon (NET-GORDON-NET5) +#155.8.0.0 +## United States Army Information Systems Command (NET-LWOOD-NET2) +#155.9.0.0 +## PEO STAMIS (NET-CEAP2) +#155.74.0.0 +## US Army Corps of Engineers (NET-CEAP3) +#155.75.0.0 +## PEO STAMIS (NET-CEAP4) +#155.76.0.0 +## PEO STAMIS (NET-CEAP5) +#155.77.0.0 +## PEO STAMIS (NET-CEAP6) +#155.78.0.0 +## US Army Corps of Engineers (NET-CEAP7) +#155.79.0.0 +## PEO STAMIS (NET-CEAP +#155.80.0.0 +## PEO STAMIS (NET-CEAP9) +#155.81.0.0 +## PEO STAMIS (NET-CEAP10) +#155.82.0.0 +## US Army Corps of Enginers (NET-CEAP11) +#155.83.0.0 +## PEO STAMIS (NET-CEAP12) +#155.84.0.0 +## PEO STAMIS (NET-CEAP13) +#155.85.0.0 +## US Army Corps of Engineers (NET-CEAP14) +#155.86.0.0 +## PEO STAMIS (NET-CEAP15) +#155.87.0.0 +## PEO STAMIS (NET-CEAP16) +#155.88.0.0 +## Federal Aviation Administration (NET-FAA) +#155.178.0.0 +## USAISC Fort Benning (NET-FTBENNNET3 +#155.213.0.0 +## Director of Information Management (NET-CARSON-TCACC ) +#155.214.0.0 +## USAISC-FT DRUM (NET-DRUM-TCACCIS) +#155.215.0.0 +## TCACCIS Project Management Office (NET-FTDIX-TCACCI) +#155.216.0.0 +## Directorate of Information Management (NET- EUSTIS-EMH1) +#155.217.0.0 +## USAISC (NET-WVA-EMH2) +#155.218.0.0 +## DOIM/USAISC Fort Sill (NET-SILL-TCACCIS) +#155.219.0.0 +## USAISC-DOIM (NET-FTKNOX-NET4) +#155.220.0.0 +## USAISC-Ft Ord (NET-FTORD-NET2) +#155.221.0.0 +## US Courts, Siemens, State of Minnesota +#156.0.0.0 +## U.S. Army TACOM +#158.3.0.0 +## US Army Soldier Support Center +#158.8.0.0 +## US Army Harry Diamond Laboratories +#158.12.0.0 +## Washington State Dept. of State and Ind. +#159.1.0.0 +## Air Canada +#159.206.0.0 +## BMW AG, Berlin production plant +#160.46.0.0 +## BMW AG, Dingolfingen production plant +#160.47.0.0 +## BMW AG, Landshut production plant +#160.48.0.0 +## BMW AG, Munich production plant +#160.49.0.0 +## BMW AG, Munich research and admin. sites +#160.50.0.0 +## BMW AG, Regensburg production plant +#160.51.0.0 +## BMW AG, Steyr production plant +#160.52.0.0 +## US Army Recruiting Command +#160.132.0.0 +## Navy Aviation Supply Office +#163.12.0.0 +## Schering AG (NETBLK-TBLK-SCHERING) +#164.59.0.0 - 164.60.0.0 +## Schering AG (NET-SCHERING2) +#164.60.0.0 +## Kaufhof Holding AG, Koeln +#164.61.0.0 +## Secretary of the Navy (NET-SECNAV-LAN) +#164.224.0.0 +## U.S. Army Intelligence and Security Command +#164.225.0.0 +## Military Sealift Command +#164.231.0.0 +## windows default +#169.0.0.0 +## J. P. Morgan & Co (USA) +#169.120.0.0 +## AOL Deutschland +#171.224.0.0 +## ??? +#171.223.0.0 +## [XS4ALL, Netherlands +#172.16. +## America Online, Inc. +#172.128-180.0.0 +## Netvision +#192.114.63.0 +## [private network +#192.168. +## lahm +#193.0.0.0 +## UUNet +#193.79.178.0 +##Siemens +#193.101.100.0 +##SONNET (Flatrate) (TOT) +#193.122.199.0 +## T-Online +#193.159.0.0 +##Siemens +#193.189.224.0 +##Siemens +#193.189.231.0 +## SONNET (Flatrate) (TOT) +#193.190.215.0 +## Net-Cologne +#194.8.193.0 +## Net-Cologne +#194.8.197.0 +## DE-CIX +#194.31.232.0 +## Netvision +#194.90.0.0 +## MBinternet, Finland +#194.100. 0 +## Microsoft +#194.121.59.0 +##Siemens +#194.174.230.0 +##Microsoft +#194.179.124.0 +##UUNet +#194.203.122.0 +## PLUSNET AG (Schweitz) +#194.230.0.0 +## T-Online +#195.3.0.0 +## Siemens +#195.7.49.0 +## (FBI's honeypot) +#195.10.0 +## Siemens +#195.40.101.0 +## Sonera, Netherlands +#195.66. 0 +## Chello/Capacity, Netherlands +#195.86. 0 +## AOL Deutschland +#195.93.0.0 +## [Pandora, Belgium +#195.130. +## Bluewin (Schweiz) +#195.186.0.0 +##SONNET (Flatrate) (TOT) +#195.238.160.0 +##SONNET (Flatrate) (TOT) +#195.238.164.0 +##SONNET (Flatrate) (TOT) +#195.238.163.0 +##SONNET (Flatrate) (TOT) +#195.238.174.0 +## US Army Information Systems Command +#199.31.0.0 - 199.31.255.0 +## Netvision +#199.203.4.0 +##Microsoft +#199.203.230.0 +## GSI (NET-GSI-13 +#204.34.138.0 +## HQ, JFMO Korea, Headquarters (NET-JFMO-KOREA) +#204.34.139.0 +## DISA D75 (NET-TPOCNET) +#204.34.140.0 +## U. S. Naval Air Facility, Atsugi Japan (NET-NAFATSUGI01) +#204.34.141.0 +## Naval Enlisted Personnel Management Center (NET-DMCCNHUB) +#204.34.142.0 +## Afloat Training Group Pacific (NET-ATGPAC) +#204.34.143.0 +## HQ Special Operations Command - Europe (HQ SOCEUR) (NET-SCCIS) +#204.34.144.0 +## Commander Naval Base Pearl Harbor (NET-CNBGW) +#204.34.145.0 +## NAVSEA Information Management Improvement Program (NET-TII-NET) +#204.34.147.0 +## Q112 (NET-Q112) +#204.34.148.0 +## Ctr. for Info. Sys.Security,CounterMeasures (NET-ASSIST-149) +#204.34.149.0 +## Resource Consultants, Inc. (NET-RCIADP) +#204.34.150.0 +## Personnel Support Activity, San Diego (NET-PSASD) +#204.34.151.0 +## NAVAL AIR FACILITY, ADAK (NET-LEVEL2ADAK) +#204.34.152.0 +## NAVSEA Logistics Command Detachment (NET-NSLC) +#204.34.153.0 +## PEARL HARBOR NAVAL SHIPYARD (NET-PHNSY-NET1) +#204.34.154.0 +## PEARL HARBOR NAVAL SHIPYARD (NET-PHNSY-NET2) +#204.34.155.0 +## Defense Photography School (NET-DPHSCH) +#204.34.156.0 +## Defense Information School (NETBLK-DINFOSNET1) +#204.34.157.0 - 204.34.160.0 +## Naval Air Systems Command (NET-PMA-222) +#204.34.161.0 +## Puget Sound Naval Shipyard (NET-PSNS-RMC) +#204.34.162.0 +## Joint Precision Strike Demonstration (NET-JPSD-EH) +#204.34.163.0 +## Naval Pacific Meteorology and Ocean (NET-NPMOCW-cool.gif +#204.34.164.0 +## Joint Precision Strike Demonstration (NET-JPSD-ES) +#204.34.165.0 +## USAF (NET-DMSHELPDESK) +#204.34.167.0 +## Commander (NET-THIRDNCB-LAN) +#204.34.168.0 +## Naval Air Warfare Center (NET-MSAICN) +#204.34.169.0 +## Naval Air Systems Command (NET-NADEP) +#204.34.170.0 +## NAVSTA SUPPLY DEPARTMENT (NET-GTMOSUPPLY) +#204.34.171.0 +## SUBMEPP Activity (NET-SUBMEPP-NET5) +#204.34.173.0 +## COMMANDER TASK FORCE 74 YOKOSUKA JAPAN (NET-SEVENFLEET) +#204.34.174.0 +## DISA-PAC,IPC-GUAM (NET-DIPCGU) +#204.34.176.0 +## Satellite Production Test Center (NET-SPTC-CS) +#204.34.177.0 +## 940 Air Refueling Wing (ARW) (NET-ARW940-NET) +#204.34.181.0 +## Defense Megacenter Warner Robins (NET-WR) +#204.34.182.0 +## GCCS Support Facility (NET-GCCS-SUPPFAC) +#204.34.183.0 +## Nav Air Tech Serv Facility-Detachment (NET-NATSFDET-WNY) +#204.34.184.0 +## NAVAL SUPPORT FACILITY, DIEGO GARCIA (NET-NSFDG) +#204.34.185.0 +## Defense Logistics Agency - Europe (NET-EURNET2) +#204.34.186.0 +## NAVMASSO (NET-SNAP1-MATSGMERIDIAN) +#204.34.187.0 +## Commander-In-Chief, US Pacific Fleet (NET-CPF-LAN1) +#204.34.188.0 +## Defense MegaCenter - St Louis (NET-DMC-S-DFAS) +#204.34.189.0 +## NAVMASSO (NET-HMX1MCAFQUANVA-SNAP) +#204.34.190.0 +## HQ SOCEUR (NET-U-SIN) +#204.34.192.0 +## Second Marine Expeditionary Force (NET-U22-NIP) +#204.34.193.0 +## Second Marine Expeditionary Force (NET-U24-NIP) +#204.34.194.0 +## Second Marine Expeditionary Force (NET-U26-NIP) +#204.34.195.0 +## NAVCOMTELSTAWASHDC (NET-MISSI) +#204.34.196.0 +## INFORMATION SYSTEMS TECHNOLOGY CENTER (ISTC) (NET-ISTC) +#204.34.197.0 +## Naval Observatory Detachment, Colorado (NET-FALCON1) +#204.34.198.0 +## NAVILCODETMECH (NET-NAVILCOMECH1) +#204.34.199.0 +## Navy Environmental Preventive Medicine (NET-NEHC-BURNED) +#204.34.200.0 +## Port Hueneme Division, Naval Surf (NET-PHDNSWC) +#204.34.201.0 +## Naval Facilities Engineering Housing (NET-NAVFAC) +#204.34.202.0 +## NAVSEA Logistics Command Detachment (NET-NSLC2) +#204.34.203.0 +## Naval Air Warfare Center (NET-OTT-NET) +#204.34.204.0 +## Portsmouth Naval Shipyard (NET-PORTSNET) +#204.34.205.0 +## INFORMATION SYSTEMS TECHNOLOGY CENTER (ISTC) (NET-ISTCTC) +#204.34.206.0 +## Military Sealift Command Pacific (NETBLK-NETBLK-USNSMERCY204) +#204.34.208.0 - 204.34.210.0 +## USAF Academy (NET-USAFA-GW-NET) +#204.34.211.0 +## 3rd Combat Service Support (NET-CSSG3-NIP) +#204.34.212.0 +## 1st Radio Battalion (NET-RDBN1-NIP) +#204.34.213.0 +## OASD (Health Affairs) (NET-TRICARE) +#204.34.214.0 +## Second Marine Expeditionary Force (NET-MEU26-SIP) +#204.34.215.0 +## 1st Marine Air Wing (NET-ASE1-NIP) +#204.34.216.0 +## SA-ALC/LTE (NET-ITACNET) +#204.34.217.0 +## 3rd Marine (NET-MAR3-NIP) +#204.34.218.0 +## Communications and Electronics (NET-MCBH-NIP) +#204.34.219.0 +## G-6 Operations, III MEF (NET-DFW5BR-NIP) +#204.34.220.0 +## G-6 Operations, III MEF (NET-DFW1-NIP) +#204.34.221.0 +## G-6 Operations, III MEF (NET-DFW2-NIP) +#204.34.222.0 +## G-6 Operations, III MEF (NET-DFW3-NIP) +#204.34.223.0 +## G-6 Operations, III MEF (NET-DFW4-NIP) +#204.34.224.0 +## Joint Interoperability Test Command (NET-JITC-RTD216.25.0 <-- REAL DANGEROUS +#204.34.225.0 +## +#205.96-103.0 +## (FBI's honeypot) +#205.97.0.0 +## (FBI's honeypot) +#205.98.0.0 +## (FBI's honeypot) +#205.99.0.0 +## (FBI's honeypot) +#205.100.0.0 +## (FBI's honeypot) +#205.101.0.0 +## (FBI's honeypot) +#205.102.0.0 +## (FBI's honeypot) +#205.103.0.0 +## [Sherbrooke University, Canada +#206.167. +## (FBI's honeypot) +#207.30.0.0 +## (FBI's honeypot) +#207.31.0.0 +## (FBI's honeypot) +#207.32.0.0 +## (FBI's honeypot) +#207.33.0.0 +## (FBI's honeypot) +#207.34.0.0 +## (FBI's honeypot) +#207.35.0.0 +## (FBI's honeypot) +#207.36.0.0 +## (FBI's honeypot) +#207.37.0.0 +## (FBI's honeypot) +#207.38.0.0 +## (FBI's honeypot) +#207.39.0.0 +## (FBI's honeypot) +#207.40.0.0 +## (FBI's honeypot) +#207.41.0.0 +## (FBI's honeypot) +#207.42.0.0 +## (FBI's honeypot) +#207.43.0.0 +## (FBI's honeypot) +#207.44.0.0 +## (FBI's honey07.60.14.255 J. A. Webster (NETBLK-TIAC-JAWFRT1-2) +#207.45.0.0 +## Trilogic (NETBLK-TIAC-LOGIC384) +#207.60.15.0 - 207.60.15.127 +## Area 54 (NETBLK-TIAC-AREA54) +#207.60.16.0 - 207.60.16.255 +## Vested Development Inc (NETBLK-TIAC-VESTEDT-2) +#207.60.18.0 - 207.60.18.63 +## Conventures (NETBLK-TIAC-CON56-2) +#207.60.18.64 - 207.60.18.127 +## Don Law Company (NETBLK-TIAC-DONLAWT1-2) +#207.60.21.0 - 207.60.21.255 +## Advanced Microsensors (NETBLK-TIAC-AMS384) +#207.60.22.0 - 207.60.22.255 +## Applied Business Center (NETBLK-TIAC-APP56K-2) +#207.60.28.0 - 207.60.28.63 +## Color and Design Exchange (NETBLK-TIAC-COLOR-2) +#207.60.28.64 - 207.60.28.127 +## Shaun McCusker (NETBLK-TIAC-FINLAY) +#207.60.36.8 - 207.60.36.15 +## Town of Framingham (NETBLK-TIAC-INFOSERV) +#207.60.36.16 - 207.60.36.23 +## AB Software (NETBLK-TIAC-ABSADSL) +#207.60.36.24 - 207.60.36.31 +## Seabass Dreams Too Much, Inc (NETBLK-TIAC-SEABASS) +#207.60.36.32 - 207.60.36.39 +## Next Ticketing (NETBLK-TIAC-NT256T1-2) +#207.60.36.40 - 207.60.36.47 +## Dulsi (NETBLK-TIAC-DULSI) +#207.60.36.48 - 207.60.36.55 +## The Internet Access Company (NETBLK-TIAC-PTIMJ) +#207.60.36.56 - 207.60.36.63 +## Maguire Group (NETBLK-TIAC-MAGISDN) +#207.60.36.64 - 207.60.36.71 +## Cogenex (NETBLK-TIAC-COGENEX-2) +#207.60.36.72 - 207.60.36.79 +## AKNDC (NETBLK-TIAC-AKNDC) +#207.60.36.88 - 207.60.36.95 +## McGovern election commitee (NETBLK-TIAC-MCGOVDED) +#207.60.36.96 - 207.60.36.103 +## Digital Equipment Corp (NETBLK-TIAC-DECISDN) +#207.60.36.104 - 207.60.36.111 +## PTR - Precision Technologies (NETBLK-TIAC-PTREB) +#207.60.36.112 - 207.60.36.119 +## Extech (NETBLK-TIAC-EXTECH-2) +#207.60.36.120 - 207.60.36.127 +## Manfreddi Architects (NETBLK-TIAC-MANELKUS-2) +#207.60.36.128 - 207.60.36.135 +## Parent Naffah (NETBLK-TIAC-PARENT) +#207.60.36.144 - 207.60.36.151 +## Darling Dolls Inc (NETBLK-TIAC-EMARTEL) +#207.60.36.152 - 207.60.36.159 +## Wright Communications (NETBLK-TIAC-WRIGHT56) +#207.60.36.160 - 207.60.36.167 +## Principle Software (NETBLK-TIAC-PRINCIPL) +#207.60.36.168 - 207.60.36.175 +## Chris Pet Store (NETBLK-TIAC-CPETDIAL) +#207.60.36.176 - 207.60.36.183 +## Fifteen Lilies (NETBLK-TIAC-GREYROSE) +#207.60.36.184 - 207.60.36.191 +## All-Com Technologies (NETBLK-TIAC-ALLCOM1-2) +#207.60.36.192 - 207.60.36.199 +## Cardio Thoracic Surgical Associates, P. A. (NETBLK-TIAC-HEARTDOC) +#207.60.37.0 - 207.60.37.31 +## Preferred Fixtures Inc (NETBLK-TIAC-PREFMFG-2) +#207.60.37.32 - 207.60.37.63 +## Apple and Eve Distributors (NETBLK-TIAC-AEDED99) +#207.60.37.64 - 207.60.37.95 +## Nelson Copy Supply (NETBLK-TIAC-NCOP56K) +#207.60.37.96 - 207.60.37.127 +## Boston Optical Fiber (NETBLK-TIAC-BOSISDN) +#207.60.37.128 - 207.60.37.159 +## Fantasia&Company (NETBLK-TIAC-FANTCOMP) +#207.60.37.192 - 207.60.37.223 +## Infoactive (NETBLK-TIAC-INFOACT-2) +#207.60.41.0 - 207.60.41.255 +## Curry College (NETBLK-TIAC-CURRY) +#207.60.48.0 - 207.60.48.255 +## Alternate Power Source (NETBLK-TIAC-APSIS) +#207.60.62.32 - 207.60.62.63 +## Keystone Howley-White (NETBLK-TIAC-KEYSTONE-2) +#207.60.62.64 - 207.60.62.95 +## Bridgehead Associates LTD (NETBLK-TIAC-BRIDG384) +#207.60.62.128 - 207.60.62.159 +## County Supply (NETBLK-TIAC-CS384) +#207.60.62.160 - 207.60.62.191 +## NH Board of Nursing (NETBLK-TIAC-NHBNDED99) +#207.60.62.192 - 207.60.62.223 +## Diversified Wireless Technologies (NETBLK-TIAC-LAUCHAM) +#207.60.64.0 - 207.60.64.63 +## Phytera (NETBLK-TIAC-PHYTERA1) +#207.60.64.64 - 207.60.64.127 +## The Network Connection (NETBLK-TIAC-TNC56) +#207.60.66.0 - 207.60.66.15 +## Young Refrigeration (NETBLK-TIAC-YRDED135) +#207.60.66.16 - 207.60.66.31 +## Vision Appraisal Technology (NETBLK-TIAC-VISIONT1) +#207.60.66.32 - 207.60.66.47 +## EffNet Inc (NETBLK-TIAC-EFFNETT1) +#207.60.66.48 - 207.60.66.63 +## Entropic Systems Inc (NETBLK-TIAC-ENTRO128-3) +#207.60.66.64 - 207.60.66.79 +## Finley Properties (NETBLK-TIAC-FPDED135) +#207.60.66.80 - 207.60.66.95 +## Nancy Plowman Associates (NETBLK-TIAC-NPAISDN) +#207.60.66.96 - 207.60.66.111 +## Northeast Financial Strategies (NETBLK-TIAC-NFSISDN) +#207.60.66.112 - 207.60.66.127 +## Textnology Corp (NETBLK-TIAC-TEXTC0RP) +#207.60.66.128 - 207.60.66.143 +## Groton Neochem LLC (NETBLK-TIAC-NEOCHEM) +#207.60.66.144 - 207.60.66.159 +## Tab Computers (NETBLK-TIAC-TCSI3) +#207.60.66.160 - 207.60.66.175 +## Patrons Insurance (NETBL +#207.60.66.176 - 207.60.66.191 \ No newline at end of file diff --git a/html/login.html b/html/login.html new file mode 100755 index 0000000..8b911a2 --- /dev/null +++ b/html/login.html @@ -0,0 +1,26 @@ + +
+
+

- Log in

+ + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/html/nav.html b/html/nav.html new file mode 100755 index 0000000..435113e --- /dev/null +++ b/html/nav.html @@ -0,0 +1,33 @@ + + + + + + + + <!--Place title here!!!--> + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/html/src/UbuntuMono-R.ttf b/html/src/UbuntuMono-R.ttf new file mode 100755 index 0000000..fdd309d Binary files /dev/null and b/html/src/UbuntuMono-R.ttf differ diff --git a/html/src/auth.js b/html/src/auth.js new file mode 100755 index 0000000..94e5f7c --- /dev/null +++ b/html/src/auth.js @@ -0,0 +1,65 @@ +import * as utils from '/src/utils.js' +import * as jsonpack from '/src/jsonpack.js' + +let c = null + +export function authClient(rawClient) { + c = this + this.username = null + this.permGroups = null + this.accountCreated = null + this.passwordUpdated = null + this.id = null + this.rawClient = rawClient + + this.login = (username, password)=>{ + const salt = utils.getUnixTime() + const key = (utils.sha256( + username + + utils.sha256(password) + + salt + )) + //console.log(c.rawClient.send) + c.rawClient.send('login', { + data: key, + salt: salt + }) + } + + this.clidata = (data)=>{ + const session = utils.getCookie('session') + if(session != ''){ + this.rawClient.send('reauth', { + session: session + }) + } + } + + this.loginSuccess = (data)=>{ + utils.popupSuccess('Connection', 'Successfully logged in!') + utils.iconauth() + console.log(data.data.session) + if(data.data.session != null){ + utils.setCookie('session', data.data.session) + } + window.location = data.data.redir + } + + this.reauth = (data)=>{ + + this.username = data.data.username + this.id = data.data.id + this.permGroups = data.data.permGroups + this.accountCreated = data.data.created + this.passwordUpdated = data.data.passwordUpdated + + if(window.location.pathname == "/login"){ + utils.popupSuccess('Connection', 'Successfully logged in!') + } + utils.iconauth() + } + + this.rawClient.addRawTypeListener('clidata', this.clidata) + this.rawClient.addRawTypeListener('loginSuccess', this.loginSuccess) + this.rawClient.addRawTypeListener('reauth', this.reauth) +} \ No newline at end of file diff --git a/html/src/jsonpack.js b/html/src/jsonpack.js new file mode 100755 index 0000000..7569abc --- /dev/null +++ b/html/src/jsonpack.js @@ -0,0 +1,559 @@ +/* + Copyright (c) 2013, Rodrigo González, Sapienlab All Rights Reserved. + Available via MIT LICENSE. See https://github.com/roro89/jsonpack/blob/master/LICENSE.md for details. +*/ + + +var TOKEN_TRUE = -1; +var TOKEN_FALSE = -2; +var TOKEN_NULL = -3; +var TOKEN_EMPTY_STRING = -4; +var TOKEN_UNDEFINED = -5; + +export function pack(json, options) { + + // Canonizes the options + options = options || {}; + + // A shorthand for debugging + var verbose = options.verbose || false; + + verbose && console.log('Normalize the JSON Object'); + + // JSON as Javascript Object (Not string representation) + json = typeof json === 'string' ? this.JSON.parse(json) : json; + + verbose && console.log('Creating a empty dictionary'); + + // The dictionary + var dictionary = { + strings : [], + integers : [], + floats : [] + }; + + verbose && console.log('Creating the AST'); + + // The AST + var ast = (function recursiveAstBuilder(item) { + + verbose && console.log('Calling recursiveAstBuilder with ' + this.JSON.stringify(item)); + + // The type of the item + var type = typeof item; + + // Case 7: The item is null + if (item === null) { + return { + type : 'null', + index : TOKEN_NULL + }; + } + + //add undefined + if (typeof item === 'undefined') { + return { + type : 'undefined', + index : TOKEN_UNDEFINED + }; + } + + // Case 1: The item is Array Object + if ( item instanceof Array) { + + // Create a new sub-AST of type Array (@) + var ast = ['@']; + + // Add each items + for (var i in item) { + + if (!item.hasOwnProperty(i)) continue; + + ast.push(recursiveAstBuilder(item[i])); + } + + // And return + return ast; + + } + + // Case 2: The item is Object + if (type === 'object') { + + // Create a new sub-AST of type Object ($) + var ast = ['$']; + + // Add each items + for (var key in item) { + + if (!item.hasOwnProperty(key)) + continue; + + ast.push(recursiveAstBuilder(key)); + ast.push(recursiveAstBuilder(item[key])); + } + + // And return + return ast; + + } + + // Case 3: The item empty string + if (item === '') { + return { + type : 'empty', + index : TOKEN_EMPTY_STRING + }; + } + + // Case 4: The item is String + if (type === 'string') { + + // The index of that word in the dictionary + var index = _indexOf.call(dictionary.strings, item); + + // If not, add to the dictionary and actualize the index + if (index == -1) { + dictionary.strings.push(_encode(item)); + index = dictionary.strings.length - 1; + } + + // Return the token + return { + type : 'strings', + index : index + }; + } + + // Case 5: The item is integer + if (type === 'number' && item % 1 === 0) { + + // The index of that number in the dictionary + var index = _indexOf.call(dictionary.integers, item); + + // If not, add to the dictionary and actualize the index + if (index == -1) { + dictionary.integers.push(_base10To36(item)); + index = dictionary.integers.length - 1; + } + + // Return the token + return { + type : 'integers', + index : index + }; + } + + // Case 6: The item is float + if (type === 'number') { + // The index of that number in the dictionary + var index = _indexOf.call(dictionary.floats, item); + + // If not, add to the dictionary and actualize the index + if (index == -1) { + // Float not use base 36 + dictionary.floats.push(item); + index = dictionary.floats.length - 1; + } + + // Return the token + return { + type : 'floats', + index : index + }; + } + + // Case 7: The item is boolean + if (type === 'boolean') { + return { + type : 'boolean', + index : item ? TOKEN_TRUE : TOKEN_FALSE + }; + } + + // Default + throw new Error('Unexpected argument of type ' + typeof (item)); + + })(json); + + // A set of shorthands proxies for the length of the dictionaries + var stringLength = dictionary.strings.length; + var integerLength = dictionary.integers.length; + var floatLength = dictionary.floats.length; + + verbose && console.log('Parsing the dictionary'); + + // Create a raw dictionary + var packed = dictionary.strings.join('|'); + packed += '^' + dictionary.integers.join('|'); + packed += '^' + dictionary.floats.join('|'); + + verbose && console.log('Parsing the structure'); + + // And add the structure + packed += '^' + (function recursiveParser(item) { + + verbose && console.log('Calling a recursiveParser with ' + this.JSON.stringify(item)); + + // If the item is Array, then is a object of + // type [object Object] or [object Array] + if ( item instanceof Array) { + + // The packed resulting + var packed = item.shift(); + + for (var i in item) { + + if (!item.hasOwnProperty(i)) + continue; + + packed += recursiveParser(item[i]) + '|'; + } + + return (packed[packed.length - 1] === '|' ? packed.slice(0, -1) : packed) + ']'; + + } + + // A shorthand proxies + var type = item.type, index = item.index; + + if (type === 'strings') { + // Just return the base 36 of index + return _base10To36(index); + } + + if (type === 'integers') { + // Return a base 36 of index plus stringLength offset + return _base10To36(stringLength + index); + } + + if (type === 'floats') { + // Return a base 36 of index plus stringLength and integerLength offset + return _base10To36(stringLength + integerLength + index); + } + + if (type === 'boolean') { + return item.index; + } + + if (type === 'null') { + return TOKEN_NULL; + } + + if (type === 'undefined') { + return TOKEN_UNDEFINED; + } + + if (type === 'empty') { + return TOKEN_EMPTY_STRING; + } + + throw new TypeError('The item is alien!'); + + })(ast); + + verbose && console.log('Ending parser'); + + // If debug, return a internal representation of dictionary and stuff + if (options.debug) + return { + dictionary : dictionary, + ast : ast, + packed : packed + }; + + return packed; + +}; + +export function unpack(packed, options) { + + // Canonizes the options + options = options || {}; + + // A raw buffer + var rawBuffers = packed.split('^'); + + // Create a dictionary + options.verbose && console.log('Building dictionary'); + var dictionary = []; + + // Add the strings values + var buffer = rawBuffers[0]; + if (buffer !== '') { + buffer = buffer.split('|'); + options.verbose && console.log('Parse the strings dictionary'); + for (var i=0, n=buffer.length; i{ + window.utils.popupInfo(data.data.title, data.data.msg) +}) + +window.addListener('popupSuccess', (data)=>{ + window.utils.popupSuccess(data.data.title, data.data.msg) +}) + +window.addListener('popupWarning', (data)=>{ + window.utils.popupWarning(data.data.title, data.data.msg) +}) + +window.addListener('popupError', (data)=>{ + window.utils.popupError(data.data.title, data.data.msg) +}) + +window.addListener('popupColor', (data)=>{ + window.utils.addPopup(data.data.color, data.data.isDark, data.data.title, data.data.msg) +}) + +window.main() \ No newline at end of file diff --git a/html/src/packets.js b/html/src/packets.js new file mode 100755 index 0000000..d9335e9 --- /dev/null +++ b/html/src/packets.js @@ -0,0 +1,106 @@ +import * as jsonpack from '/src/jsonpack.js' +import * as utils from "/src/utils.js" + + +let cID = null +let evListeners = [] +let c = null + +function getErrorDesc(error){ + switch(error){ + case 'invalidLogin': + return 'Invalid username or password' + case 'invalidLoginRequest': + utils.setCookie('session', '') + return 'Some part of the login request is invalid, please try again' + case 'prelogin': + window.location.pathname = '/' + return 'You are already logged in' + } + return error +} + +function processData(data){ + data = jsonpack.unpack(data) + switch(data.type){ + case 'clidata': + c.cID = data.data.cid + if(window.location.pathname == "/login") { + utils.popupWarning('Connection', 'Connected to server!') + utils.iconunauth() + }else{ + utils.popupInfo('Connection', 'Connected to server!') + utils.iconauth() + } + break + case 'redir': + window.location.pathname = data.location + case 'data': + console.log(data.data) + break + case 'error': + utils.popupError(`Error: ${data.data}`, getErrorDesc(data.data)) + } + for(let i=0;i{} + this.onclose = ()=>{} + + this.cID = null + + this.evtSource.onmessage = (event) => { + console.log(`Data: ${event.data}`) + processData(event.data) + } + + this.evtSource.onerror = (event) => { + console.log('Error!') + this.connected = false + utils.icondisconnect() + utils.popupError('Connection', 'Disconnected from server') + this.onclose() + } + + this.evtSource.onopen = (event) => { + console.log('Connected!') + this.connected = true + utils.iconunauth() + this.onopen() + } + + this.send = (type, data)=>{ + //console.log({type, data}) + fetch(this.location, { + method: "post", + headers: { + 'Accept': '*', + 'Content-Type': 'text/plain' + }, + body: jsonpack.pack({ + type: type, + data: data, + cid: this.cID + }) + }) + } + + this.addRawTypeListener = (type, func)=>{ + evListeners.push({ + type: type, + func: func + }) + } +} \ No newline at end of file diff --git a/html/src/pico.min.css b/html/src/pico.min.css new file mode 100755 index 0000000..d0d5ba4 --- /dev/null +++ b/html/src/pico.min.css @@ -0,0 +1,5 @@ +@charset "UTF-8";/*! + * Pico CSS v1.5.11 (https://picocss.com) + * Copyright 2019-2023 - Licensed under MIT + */:root{--font-family:system-ui,-apple-system,"Segoe UI","Roboto","Ubuntu","Cantarell","Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--line-height:1.5;--font-weight:400;--font-size:16px;--border-radius:0.25rem;--border-width:1px;--outline-width:3px;--spacing:1rem;--typography-spacing-vertical:1.5rem;--block-spacing-vertical:calc(var(--spacing) * 2);--block-spacing-horizontal:var(--spacing);--grid-spacing-vertical:0;--grid-spacing-horizontal:var(--spacing);--form-element-spacing-vertical:0.75rem;--form-element-spacing-horizontal:1rem;--nav-element-spacing-vertical:1rem;--nav-element-spacing-horizontal:0.5rem;--nav-link-spacing-vertical:0.5rem;--nav-link-spacing-horizontal:0.5rem;--form-label-font-weight:var(--font-weight);--transition:0.2s ease-in-out;--modal-overlay-backdrop-filter:blur(0.25rem)}@media (min-width:576px){:root{--font-size:17px}}@media (min-width:768px){:root{--font-size:18px}}@media (min-width:992px){:root{--font-size:19px}}@media (min-width:1200px){:root{--font-size:20px}}@media (min-width:576px){body>footer,body>header,body>main,section{--block-spacing-vertical:calc(var(--spacing) * 2.5)}}@media (min-width:768px){body>footer,body>header,body>main,section{--block-spacing-vertical:calc(var(--spacing) * 3)}}@media (min-width:992px){body>footer,body>header,body>main,section{--block-spacing-vertical:calc(var(--spacing) * 3.5)}}@media (min-width:1200px){body>footer,body>header,body>main,section{--block-spacing-vertical:calc(var(--spacing) * 4)}}@media (min-width:576px){article{--block-spacing-horizontal:calc(var(--spacing) * 1.25)}}@media (min-width:768px){article{--block-spacing-horizontal:calc(var(--spacing) * 1.5)}}@media (min-width:992px){article{--block-spacing-horizontal:calc(var(--spacing) * 1.75)}}@media (min-width:1200px){article{--block-spacing-horizontal:calc(var(--spacing) * 2)}}dialog>article{--block-spacing-vertical:calc(var(--spacing) * 2);--block-spacing-horizontal:var(--spacing)}@media (min-width:576px){dialog>article{--block-spacing-vertical:calc(var(--spacing) * 2.5);--block-spacing-horizontal:calc(var(--spacing) * 1.25)}}@media (min-width:768px){dialog>article{--block-spacing-vertical:calc(var(--spacing) * 3);--block-spacing-horizontal:calc(var(--spacing) * 1.5)}}a{--text-decoration:none}a.contrast,a.secondary{--text-decoration:underline}small{--font-size:0.875em}h1,h2,h3,h4,h5,h6{--font-weight:700}h1{--font-size:2rem;--typography-spacing-vertical:3rem}h2{--font-size:1.75rem;--typography-spacing-vertical:2.625rem}h3{--font-size:1.5rem;--typography-spacing-vertical:2.25rem}h4{--font-size:1.25rem;--typography-spacing-vertical:1.874rem}h5{--font-size:1.125rem;--typography-spacing-vertical:1.6875rem}[type=checkbox],[type=radio]{--border-width:2px}[type=checkbox][role=switch]{--border-width:3px}tfoot td,tfoot th,thead td,thead th{--border-width:3px}:not(thead,tfoot)>*>td{--font-size:0.875em}code,kbd,pre,samp{--font-family:"Menlo","Consolas","Roboto Mono","Ubuntu Monospace","Noto Mono","Oxygen Mono","Liberation Mono",monospace,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji"}kbd{--font-weight:bolder}:root:not([data-theme=dark]),[data-theme=light]{--background-color:#fff;--color:hsl(205, 20%, 32%);--h1-color:hsl(205, 30%, 15%);--h2-color:#24333e;--h3-color:hsl(205, 25%, 23%);--h4-color:#374956;--h5-color:hsl(205, 20%, 32%);--h6-color:#4d606d;--muted-color:hsl(205, 10%, 50%);--muted-border-color:hsl(205, 20%, 94%);--primary:hsl(195, 85%, 41%);--primary-hover:hsl(195, 90%, 32%);--primary-focus:rgba(16, 149, 193, 0.125);--primary-inverse:#fff;--secondary:hsl(205, 15%, 41%);--secondary-hover:hsl(205, 20%, 32%);--secondary-focus:rgba(89, 107, 120, 0.125);--secondary-inverse:#fff;--contrast:hsl(205, 30%, 15%);--contrast-hover:#000;--contrast-focus:rgba(89, 107, 120, 0.125);--contrast-inverse:#fff;--mark-background-color:#fff2ca;--mark-color:#543a26;--ins-color:#388e3c;--del-color:#c62828;--blockquote-border-color:var(--muted-border-color);--blockquote-footer-color:var(--muted-color);--button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--form-element-background-color:transparent;--form-element-border-color:hsl(205, 14%, 68%);--form-element-color:var(--color);--form-element-placeholder-color:var(--muted-color);--form-element-active-background-color:transparent;--form-element-active-border-color:var(--primary);--form-element-focus-color:var(--primary-focus);--form-element-disabled-background-color:hsl(205, 18%, 86%);--form-element-disabled-border-color:hsl(205, 14%, 68%);--form-element-disabled-opacity:0.5;--form-element-invalid-border-color:#c62828;--form-element-invalid-active-border-color:#d32f2f;--form-element-invalid-focus-color:rgba(211, 47, 47, 0.125);--form-element-valid-border-color:#388e3c;--form-element-valid-active-border-color:#43a047;--form-element-valid-focus-color:rgba(67, 160, 71, 0.125);--switch-background-color:hsl(205, 16%, 77%);--switch-color:var(--primary-inverse);--switch-checked-background-color:var(--primary);--range-border-color:hsl(205, 18%, 86%);--range-active-border-color:hsl(205, 16%, 77%);--range-thumb-border-color:var(--background-color);--range-thumb-color:var(--secondary);--range-thumb-hover-color:var(--secondary-hover);--range-thumb-active-color:var(--primary);--table-border-color:var(--muted-border-color);--table-row-stripped-background-color:#f6f8f9;--code-background-color:hsl(205, 20%, 94%);--code-color:var(--muted-color);--code-kbd-background-color:var(--contrast);--code-kbd-color:var(--contrast-inverse);--code-tag-color:hsl(330, 40%, 50%);--code-property-color:hsl(185, 40%, 40%);--code-value-color:hsl(40, 20%, 50%);--code-comment-color:hsl(205, 14%, 68%);--accordion-border-color:var(--muted-border-color);--accordion-close-summary-color:var(--color);--accordion-open-summary-color:var(--muted-color);--card-background-color:var(--background-color);--card-border-color:var(--muted-border-color);--card-box-shadow:0.0145rem 0.029rem 0.174rem rgba(27, 40, 50, 0.01698),0.0335rem 0.067rem 0.402rem rgba(27, 40, 50, 0.024),0.0625rem 0.125rem 0.75rem rgba(27, 40, 50, 0.03),0.1125rem 0.225rem 1.35rem rgba(27, 40, 50, 0.036),0.2085rem 0.417rem 2.502rem rgba(27, 40, 50, 0.04302),0.5rem 1rem 6rem rgba(27, 40, 50, 0.06),0 0 0 0.0625rem rgba(27, 40, 50, 0.015);--card-sectionning-background-color:#fbfbfc;--dropdown-background-color:#fbfbfc;--dropdown-border-color:#e1e6eb;--dropdown-box-shadow:var(--card-box-shadow);--dropdown-color:var(--color);--dropdown-hover-background-color:hsl(205, 20%, 94%);--modal-overlay-background-color:rgba(213, 220, 226, 0.7);--progress-background-color:hsl(205, 18%, 86%);--progress-color:var(--primary);--loading-spinner-opacity:0.5;--tooltip-background-color:var(--contrast);--tooltip-color:var(--contrast-inverse);--icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(198, 40, 40)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(65, 84, 98)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(56, 142, 60)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:light}@media only screen and (prefers-color-scheme:dark){:root:not([data-theme]){--background-color:#11191f;--color:hsl(205, 16%, 77%);--h1-color:hsl(205, 20%, 94%);--h2-color:#e1e6eb;--h3-color:hsl(205, 18%, 86%);--h4-color:#c8d1d8;--h5-color:hsl(205, 16%, 77%);--h6-color:#afbbc4;--muted-color:hsl(205, 10%, 50%);--muted-border-color:#1f2d38;--primary:hsl(195, 85%, 41%);--primary-hover:hsl(195, 80%, 50%);--primary-focus:rgba(16, 149, 193, 0.25);--primary-inverse:#fff;--secondary:hsl(205, 15%, 41%);--secondary-hover:hsl(205, 10%, 50%);--secondary-focus:rgba(115, 130, 140, 0.25);--secondary-inverse:#fff;--contrast:hsl(205, 20%, 94%);--contrast-hover:#fff;--contrast-focus:rgba(115, 130, 140, 0.25);--contrast-inverse:#000;--mark-background-color:#d1c284;--mark-color:#11191f;--ins-color:#388e3c;--del-color:#c62828;--blockquote-border-color:var(--muted-border-color);--blockquote-footer-color:var(--muted-color);--button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--form-element-background-color:#11191f;--form-element-border-color:#374956;--form-element-color:var(--color);--form-element-placeholder-color:var(--muted-color);--form-element-active-background-color:var(--form-element-background-color);--form-element-active-border-color:var(--primary);--form-element-focus-color:var(--primary-focus);--form-element-disabled-background-color:hsl(205, 25%, 23%);--form-element-disabled-border-color:hsl(205, 20%, 32%);--form-element-disabled-opacity:0.5;--form-element-invalid-border-color:#b71c1c;--form-element-invalid-active-border-color:#c62828;--form-element-invalid-focus-color:rgba(198, 40, 40, 0.25);--form-element-valid-border-color:#2e7d32;--form-element-valid-active-border-color:#388e3c;--form-element-valid-focus-color:rgba(56, 142, 60, 0.25);--switch-background-color:#374956;--switch-color:var(--primary-inverse);--switch-checked-background-color:var(--primary);--range-border-color:#24333e;--range-active-border-color:hsl(205, 25%, 23%);--range-thumb-border-color:var(--background-color);--range-thumb-color:var(--secondary);--range-thumb-hover-color:var(--secondary-hover);--range-thumb-active-color:var(--primary);--table-border-color:var(--muted-border-color);--table-row-stripped-background-color:rgba(115, 130, 140, 0.05);--code-background-color:#18232c;--code-color:var(--muted-color);--code-kbd-background-color:var(--contrast);--code-kbd-color:var(--contrast-inverse);--code-tag-color:hsl(330, 30%, 50%);--code-property-color:hsl(185, 30%, 50%);--code-value-color:hsl(40, 10%, 50%);--code-comment-color:#4d606d;--accordion-border-color:var(--muted-border-color);--accordion-active-summary-color:var(--primary);--accordion-close-summary-color:var(--color);--accordion-open-summary-color:var(--muted-color);--card-background-color:#141e26;--card-border-color:var(--card-background-color);--card-box-shadow:0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),0 0 0 0.0625rem rgba(0, 0, 0, 0.015);--card-sectionning-background-color:#18232c;--dropdown-background-color:hsl(205, 30%, 15%);--dropdown-border-color:#24333e;--dropdown-box-shadow:var(--card-box-shadow);--dropdown-color:var(--color);--dropdown-hover-background-color:rgba(36, 51, 62, 0.75);--modal-overlay-background-color:rgba(36, 51, 62, 0.8);--progress-background-color:#24333e;--progress-color:var(--primary);--loading-spinner-opacity:0.5;--tooltip-background-color:var(--contrast);--tooltip-color:var(--contrast-inverse);--icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}}[data-theme=dark]{--background-color:#11191f;--color:hsl(205, 16%, 77%);--h1-color:hsl(205, 20%, 94%);--h2-color:#e1e6eb;--h3-color:hsl(205, 18%, 86%);--h4-color:#c8d1d8;--h5-color:hsl(205, 16%, 77%);--h6-color:#afbbc4;--muted-color:hsl(205, 10%, 50%);--muted-border-color:#1f2d38;--primary:hsl(195, 85%, 41%);--primary-hover:hsl(195, 80%, 50%);--primary-focus:rgba(16, 149, 193, 0.25);--primary-inverse:#fff;--secondary:hsl(205, 15%, 41%);--secondary-hover:hsl(205, 10%, 50%);--secondary-focus:rgba(115, 130, 140, 0.25);--secondary-inverse:#fff;--contrast:hsl(205, 20%, 94%);--contrast-hover:#fff;--contrast-focus:rgba(115, 130, 140, 0.25);--contrast-inverse:#000;--mark-background-color:#d1c284;--mark-color:#11191f;--ins-color:#388e3c;--del-color:#c62828;--blockquote-border-color:var(--muted-border-color);--blockquote-footer-color:var(--muted-color);--button-box-shadow:0 0 0 rgba(0, 0, 0, 0);--button-hover-box-shadow:0 0 0 rgba(0, 0, 0, 0);--form-element-background-color:#11191f;--form-element-border-color:#374956;--form-element-color:var(--color);--form-element-placeholder-color:var(--muted-color);--form-element-active-background-color:var(--form-element-background-color);--form-element-active-border-color:var(--primary);--form-element-focus-color:var(--primary-focus);--form-element-disabled-background-color:hsl(205, 25%, 23%);--form-element-disabled-border-color:hsl(205, 20%, 32%);--form-element-disabled-opacity:0.5;--form-element-invalid-border-color:#b71c1c;--form-element-invalid-active-border-color:#c62828;--form-element-invalid-focus-color:rgba(198, 40, 40, 0.25);--form-element-valid-border-color:#2e7d32;--form-element-valid-active-border-color:#388e3c;--form-element-valid-focus-color:rgba(56, 142, 60, 0.25);--switch-background-color:#374956;--switch-color:var(--primary-inverse);--switch-checked-background-color:var(--primary);--range-border-color:#24333e;--range-active-border-color:hsl(205, 25%, 23%);--range-thumb-border-color:var(--background-color);--range-thumb-color:var(--secondary);--range-thumb-hover-color:var(--secondary-hover);--range-thumb-active-color:var(--primary);--table-border-color:var(--muted-border-color);--table-row-stripped-background-color:rgba(115, 130, 140, 0.05);--code-background-color:#18232c;--code-color:var(--muted-color);--code-kbd-background-color:var(--contrast);--code-kbd-color:var(--contrast-inverse);--code-tag-color:hsl(330, 30%, 50%);--code-property-color:hsl(185, 30%, 50%);--code-value-color:hsl(40, 10%, 50%);--code-comment-color:#4d606d;--accordion-border-color:var(--muted-border-color);--accordion-active-summary-color:var(--primary);--accordion-close-summary-color:var(--color);--accordion-open-summary-color:var(--muted-color);--card-background-color:#141e26;--card-border-color:var(--card-background-color);--card-box-shadow:0.0145rem 0.029rem 0.174rem rgba(0, 0, 0, 0.01698),0.0335rem 0.067rem 0.402rem rgba(0, 0, 0, 0.024),0.0625rem 0.125rem 0.75rem rgba(0, 0, 0, 0.03),0.1125rem 0.225rem 1.35rem rgba(0, 0, 0, 0.036),0.2085rem 0.417rem 2.502rem rgba(0, 0, 0, 0.04302),0.5rem 1rem 6rem rgba(0, 0, 0, 0.06),0 0 0 0.0625rem rgba(0, 0, 0, 0.015);--card-sectionning-background-color:#18232c;--dropdown-background-color:hsl(205, 30%, 15%);--dropdown-border-color:#24333e;--dropdown-box-shadow:var(--card-box-shadow);--dropdown-color:var(--color);--dropdown-hover-background-color:rgba(36, 51, 62, 0.75);--modal-overlay-background-color:rgba(36, 51, 62, 0.8);--progress-background-color:#24333e;--progress-color:var(--primary);--loading-spinner-opacity:0.5;--tooltip-background-color:var(--contrast);--tooltip-color:var(--contrast-inverse);--icon-checkbox:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-chevron-button-inverse:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(0, 0, 0)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");--icon-close:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(115, 130, 140)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'%3E%3C/line%3E%3Cline x1='6' y1='6' x2='18' y2='18'%3E%3C/line%3E%3C/svg%3E");--icon-date:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='16' y1='2' x2='16' y2='6'%3E%3C/line%3E%3Cline x1='8' y1='2' x2='8' y2='6'%3E%3C/line%3E%3Cline x1='3' y1='10' x2='21' y2='10'%3E%3C/line%3E%3C/svg%3E");--icon-invalid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(183, 28, 28)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");--icon-minus:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(255, 255, 255)' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'%3E%3C/line%3E%3C/svg%3E");--icon-search:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E");--icon-time:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(162, 175, 185)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpolyline points='12 6 12 12 16 14'%3E%3C/polyline%3E%3C/svg%3E");--icon-valid:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='rgb(46, 125, 50)' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");color-scheme:dark}[type=checkbox],[type=radio],[type=range],progress{accent-color:var(--primary)}*,::after,::before{box-sizing:border-box;background-repeat:no-repeat}::after,::before{text-decoration:inherit;vertical-align:inherit}:where(:root){-webkit-tap-highlight-color:transparent;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--background-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);line-height:var(--line-height);font-family:var(--font-family);text-rendering:optimizeLegibility;overflow-wrap:break-word;cursor:default;-moz-tab-size:4;-o-tab-size:4;tab-size:4}main{display:block}body{width:100%;margin:0}body>footer,body>header,body>main{width:100%;margin-right:auto;margin-left:auto;padding:var(--block-spacing-vertical) 0}.container,.container-fluid{width:100%;margin-right:auto;margin-left:auto;padding-right:var(--spacing);padding-left:var(--spacing)}@media (min-width:576px){.container{max-width:510px;padding-right:0;padding-left:0}}@media (min-width:768px){.container{max-width:700px}}@media (min-width:992px){.container{max-width:920px}}@media (min-width:1200px){.container{max-width:1130px}}section{margin-bottom:var(--block-spacing-vertical)}.grid{grid-column-gap:var(--grid-spacing-horizontal);grid-row-gap:var(--grid-spacing-vertical);display:grid;grid-template-columns:1fr;margin:0}@media (min-width:992px){.grid{grid-template-columns:repeat(auto-fit,minmax(0%,1fr))}}.grid>*{min-width:0}figure{display:block;margin:0;padding:0;overflow-x:auto}figure figcaption{padding:calc(var(--spacing) * .5) 0;color:var(--muted-color)}b,strong{font-weight:bolder}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}address,blockquote,dl,figure,form,ol,p,pre,table,ul{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-style:normal;font-weight:var(--font-weight);font-size:var(--font-size)}[role=link],a{--color:var(--primary);--background-color:transparent;outline:0;background-color:var(--background-color);color:var(--color);-webkit-text-decoration:var(--text-decoration);text-decoration:var(--text-decoration);transition:background-color var(--transition),color var(--transition),box-shadow var(--transition),-webkit-text-decoration var(--transition);transition:background-color var(--transition),color var(--transition),text-decoration var(--transition),box-shadow var(--transition);transition:background-color var(--transition),color var(--transition),text-decoration var(--transition),box-shadow var(--transition),-webkit-text-decoration var(--transition)}[role=link]:is([aria-current],:hover,:active,:focus),a:is([aria-current],:hover,:active,:focus){--color:var(--primary-hover);--text-decoration:underline}[role=link]:focus,a:focus{--background-color:var(--primary-focus)}[role=link].secondary,a.secondary{--color:var(--secondary)}[role=link].secondary:is([aria-current],:hover,:active,:focus),a.secondary:is([aria-current],:hover,:active,:focus){--color:var(--secondary-hover)}[role=link].secondary:focus,a.secondary:focus{--background-color:var(--secondary-focus)}[role=link].contrast,a.contrast{--color:var(--contrast)}[role=link].contrast:is([aria-current],:hover,:active,:focus),a.contrast:is([aria-current],:hover,:active,:focus){--color:var(--contrast-hover)}[role=link].contrast:focus,a.contrast:focus{--background-color:var(--contrast-focus)}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:var(--typography-spacing-vertical);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);font-family:var(--font-family)}h1{--color:var(--h1-color)}h2{--color:var(--h2-color)}h3{--color:var(--h3-color)}h4{--color:var(--h4-color)}h5{--color:var(--h5-color)}h6{--color:var(--h6-color)}:where(address,blockquote,dl,figure,form,ol,p,pre,table,ul)~:is(h1,h2,h3,h4,h5,h6){margin-top:var(--typography-spacing-vertical)}.headings,hgroup{margin-bottom:var(--typography-spacing-vertical)}.headings>*,hgroup>*{margin-bottom:0}.headings>:last-child,hgroup>:last-child{--color:var(--muted-color);--font-weight:unset;font-size:1rem;font-family:unset}p{margin-bottom:var(--typography-spacing-vertical)}small{font-size:var(--font-size)}:where(dl,ol,ul){padding-right:0;padding-left:var(--spacing);padding-inline-start:var(--spacing);padding-inline-end:0}:where(dl,ol,ul) li{margin-bottom:calc(var(--typography-spacing-vertical) * .25)}:where(dl,ol,ul) :is(dl,ol,ul){margin:0;margin-top:calc(var(--typography-spacing-vertical) * .25)}ul li{list-style:square}mark{padding:.125rem .25rem;background-color:var(--mark-background-color);color:var(--mark-color);vertical-align:baseline}blockquote{display:block;margin:var(--typography-spacing-vertical) 0;padding:var(--spacing);border-right:none;border-left:.25rem solid var(--blockquote-border-color);border-inline-start:0.25rem solid var(--blockquote-border-color);border-inline-end:none}blockquote footer{margin-top:calc(var(--typography-spacing-vertical) * .5);color:var(--blockquote-footer-color)}abbr[title]{border-bottom:1px dotted;text-decoration:none;cursor:help}ins{color:var(--ins-color);text-decoration:none}del{color:var(--del-color)}::-moz-selection{background-color:var(--primary-focus)}::selection{background-color:var(--primary-focus)}:where(audio,canvas,iframe,img,svg,video){vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}:where(iframe){border-style:none}img{max-width:100%;height:auto;border-style:none}:where(svg:not([fill])){fill:currentColor}svg:not(:root){overflow:hidden}button{margin:0;overflow:visible;font-family:inherit;text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}button{display:block;width:100%;margin-bottom:var(--spacing)}[role=button]{display:inline-block;text-decoration:none}[role=button],button,input[type=button],input[type=reset],input[type=submit]{--background-color:var(--primary);--border-color:var(--primary);--color:var(--primary-inverse);--box-shadow:var(--button-box-shadow, 0 0 0 rgba(0, 0, 0, 0));padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:0;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[role=button]:is([aria-current],:hover,:active,:focus),button:is([aria-current],:hover,:active,:focus),input[type=button]:is([aria-current],:hover,:active,:focus),input[type=reset]:is([aria-current],:hover,:active,:focus),input[type=submit]:is([aria-current],:hover,:active,:focus){--background-color:var(--primary-hover);--border-color:var(--primary-hover);--box-shadow:var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0));--color:var(--primary-inverse)}[role=button]:focus,button:focus,input[type=button]:focus,input[type=reset]:focus,input[type=submit]:focus{--box-shadow:var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--outline-width) var(--primary-focus)}:is(button,input[type=submit],input[type=button],[role=button]).secondary,input[type=reset]{--background-color:var(--secondary);--border-color:var(--secondary);--color:var(--secondary-inverse);cursor:pointer}:is(button,input[type=submit],input[type=button],[role=button]).secondary:is([aria-current],:hover,:active,:focus),input[type=reset]:is([aria-current],:hover,:active,:focus){--background-color:var(--secondary-hover);--border-color:var(--secondary-hover);--color:var(--secondary-inverse)}:is(button,input[type=submit],input[type=button],[role=button]).secondary:focus,input[type=reset]:focus{--box-shadow:var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--outline-width) var(--secondary-focus)}:is(button,input[type=submit],input[type=button],[role=button]).contrast{--background-color:var(--contrast);--border-color:var(--contrast);--color:var(--contrast-inverse)}:is(button,input[type=submit],input[type=button],[role=button]).contrast:is([aria-current],:hover,:active,:focus){--background-color:var(--contrast-hover);--border-color:var(--contrast-hover);--color:var(--contrast-inverse)}:is(button,input[type=submit],input[type=button],[role=button]).contrast:focus{--box-shadow:var(--button-hover-box-shadow, 0 0 0 rgba(0, 0, 0, 0)),0 0 0 var(--outline-width) var(--contrast-focus)}:is(button,input[type=submit],input[type=button],[role=button]).outline,input[type=reset].outline{--background-color:transparent;--color:var(--primary)}:is(button,input[type=submit],input[type=button],[role=button]).outline:is([aria-current],:hover,:active,:focus),input[type=reset].outline:is([aria-current],:hover,:active,:focus){--background-color:transparent;--color:var(--primary-hover)}:is(button,input[type=submit],input[type=button],[role=button]).outline.secondary,input[type=reset].outline{--color:var(--secondary)}:is(button,input[type=submit],input[type=button],[role=button]).outline.secondary:is([aria-current],:hover,:active,:focus),input[type=reset].outline:is([aria-current],:hover,:active,:focus){--color:var(--secondary-hover)}:is(button,input[type=submit],input[type=button],[role=button]).outline.contrast{--color:var(--contrast)}:is(button,input[type=submit],input[type=button],[role=button]).outline.contrast:is([aria-current],:hover,:active,:focus){--color:var(--contrast-hover)}:where(button,[type=submit],[type=button],[type=reset],[role=button])[disabled],:where(fieldset[disabled]) :is(button,[type=submit],[type=button],[type=reset],[role=button]),a[role=button]:not([href]){opacity:.5;pointer-events:none}input,optgroup,select,textarea{margin:0;font-size:1rem;line-height:var(--line-height);font-family:inherit;letter-spacing:inherit}input{overflow:visible}select{text-transform:none}legend{max-width:100%;padding:0;color:inherit;white-space:normal}textarea{overflow:auto}[type=checkbox],[type=radio]{padding:0}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}::-moz-focus-inner{padding:0;border-style:none}:-moz-focusring{outline:0}:-moz-ui-invalid{box-shadow:none}::-ms-expand{display:none}[type=file],[type=range]{padding:0;border-width:0}input:not([type=checkbox],[type=radio],[type=range]){height:calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2)}fieldset{margin:0;margin-bottom:var(--spacing);padding:0;border:0}fieldset legend,label{display:block;margin-bottom:calc(var(--spacing) * .25);font-weight:var(--form-label-font-weight,var(--font-weight))}input:not([type=checkbox],[type=radio]),select,textarea{width:100%}input:not([type=checkbox],[type=radio],[type=range],[type=file]),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal)}input,select,textarea{--background-color:var(--form-element-background-color);--border-color:var(--form-element-border-color);--color:var(--form-element-color);--box-shadow:none;border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:0;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}:where(select,textarea):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[type=checkbox],[type=radio],[readonly]):is(:active,:focus){--background-color:var(--form-element-active-background-color)}:where(select,textarea):is(:active,:focus),input:not([type=submit],[type=button],[type=reset],[role=switch],[readonly]):is(:active,:focus){--border-color:var(--form-element-active-border-color)}input:not([type=submit],[type=button],[type=reset],[type=range],[type=file],[readonly]):focus,select:focus,textarea:focus{--box-shadow:0 0 0 var(--outline-width) var(--form-element-focus-color)}:where(fieldset[disabled]) :is(input:not([type=submit],[type=button],[type=reset]),select,textarea),input:not([type=submit],[type=button],[type=reset])[disabled],select[disabled],textarea[disabled]{--background-color:var(--form-element-disabled-background-color);--border-color:var(--form-element-disabled-border-color);opacity:var(--form-element-disabled-opacity);pointer-events:none}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week])[aria-invalid]{padding-right:calc(var(--form-element-spacing-horizontal) + 1.5rem)!important;padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal)!important;padding-inline-end:calc(var(--form-element-spacing-horizontal) + 1.5rem)!important;background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week])[aria-invalid=false]{background-image:var(--icon-valid)}:where(input,select,textarea):not([type=checkbox],[type=radio],[type=date],[type=datetime-local],[type=month],[type=time],[type=week])[aria-invalid=true]{background-image:var(--icon-invalid)}:where(input,select,textarea)[aria-invalid=false]{--border-color:var(--form-element-valid-border-color)}:where(input,select,textarea)[aria-invalid=false]:is(:active,:focus){--border-color:var(--form-element-valid-active-border-color)!important;--box-shadow:0 0 0 var(--outline-width) var(--form-element-valid-focus-color)!important}:where(input,select,textarea)[aria-invalid=true]{--border-color:var(--form-element-invalid-border-color)}:where(input,select,textarea)[aria-invalid=true]:is(:active,:focus){--border-color:var(--form-element-invalid-active-border-color)!important;--box-shadow:0 0 0 var(--outline-width) var(--form-element-invalid-focus-color)!important}[dir=rtl] :where(input,select,textarea):not([type=checkbox],[type=radio]):is([aria-invalid],[aria-invalid=true],[aria-invalid=false]){background-position:center left .75rem}input::-webkit-input-placeholder,input::placeholder,select:invalid,textarea::-webkit-input-placeholder,textarea::placeholder{color:var(--form-element-placeholder-color);opacity:1}input:not([type=checkbox],[type=radio]),select,textarea{margin-bottom:var(--spacing)}select::-ms-expand{border:0;background-color:transparent}select:not([multiple],[size]){padding-right:calc(var(--form-element-spacing-horizontal) + 1.5rem);padding-left:var(--form-element-spacing-horizontal);padding-inline-start:var(--form-element-spacing-horizontal);padding-inline-end:calc(var(--form-element-spacing-horizontal) + 1.5rem);background-image:var(--icon-chevron);background-position:center right .75rem;background-size:1rem auto;background-repeat:no-repeat}[dir=rtl] select:not([multiple],[size]){background-position:center left .75rem}:where(input,select,textarea,.grid)+small{display:block;width:100%;margin-top:calc(var(--spacing) * -.75);margin-bottom:var(--spacing);color:var(--muted-color)}label>:where(input,select,textarea){margin-top:calc(var(--spacing) * .25)}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:1.25em;height:1.25em;margin-top:-.125em;margin-right:.375em;margin-left:0;margin-inline-start:0;margin-inline-end:.375em;border-width:var(--border-width);font-size:inherit;vertical-align:middle;cursor:pointer}[type=checkbox]::-ms-check,[type=radio]::-ms-check{display:none}[type=checkbox]:checked,[type=checkbox]:checked:active,[type=checkbox]:checked:focus,[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--background-color:var(--primary);--border-color:var(--primary);background-image:var(--icon-checkbox);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=checkbox]~label,[type=radio]~label{display:inline-block;margin-right:.375em;margin-bottom:0;cursor:pointer}[type=checkbox]:indeterminate{--background-color:var(--primary);--border-color:var(--primary);background-image:var(--icon-minus);background-position:center;background-size:.75em auto;background-repeat:no-repeat}[type=radio]{border-radius:50%}[type=radio]:checked,[type=radio]:checked:active,[type=radio]:checked:focus{--background-color:var(--primary-inverse);border-width:.35em;background-image:none}[type=checkbox][role=switch]{--background-color:var(--switch-background-color);--border-color:var(--switch-background-color);--color:var(--switch-color);width:2.25em;height:1.25em;border:var(--border-width) solid var(--border-color);border-radius:1.25em;background-color:var(--background-color);line-height:1.25em}[type=checkbox][role=switch]:focus{--background-color:var(--switch-background-color);--border-color:var(--switch-background-color)}[type=checkbox][role=switch]:checked{--background-color:var(--switch-checked-background-color);--border-color:var(--switch-checked-background-color)}[type=checkbox][role=switch]:before{display:block;width:calc(1.25em - (var(--border-width) * 2));height:100%;border-radius:50%;background-color:var(--color);content:"";transition:margin .1s ease-in-out}[type=checkbox][role=switch]:checked{background-image:none}[type=checkbox][role=switch]:checked::before{margin-left:calc(1.125em - var(--border-width));margin-inline-start:calc(1.125em - var(--border-width))}[type=checkbox]:checked[aria-invalid=false],[type=checkbox][aria-invalid=false],[type=checkbox][role=switch]:checked[aria-invalid=false],[type=checkbox][role=switch][aria-invalid=false],[type=radio]:checked[aria-invalid=false],[type=radio][aria-invalid=false]{--border-color:var(--form-element-valid-border-color)}[type=checkbox]:checked[aria-invalid=true],[type=checkbox][aria-invalid=true],[type=checkbox][role=switch]:checked[aria-invalid=true],[type=checkbox][role=switch][aria-invalid=true],[type=radio]:checked[aria-invalid=true],[type=radio][aria-invalid=true]{--border-color:var(--form-element-invalid-border-color)}[type=color]::-webkit-color-swatch-wrapper{padding:0}[type=color]::-moz-focus-inner{padding:0}[type=color]::-webkit-color-swatch{border:0;border-radius:calc(var(--border-radius) * .5)}[type=color]::-moz-color-swatch{border:0;border-radius:calc(var(--border-radius) * .5)}input:not([type=checkbox],[type=radio],[type=range],[type=file]):is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){--icon-position:0.75rem;--icon-width:1rem;padding-right:calc(var(--icon-width) + var(--icon-position));background-image:var(--icon-date);background-position:center right var(--icon-position);background-size:var(--icon-width) auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=time]{background-image:var(--icon-time)}[type=date]::-webkit-calendar-picker-indicator,[type=datetime-local]::-webkit-calendar-picker-indicator,[type=month]::-webkit-calendar-picker-indicator,[type=time]::-webkit-calendar-picker-indicator,[type=week]::-webkit-calendar-picker-indicator{width:var(--icon-width);margin-right:calc(var(--icon-width) * -1);margin-left:var(--icon-position);opacity:0}[dir=rtl] :is([type=date],[type=datetime-local],[type=month],[type=time],[type=week]){text-align:right}@-moz-document url-prefix(){[type=date],[type=datetime-local],[type=month],[type=time],[type=week]{padding-right:var(--form-element-spacing-horizontal)!important;background-image:none!important}}[type=file]{--color:var(--muted-color);padding:calc(var(--form-element-spacing-vertical) * .5) 0;border:0;border-radius:0;background:0 0}[type=file]::file-selector-button{--background-color:var(--secondary);--border-color:var(--secondary);--color:var(--secondary-inverse);margin-right:calc(var(--spacing)/ 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing)/ 2);padding:calc(var(--form-element-spacing-vertical) * .5) calc(var(--form-element-spacing-horizontal) * .5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:0;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type=file]::file-selector-button:is(:hover,:active,:focus){--background-color:var(--secondary-hover);--border-color:var(--secondary-hover)}[type=file]::-webkit-file-upload-button{--background-color:var(--secondary);--border-color:var(--secondary);--color:var(--secondary-inverse);margin-right:calc(var(--spacing)/ 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing)/ 2);padding:calc(var(--form-element-spacing-vertical) * .5) calc(var(--form-element-spacing-horizontal) * .5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:0;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;-webkit-transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type=file]::-webkit-file-upload-button:is(:hover,:active,:focus){--background-color:var(--secondary-hover);--border-color:var(--secondary-hover)}[type=file]::-ms-browse{--background-color:var(--secondary);--border-color:var(--secondary);--color:var(--secondary-inverse);margin-right:calc(var(--spacing)/ 2);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing)/ 2);padding:calc(var(--form-element-spacing-vertical) * .5) calc(var(--form-element-spacing-horizontal) * .5);border:var(--border-width) solid var(--border-color);border-radius:var(--border-radius);outline:0;background-color:var(--background-color);box-shadow:var(--box-shadow);color:var(--color);font-weight:var(--font-weight);font-size:1rem;line-height:var(--line-height);text-align:center;cursor:pointer;-ms-transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition);transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}[type=file]::-ms-browse:is(:hover,:active,:focus){--background-color:var(--secondary-hover);--border-color:var(--secondary-hover)}[type=range]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:1.25rem;background:0 0}[type=range]::-webkit-slider-runnable-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);-webkit-transition:background-color var(--transition),box-shadow var(--transition);transition:background-color var(--transition),box-shadow var(--transition)}[type=range]::-moz-range-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);-moz-transition:background-color var(--transition),box-shadow var(--transition);transition:background-color var(--transition),box-shadow var(--transition)}[type=range]::-ms-track{width:100%;height:.25rem;border-radius:var(--border-radius);background-color:var(--range-border-color);-ms-transition:background-color var(--transition),box-shadow var(--transition);transition:background-color var(--transition),box-shadow var(--transition)}[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;-webkit-transition:background-color var(--transition),transform var(--transition);transition:background-color var(--transition),transform var(--transition)}[type=range]::-moz-range-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;-moz-transition:background-color var(--transition),transform var(--transition);transition:background-color var(--transition),transform var(--transition)}[type=range]::-ms-thumb{-webkit-appearance:none;width:1.25rem;height:1.25rem;margin-top:-.5rem;border:2px solid var(--range-thumb-border-color);border-radius:50%;background-color:var(--range-thumb-color);cursor:pointer;-ms-transition:background-color var(--transition),transform var(--transition);transition:background-color var(--transition),transform var(--transition)}[type=range]:focus,[type=range]:hover{--range-border-color:var(--range-active-border-color);--range-thumb-color:var(--range-thumb-hover-color)}[type=range]:active{--range-thumb-color:var(--range-thumb-active-color)}[type=range]:active::-webkit-slider-thumb{transform:scale(1.25)}[type=range]:active::-moz-range-thumb{transform:scale(1.25)}[type=range]:active::-ms-thumb{transform:scale(1.25)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem);border-radius:5rem;background-image:var(--icon-search);background-position:center left 1.125rem;background-size:1rem auto;background-repeat:no-repeat}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{padding-inline-start:calc(var(--form-element-spacing-horizontal) + 1.75rem)!important;background-position:center left 1.125rem,center right .75rem}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=false]{background-image:var(--icon-search),var(--icon-valid)}input:not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid=true]{background-image:var(--icon-search),var(--icon-invalid)}[type=search]::-webkit-search-cancel-button{-webkit-appearance:none;display:none}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search]{background-position:center right 1.125rem}[dir=rtl] :where(input):not([type=checkbox],[type=radio],[type=range],[type=file])[type=search][aria-invalid]{background-position:center right 1.125rem,center left .75rem}:where(table){width:100%;border-collapse:collapse;border-spacing:0;text-indent:0}td,th{padding:calc(var(--spacing)/ 2) var(--spacing);border-bottom:var(--border-width) solid var(--table-border-color);color:var(--color);font-weight:var(--font-weight);font-size:var(--font-size);text-align:left;text-align:start}tfoot td,tfoot th{border-top:var(--border-width) solid var(--table-border-color);border-bottom:0}table[role=grid] tbody tr:nth-child(odd){background-color:var(--table-row-stripped-background-color)}code,kbd,pre,samp{font-size:.875em;font-family:var(--font-family)}pre{-ms-overflow-style:scrollbar;overflow:auto}code,kbd,pre{border-radius:var(--border-radius);background:var(--code-background-color);color:var(--code-color);font-weight:var(--font-weight);line-height:initial}code,kbd{display:inline-block;padding:.375rem .5rem}pre{display:block;margin-bottom:var(--spacing);overflow-x:auto}pre>code{display:block;padding:var(--spacing);background:0 0;font-size:14px;line-height:var(--line-height)}code b{color:var(--code-tag-color);font-weight:var(--font-weight)}code i{color:var(--code-property-color);font-style:normal}code u{color:var(--code-value-color);text-decoration:none}code em{color:var(--code-comment-color);font-style:normal}kbd{background-color:var(--code-kbd-background-color);color:var(--code-kbd-color);vertical-align:baseline}hr{height:0;border:0;border-top:1px solid var(--muted-border-color);color:inherit}[hidden],template{display:none!important}canvas{display:inline-block}details{display:block;margin-bottom:var(--spacing);padding-bottom:var(--spacing);border-bottom:var(--border-width) solid var(--accordion-border-color)}details summary{line-height:1rem;list-style-type:none;cursor:pointer;transition:color var(--transition)}details summary:not([role]){color:var(--accordion-close-summary-color)}details summary::-webkit-details-marker{display:none}details summary::marker{display:none}details summary::-moz-list-bullet{list-style-type:none}details summary::after{display:block;width:1rem;height:1rem;margin-inline-start:calc(var(--spacing,1rem) * .5);float:right;transform:rotate(-90deg);background-image:var(--icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:"";transition:transform var(--transition)}details summary:focus{outline:0}details summary:focus:not([role=button]){color:var(--accordion-active-summary-color)}details summary[role=button]{width:100%;text-align:left}details summary[role=button]::after{height:calc(1rem * var(--line-height,1.5));background-image:var(--icon-chevron-button)}details summary[role=button]:not(.outline).contrast::after{background-image:var(--icon-chevron-button-inverse)}details[open]>summary{margin-bottom:calc(var(--spacing))}details[open]>summary:not([role]):not(:focus){color:var(--accordion-open-summary-color)}details[open]>summary::after{transform:rotate(0)}[dir=rtl] details summary{text-align:right}[dir=rtl] details summary::after{float:left;background-position:left center}article{margin:var(--block-spacing-vertical) 0;padding:var(--block-spacing-vertical) var(--block-spacing-horizontal);border-radius:var(--border-radius);background:var(--card-background-color);box-shadow:var(--card-box-shadow)}article>footer,article>header{margin-right:calc(var(--block-spacing-horizontal) * -1);margin-left:calc(var(--block-spacing-horizontal) * -1);padding:calc(var(--block-spacing-vertical) * .66) var(--block-spacing-horizontal);background-color:var(--card-sectionning-background-color)}article>header{margin-top:calc(var(--block-spacing-vertical) * -1);margin-bottom:var(--block-spacing-vertical);border-bottom:var(--border-width) solid var(--card-border-color);border-top-right-radius:var(--border-radius);border-top-left-radius:var(--border-radius)}article>footer{margin-top:var(--block-spacing-vertical);margin-bottom:calc(var(--block-spacing-vertical) * -1);border-top:var(--border-width) solid var(--card-border-color);border-bottom-right-radius:var(--border-radius);border-bottom-left-radius:var(--border-radius)}:root{--scrollbar-width:0px}dialog{display:flex;z-index:999;position:fixed;top:0;right:0;bottom:0;left:0;align-items:center;justify-content:center;width:inherit;min-width:100%;height:inherit;min-height:100%;padding:var(--spacing);border:0;-webkit-backdrop-filter:var(--modal-overlay-backdrop-filter);backdrop-filter:var(--modal-overlay-backdrop-filter);background-color:var(--modal-overlay-background-color);color:var(--color)}dialog article{max-height:calc(100vh - var(--spacing) * 2);overflow:auto}@media (min-width:576px){dialog article{max-width:510px}}@media (min-width:768px){dialog article{max-width:700px}}dialog article>footer,dialog article>header{padding:calc(var(--block-spacing-vertical) * .5) var(--block-spacing-horizontal)}dialog article>header .close{margin:0;margin-left:var(--spacing);float:right}dialog article>footer{text-align:right}dialog article>footer [role=button]{margin-bottom:0}dialog article>footer [role=button]:not(:first-of-type){margin-left:calc(var(--spacing) * .5)}dialog article p:last-of-type{margin:0}dialog article .close{display:block;width:1rem;height:1rem;margin-top:calc(var(--block-spacing-vertical) * -.5);margin-bottom:var(--typography-spacing-vertical);margin-left:auto;background-image:var(--icon-close);background-position:center;background-size:auto 1rem;background-repeat:no-repeat;opacity:.5;transition:opacity var(--transition)}dialog article .close:is([aria-current],:hover,:active,:focus){opacity:1}dialog:not([open]),dialog[open=false]{display:none}.modal-is-open{padding-right:var(--scrollbar-width,0);overflow:hidden;pointer-events:none;touch-action:none}.modal-is-open dialog{pointer-events:auto}:where(.modal-is-opening,.modal-is-closing) dialog,:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-duration:.2s;animation-timing-function:ease-in-out;animation-fill-mode:both}:where(.modal-is-opening,.modal-is-closing) dialog{animation-duration:.8s;animation-name:modal-overlay}:where(.modal-is-opening,.modal-is-closing) dialog>article{animation-delay:.2s;animation-name:modal}.modal-is-closing dialog,.modal-is-closing dialog>article{animation-delay:0s;animation-direction:reverse}@keyframes modal-overlay{from{-webkit-backdrop-filter:none;backdrop-filter:none;background-color:transparent}}@keyframes modal{from{transform:translateY(-100%);opacity:0}}:where(nav li)::before{float:left;content:"​"}nav,nav ul{display:flex}nav{justify-content:space-between}nav ol,nav ul{align-items:center;margin-bottom:0;padding:0;list-style:none}nav ol:first-of-type,nav ul:first-of-type{margin-left:calc(var(--nav-element-spacing-horizontal) * -1)}nav ol:last-of-type,nav ul:last-of-type{margin-right:calc(var(--nav-element-spacing-horizontal) * -1)}nav li{display:inline-block;margin:0;padding:var(--nav-element-spacing-vertical) var(--nav-element-spacing-horizontal)}nav li>*{--spacing:0}nav :where(a,[role=link]){display:inline-block;margin:calc(var(--nav-link-spacing-vertical) * -1) calc(var(--nav-link-spacing-horizontal) * -1);padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal);border-radius:var(--border-radius);text-decoration:none}nav :where(a,[role=link]):is([aria-current],:hover,:active,:focus){text-decoration:none}nav[aria-label=breadcrumb]{align-items:center;justify-content:start}nav[aria-label=breadcrumb] ul li:not(:first-child){margin-inline-start:var(--nav-link-spacing-horizontal)}nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{position:absolute;width:calc(var(--nav-link-spacing-horizontal) * 2);margin-inline-start:calc(var(--nav-link-spacing-horizontal)/ 2);content:"/";color:var(--muted-color);text-align:center}nav[aria-label=breadcrumb] a[aria-current]{background-color:transparent;color:inherit;text-decoration:none;pointer-events:none}nav [role=button]{margin-right:inherit;margin-left:inherit;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}aside li,aside nav,aside ol,aside ul{display:block}aside li{padding:calc(var(--nav-element-spacing-vertical) * .5) var(--nav-element-spacing-horizontal)}aside li a{display:block}aside li [role=button]{margin:inherit}[dir=rtl] nav[aria-label=breadcrumb] ul li:not(:last-child) ::after{content:"\\"}progress{display:inline-block;vertical-align:baseline}progress{-webkit-appearance:none;-moz-appearance:none;display:inline-block;appearance:none;width:100%;height:.5rem;margin-bottom:calc(var(--spacing) * .5);overflow:hidden;border:0;border-radius:var(--border-radius);background-color:var(--progress-background-color);color:var(--progress-color)}progress::-webkit-progress-bar{border-radius:var(--border-radius);background:0 0}progress[value]::-webkit-progress-value{background-color:var(--progress-color)}progress::-moz-progress-bar{background-color:var(--progress-color)}@media (prefers-reduced-motion:no-preference){progress:indeterminate{background:var(--progress-background-color) linear-gradient(to right,var(--progress-color) 30%,var(--progress-background-color) 30%) top left/150% 150% no-repeat;animation:progress-indeterminate 1s linear infinite}progress:indeterminate[value]::-webkit-progress-value{background-color:transparent}progress:indeterminate::-moz-progress-bar{background-color:transparent}}@media (prefers-reduced-motion:no-preference){[dir=rtl] progress:indeterminate{animation-direction:reverse}}@keyframes progress-indeterminate{0%{background-position:200% 0}100%{background-position:-200% 0}}details[role=list],li[role=list]{position:relative}details[role=list] summary+ul,li[role=list]>ul{display:flex;z-index:99;position:absolute;top:auto;right:0;left:0;flex-direction:column;margin:0;padding:0;border:var(--border-width) solid var(--dropdown-border-color);border-radius:var(--border-radius);border-top-right-radius:0;border-top-left-radius:0;background-color:var(--dropdown-background-color);box-shadow:var(--card-box-shadow);color:var(--dropdown-color);white-space:nowrap}details[role=list] summary+ul li,li[role=list]>ul li{width:100%;margin-bottom:0;padding:calc(var(--form-element-spacing-vertical) * .5) var(--form-element-spacing-horizontal);list-style:none}details[role=list] summary+ul li:first-of-type,li[role=list]>ul li:first-of-type{margin-top:calc(var(--form-element-spacing-vertical) * .5)}details[role=list] summary+ul li:last-of-type,li[role=list]>ul li:last-of-type{margin-bottom:calc(var(--form-element-spacing-vertical) * .5)}details[role=list] summary+ul li a,li[role=list]>ul li a{display:block;margin:calc(var(--form-element-spacing-vertical) * -.5) calc(var(--form-element-spacing-horizontal) * -1);padding:calc(var(--form-element-spacing-vertical) * .5) var(--form-element-spacing-horizontal);overflow:hidden;color:var(--dropdown-color);text-decoration:none;text-overflow:ellipsis}details[role=list] summary+ul li a:hover,li[role=list]>ul li a:hover{background-color:var(--dropdown-hover-background-color)}details[role=list] summary::after,li[role=list]>a::after{display:block;width:1rem;height:calc(1rem * var(--line-height,1.5));margin-inline-start:.5rem;float:right;transform:rotate(0);background-image:var(--icon-chevron);background-position:right center;background-size:1rem auto;background-repeat:no-repeat;content:""}details[role=list]{padding:0;border-bottom:none}details[role=list] summary{margin-bottom:0}details[role=list] summary:not([role]){height:calc(1rem * var(--line-height) + var(--form-element-spacing-vertical) * 2 + var(--border-width) * 2);padding:var(--form-element-spacing-vertical) var(--form-element-spacing-horizontal);border:var(--border-width) solid var(--form-element-border-color);border-radius:var(--border-radius);background-color:var(--form-element-background-color);color:var(--form-element-placeholder-color);line-height:inherit;cursor:pointer;transition:background-color var(--transition),border-color var(--transition),color var(--transition),box-shadow var(--transition)}details[role=list] summary:not([role]):active,details[role=list] summary:not([role]):focus{border-color:var(--form-element-active-border-color);background-color:var(--form-element-active-background-color)}details[role=list] summary:not([role]):focus{box-shadow:0 0 0 var(--outline-width) var(--form-element-focus-color)}details[role=list][open] summary{border-bottom-right-radius:0;border-bottom-left-radius:0}details[role=list][open] summary::before{display:block;z-index:1;position:fixed;top:0;right:0;bottom:0;left:0;background:0 0;content:"";cursor:default}nav details[role=list] summary,nav li[role=list] a{display:flex;direction:ltr}nav details[role=list] summary+ul,nav li[role=list]>ul{min-width:-moz-fit-content;min-width:fit-content;border-radius:var(--border-radius)}nav details[role=list] summary+ul li a,nav li[role=list]>ul li a{border-radius:0}nav details[role=list] summary,nav details[role=list] summary:not([role]){height:auto;padding:var(--nav-link-spacing-vertical) var(--nav-link-spacing-horizontal)}nav details[role=list][open] summary{border-radius:var(--border-radius)}nav details[role=list] summary+ul{margin-top:var(--outline-width);margin-inline-start:0}nav details[role=list] summary[role=link]{margin-bottom:calc(var(--nav-link-spacing-vertical) * -1);line-height:var(--line-height)}nav details[role=list] summary[role=link]+ul{margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc(var(--nav-link-spacing-horizontal) * -1)}li[role=list] a:active~ul,li[role=list] a:focus~ul,li[role=list]:hover>ul{display:flex}li[role=list]>ul{display:none;margin-top:calc(var(--nav-link-spacing-vertical) + var(--outline-width));margin-inline-start:calc(var(--nav-element-spacing-horizontal) - var(--nav-link-spacing-horizontal))}li[role=list]>a::after{background-image:var(--icon-chevron)}label>details[role=list]{margin-top:calc(var(--spacing) * .25);margin-bottom:var(--spacing)}[aria-busy=true]{cursor:progress}[aria-busy=true]:not(input,select,textarea,html)::before{display:inline-block;width:1em;height:1em;border:.1875em solid currentColor;border-radius:1em;border-right-color:transparent;content:"";vertical-align:text-bottom;vertical-align:-.125em;animation:spinner .75s linear infinite;opacity:var(--loading-spinner-opacity)}[aria-busy=true]:not(input,select,textarea,html):not(:empty)::before{margin-right:calc(var(--spacing) * .5);margin-left:0;margin-inline-start:0;margin-inline-end:calc(var(--spacing) * .5)}[aria-busy=true]:not(input,select,textarea,html):empty{text-align:center}a[aria-busy=true],button[aria-busy=true],input[type=button][aria-busy=true],input[type=reset][aria-busy=true],input[type=submit][aria-busy=true]{pointer-events:none}@keyframes spinner{to{transform:rotate(360deg)}}[data-tooltip]{position:relative}[data-tooltip]:not(a,button,input){border-bottom:1px dotted;text-decoration:none;cursor:help}[data-tooltip]::after,[data-tooltip]::before,[data-tooltip][data-placement=top]::after,[data-tooltip][data-placement=top]::before{display:block;z-index:99;position:absolute;bottom:100%;left:50%;padding:.25rem .5rem;overflow:hidden;transform:translate(-50%,-.25rem);border-radius:var(--border-radius);background:var(--tooltip-background-color);content:attr(data-tooltip);color:var(--tooltip-color);font-style:normal;font-weight:var(--font-weight);font-size:.875rem;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;opacity:0;pointer-events:none}[data-tooltip]::after,[data-tooltip][data-placement=top]::after{padding:0;transform:translate(-50%,0);border-top:.3rem solid;border-right:.3rem solid transparent;border-left:.3rem solid transparent;border-radius:0;background-color:transparent;content:"";color:var(--tooltip-background-color)}[data-tooltip][data-placement=bottom]::after,[data-tooltip][data-placement=bottom]::before{top:100%;bottom:auto;transform:translate(-50%,.25rem)}[data-tooltip][data-placement=bottom]:after{transform:translate(-50%,-.3rem);border:.3rem solid transparent;border-bottom:.3rem solid}[data-tooltip][data-placement=left]::after,[data-tooltip][data-placement=left]::before{top:50%;right:100%;bottom:auto;left:auto;transform:translate(-.25rem,-50%)}[data-tooltip][data-placement=left]:after{transform:translate(.3rem,-50%);border:.3rem solid transparent;border-left:.3rem solid}[data-tooltip][data-placement=right]::after,[data-tooltip][data-placement=right]::before{top:50%;right:auto;bottom:auto;left:100%;transform:translate(.25rem,-50%)}[data-tooltip][data-placement=right]:after{transform:translate(-.3rem,-50%);border:.3rem solid transparent;border-right:.3rem solid}[data-tooltip]:focus::after,[data-tooltip]:focus::before,[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}@media (hover:hover) and (pointer:fine){[data-tooltip]:hover::after,[data-tooltip]:hover::before,[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after,[data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::before{animation-duration:.2s;animation-name:tooltip-slide-top}[data-tooltip]:hover::after,[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover [data-tooltip]:focus::after{animation-name:tooltip-caret-slide-top}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:focus::before,[data-tooltip][data-placement=bottom]:hover::after,[data-tooltip][data-placement=bottom]:hover::before{animation-duration:.2s;animation-name:tooltip-slide-bottom}[data-tooltip][data-placement=bottom]:focus::after,[data-tooltip][data-placement=bottom]:hover::after{animation-name:tooltip-caret-slide-bottom}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:focus::before,[data-tooltip][data-placement=left]:hover::after,[data-tooltip][data-placement=left]:hover::before{animation-duration:.2s;animation-name:tooltip-slide-left}[data-tooltip][data-placement=left]:focus::after,[data-tooltip][data-placement=left]:hover::after{animation-name:tooltip-caret-slide-left}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:focus::before,[data-tooltip][data-placement=right]:hover::after,[data-tooltip][data-placement=right]:hover::before{animation-duration:.2s;animation-name:tooltip-slide-right}[data-tooltip][data-placement=right]:focus::after,[data-tooltip][data-placement=right]:hover::after{animation-name:tooltip-caret-slide-right}}@keyframes tooltip-slide-top{from{transform:translate(-50%,.75rem);opacity:0}to{transform:translate(-50%,-.25rem);opacity:1}}@keyframes tooltip-caret-slide-top{from{opacity:0}50%{transform:translate(-50%,-.25rem);opacity:0}to{transform:translate(-50%,0);opacity:1}}@keyframes tooltip-slide-bottom{from{transform:translate(-50%,-.75rem);opacity:0}to{transform:translate(-50%,.25rem);opacity:1}}@keyframes tooltip-caret-slide-bottom{from{opacity:0}50%{transform:translate(-50%,-.5rem);opacity:0}to{transform:translate(-50%,-.3rem);opacity:1}}@keyframes tooltip-slide-left{from{transform:translate(.75rem,-50%);opacity:0}to{transform:translate(-.25rem,-50%);opacity:1}}@keyframes tooltip-caret-slide-left{from{opacity:0}50%{transform:translate(.05rem,-50%);opacity:0}to{transform:translate(.3rem,-50%);opacity:1}}@keyframes tooltip-slide-right{from{transform:translate(-.75rem,-50%);opacity:0}to{transform:translate(.25rem,-50%);opacity:1}}@keyframes tooltip-caret-slide-right{from{opacity:0}50%{transform:translate(-.05rem,-50%);opacity:0}to{transform:translate(-.3rem,-50%);opacity:1}}[aria-controls]{cursor:pointer}[aria-disabled=true],[disabled]{cursor:not-allowed}[aria-hidden=false][hidden]{display:initial}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);position:absolute}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation}[dir=rtl]{direction:rtl}@media (prefers-reduced-motion:reduce){:not([aria-busy=true]),:not([aria-busy=true])::after,:not([aria-busy=true])::before{background-attachment:initial!important;animation-duration:1ms!important;animation-delay:-1ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-delay:0s!important;transition-duration:0s!important}} +/*# sourceMappingURL=pico.min.css.map */ \ No newline at end of file diff --git a/html/src/style.css b/html/src/style.css new file mode 100755 index 0000000..35e1263 --- /dev/null +++ b/html/src/style.css @@ -0,0 +1,293 @@ +@font-face { + font-family: "UbuntuMono"; + src: url(/src/UbuntuMono-R.ttf) format("truetype"); +} + +:root { + --topnavheight: 45px; + --sidenavwidth: 150px; + --popupBoxWidth: 300px; + + --background-0: #212529; + --background-1: #404040; + + --nav-color: rgba(0,0,0,0.3); + + --text-0: #242424; + --text-1: #d3d3d3; + + --text-black: #242424; + --text-white: var(--pico-primary-inverse); + + --success-1:#059100; + --warning-1:#ffdc3e; + --error-1: #b60f0f; + + --font: "UbuntuMono"; + --pico-font-family: UbuntuMono, regular; +} + +* { + font-family: var(--font) !important; +} + +body { + position:fixed; + right: 0; + height: 0; + overflow: scroll; + width: calc( 100vw - var(--sidenavwidth) + 32px ); + height: calc( 100vh - var(--topnavheight) + 32px ); +} + +.maindiv { + position: fixed; + top: var(--topnavheight); + left: var(--sidenavwidth); + width: calc(100% - var(--sidenavwidth)); + height: calc(100% - var(--topnavheight)); + padding: 5px; + overflow: scroll; +} + +.text-invert { + color: var(--pico-primary-inverse) !important; + font-family: var(--font); +} + +/* .text-black { + color: var(--text-black) !important; + font-family: var(--font); +} */ + +.half-left { + position: relative; + display: inline-block; + width: 45%; + margin-right: calc(4%); +} + +.half-right { + position: relative; + display: inline-block; + width: 44%; + margin-left: 4%; +} + + +.navbar { + position: fixed; + top:0; + left:0; + right:0; + height:var(--topnavheight); + padding:5px; + padding-left: calc(var(--sidenavwidth) + 10px); + z-index: 10; + background-color: var(--nav-color); +} + +.navbar ul { + position: fixed; + top: calc(var(--topnavheight) * 0.1); + padding: 0px; + /* height: var(--topnavheight); */ + height: calc(var(--topnavheight) * 0.8); + padding-right: calc(var(--topnavheight) * 2.8); + left: var(--sidenavwidth); + width: calc(100% - var(--sidenavwidth)); + overflow-x: scroll; + overflow-y: hidden; +} + +.navpanel { + display: flex; + flex-direction: column; + flex-shrink: 0; + font-size: 16px; + font-weight: 400; + line-height: 24px; + overflow: hidden; + padding-left: 14px; + + + position:fixed; + top: var(--topnavheight); + bottom:0; + left:0; + width: var(--sidenavwidth); + height:100%; + resize: left; + z-index: 20; + background-color: var(--nav-color); +} + +.navpanel details { + padding-bottom: 0px; + padding-left: 5px; + margin-bottom: 0px; +} + +.navpanel summary { + font-size: 15px; + padding-top: 10px; + padding-bottom: 10px; + margin-bottom: 0px !important; +} + +.navpanel summary:hover { + color: var(--primary); + background-color: rgba(0,0,0,0.1); +} +.navpanel ul { + margin-top: 10px; + margin-bottom: 10px; + padding-left: 5px; +} + + +.navpanel li { + font-size: 14px; + margin-left: -5px; + margin-bottom: 0; + cursor: pointer; +} + +.navpanel li:hover { + color: var(--primary); + background-color: rgba(0,0,0,0.1); +} + +li::marker { + content: "- "; + padding-left: -20px; + padding-right: 5px; + } + + +.navconntext { + position: fixed; + font-size: calc(var(--topnavheight) * 0.3); + margin: calc((var(--topnavheight) * 0.25 * 0.75) - 5px); + background-color: rgba(255, 0, 0, 0.2); + border-style: solid; + border-width: 1px !important; + border-color: #ff0000; + padding: 8px; + right:0; + top:0; +} + +.sidenav-button { + width:100%; + margin-bottom: 10px; +} + +.topnav-button { + margin-left: 13px !important; + height: 100% !important; + + font-size: calc( var(--topnavheight) * ( 3 / 9 ) ); + line-height: calc( var(--topnavheight) * ( 3 / 9 ) ); + +} + +.navTitle { + position: fixed; + margin-bottom: 1px; + top: 0; + left: 0; + width: var(--sidenavwidth); + height: var(--topnavheight); + + text-align:center; + line-height: var(--topnavheight); + + text-decoration:none; +} + +.navTitle:hover { + background-color: rgba(0,0,0,0.1); +} + + +.popupBox { + position: fixed; + + /* display: flex; + box-sizing: content-box; + justify-content: flex-end; + flex-direction: column; */ + + width: var(--popupBoxWidth); + height: calc(100% - var(--topnavheight) - 10px); + pointer-events: none; + bottom: 0; + right: 20px; + overflow: auto; +} + +.popupBox dialog { + display: flex; + z-index: inherit; + position: relative; + top: inherit; + right: inherit; + bottom: inherit; + left: inherit; + align-items: inherit; + justify-content: inherit; + width: inherit; + min-width: inherit; + height: auto; + min-height: inherit; + padding: 0; + background-color: inherit; + + pointer-events: all; + + left: 0; + margin: 0; + margin-bottom: 20px; + + justify-content:right; + flex-direction: column; +} + +.popupBox article { + margin: 0; + opacity: 0.5; + padding-bottom: 10px; +} + +.popupBox header { + border: 0; + padding-left: 15px; + padding-right: 15px; + padding-top: 10px; + padding-bottom: 10px; + margin-bottom: 10px; +} + +.popupBox article:hover { + opacity: 1; +} + +dialog article a { + cursor: pointer; +} + + +.popupBox article p { + font-size: 15px; +} + +.noselect { + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently + supported by Chrome, Edge, Opera and Firefox */ + } \ No newline at end of file diff --git a/html/src/utils.js b/html/src/utils.js new file mode 100755 index 0000000..870b6e0 --- /dev/null +++ b/html/src/utils.js @@ -0,0 +1,274 @@ +export const getel = (id)=>{return document.getElementById(id)} + +export const setCookie = (name, value, hours = 1, path = '/') => { + const expires = new Date(Date.now() + hours * 6e4).toUTCString() + document.cookie = `${name}=${encodeURIComponent(value)}; path=${path}; SameSite=None; secure=True; session=True` +} + +export const getCookie = (name) => { + return document.cookie.split('; ').reduce((r, v) => { + const parts = v.split('=') + return parts[0] === name ? decodeURIComponent(parts[1]) : r + }, '') +} + +export function genID(length = 8){ + // Declare all characters + let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + // Pick characers randomly + let str = ''; + for (let i = 0; i < length; i++) { + str += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return str; +} + + +export function icondisconnect() { + let icon = document.getElementById('connecticon') + icon.style.backgroundColor = "rgba(255, 0, 0, 0.2)" + icon.style.borderColor = "#ff0000" + icon.innerText = "Disconnected" +} + +export function iconunauth() { + let icon = document.getElementById('connecticon') + icon.style.backgroundColor = "rgba(255, 255, 0, 0.2)" + icon.style.borderColor = "#ffff00" + icon.innerText = "Unauthenticated" +} + +export function iconauth() { + let icon = document.getElementById('connecticon') + icon.style.backgroundColor = "rgba(0, 255, 0, 0.2)" + icon.style.borderColor = "#00ff00" + icon.innerText = "Authenticated" +} + +// function addPopup(bgcolor, fgcolor, innerHTML) { +// const elem = document.getElementById('popupBox') +// const id = 'popup-'+genID(16) +// elem.innerHTML = `` + +// elem.innerHTML + +// setTimeout(()=>{ +// elem.parentNode.removeChild(elem) +// }, 30000) + +// } + +export function getatribinarr(arr, atribname, value){ + for(let i=0;i +
+
+

${formatTime(getUnixTime())}

+ + ${title} +
+ ${innerHTML} +
+ + ` +} + +export function addPopup(bgcolor, isDark, title, content) { + const elem = document.getElementById('popupBox') + let header + let textColor + if(isDark){ + header = 'rgba(255,255,255,0.05)' + textColor = "text-invert" + }else{ + header = 'rgba(0,0,0,0.2)' + textColor = "" + } + modal(elem, bgcolor, header, textColor, title, `

${content}

`) + +} + +export function popupInfo(title, text){ + addPopup('var(--card-sectionning-background-color)', true, title, text) +} + +export function popupSuccess(title, text){ + addPopup('#005000', true, title, text) +} + +export function popupWarning(title, text){ + addPopup('#393900', true, title, text) +} + +export function popupError(title, text){ + addPopup('#500000', true, title, text) +} + +export function confirmBox(bgcolor, isDark, title, yesFunc, noFunc) { + const elem = document.body + let header + let textColor + if(isDark){ + header = 'rgba(255,255,255,0.05)' + textColor = "text-invert" + }else{ + header = 'rgba(0,0,0,0.2)' + textColor = "" + } + modal(elem, bgcolor, header, textColor, title, ` + + `) +} + +export function getUnixTime() { + return (+ new Date()) +} + +export function formatTime(Millis){ + const date = new Date(Millis) + + if(date.getDate() != (new Date()).getDate()){ + return date.getMonth()+1 + "/" + date.getDate() + "/" + date.getFullYear() + }else{ + var Hour = "" + var Minute = "" + var AmPm = "" + + if(date.getHours() == 0){ + Hour = "12" + AmPm = "AM" + }else if(date.getHours() < 12){ + Hour = date.getHours() + AmPm = "AM" + }else if(date.getHours() == 12){ + Hour = "12" + AmPm = "PM" + }else{ + Hour = date.getHours() - 12 + AmPm = "PM" + } + + if(date.getMinutes() < 10){ + Minute = "0" + date.getMinutes() + }else{ + Minute = date.getMinutes() + } + + return Hour + ":" + Minute + " " + AmPm + + } +} + +export function sha256(ascii) { + function rightRotate(value, amount) { + return (value>>>amount) | (value<<(32 - amount)); + }; + + var mathPow = Math.pow; + var maxWord = mathPow(2, 32); + var lengthProperty = 'length' + var i, j; // Used as a counter across the whole file + var result = '' + + var words = []; + var asciiBitLength = ascii[lengthProperty]*8; + + //* caching results is optional - remove/add slash from front of this line to toggle + // Initial hash value: first 32 bits of the fractional parts of the square roots of the first 8 primes + // (we actually calculate the first 64, but extra values are just ignored) + var hash = sha256.h = sha256.h || []; + // Round constants: first 32 bits of the fractional parts of the cube roots of the first 64 primes + var k = sha256.k = sha256.k || []; + var primeCounter = k[lengthProperty]; + /*/ + var hash = [], k = []; + var primeCounter = 0; + //*/ + + var isComposite = {}; + for (var candidate = 2; primeCounter < 64; candidate++) { + if (!isComposite[candidate]) { + for (i = 0; i < 313; i += candidate) { + isComposite[i] = candidate; + } + hash[primeCounter] = (mathPow(candidate, .5)*maxWord)|0; + k[primeCounter++] = (mathPow(candidate, 1/3)*maxWord)|0; + } + } + + ascii += '\x80' // Append Ƈ' bit (plus zero padding) + while (ascii[lengthProperty]%64 - 56) ascii += '\x00' // More zero padding + for (i = 0; i < ascii[lengthProperty]; i++) { + j = ascii.charCodeAt(i); + if (j>>8) return; // ASCII check: only accept characters in range 0-255 + words[i>>2] |= j << ((3 - i)%4)*8; + } + words[words[lengthProperty]] = ((asciiBitLength/maxWord)|0); + words[words[lengthProperty]] = (asciiBitLength) + + // process each chunk + for (j = 0; j < words[lengthProperty];) { + var w = words.slice(j, j += 16); // The message is expanded into 64 words as part of the iteration + var oldHash = hash; + // This is now the undefinedworking hash", often labelled as variables a...g + // (we have to truncate as well, otherwise extra entries at the end accumulate + hash = hash.slice(0, 8); + + for (i = 0; i < 64; i++) { + var i2 = i + j; + // Expand the message into 64 words + // Used below if + var w15 = w[i - 15], w2 = w[i - 2]; + + // Iterate + var a = hash[0], e = hash[4]; + var temp1 = hash[7] + + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) // S1 + + ((e&hash[5])^((~e)&hash[6])) // ch + + k[i] + // Expand the message schedule if needed + + (w[i] = (i < 16) ? w[i] : ( + w[i - 16] + + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15>>>3)) // s0 + + w[i - 7] + + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2>>>10)) // s1 + )|0 + ); + // This is only used once, so *could* be moved below, but it only saves 4 bytes and makes things unreadble + var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) // S0 + + ((a&hash[1])^(a&hash[2])^(hash[1]&hash[2])); // maj + + hash = [(temp1 + temp2)|0].concat(hash); // We don't bother trimming off the extra ones, they're harmless as long as we're truncating when we do the slice() + hash[4] = (hash[4] + temp1)|0; + } + + for (i = 0; i < 8; i++) { + hash[i] = (hash[i] + oldHash[i])|0; + } + } + + for (i = 0; i < 8; i++) { + for (j = 3; j + 1; j--) { + var b = (hash[i]>>(j*8))&255; + result += ((b < 16) ? 0 : '') + b.toString(16); + } + } + return result.toUpperCase(); +}; diff --git a/libs/scanner.py b/libs/scanner.py new file mode 100644 index 0000000..ca8219a --- /dev/null +++ b/libs/scanner.py @@ -0,0 +1,68 @@ +import subprocess +import random +import socket +import struct +from threading import Thread + +import src.utils as utils + +threads = [] + +def start(settings): + global threads + utils.makeDir("data/scans") + + + for i in range(0,settings['numJobs']): + c = ScanTask() + t = Thread(target = c.run, args=(settings['maxPingTimeout'],)) + t.start() + +# def getStdout(): +# global process +# return subprocess.check_output(process).decode() + # return "eee" + process.stdout.readline() + + +def stop(): + global threads + for thread in threads: + thread.stop() + threads = [] + print("\n\nstopped Scanner!") + + +def processStarted(): + global threads + return len(threads) != 0; + + + + + +class ScanTask: + def __init__(self): + self.running = True + + def stop(self): + self.running = False + + def run(self, maxPingTimeout): + while True: + address = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) + + pingCommand = f"ping {address} -c 1 -W {maxPingTimeout}" + + try: + subprocess.check_output(pingCommand.split(" ")) + # print(f"{address}: FOUND") + except subprocess.CalledProcessError: + # print(f"{address}: FAIL") + continue + + nmapCommand = f"sudo nmap {address} -O --send-eth --privileged -v -sS" + + try: + print(subprocess.check_output(nmapCommand.split(" ")).decode()) + except subprocess.CalledProcessError: + continue \ No newline at end of file diff --git a/libs/scanutils.py b/libs/scanutils.py new file mode 100644 index 0000000..d58d9fa --- /dev/null +++ b/libs/scanutils.py @@ -0,0 +1,12 @@ +import src.utils as utils + +def countScannedIps(): + files = utils.listSubdirs("data/") + count = 0 + for file in files: + if file.split("-")[0] != "scan": + continue + with open('data/'+file) as f: + #Count lines in scan files, Masscan has a 2 line header, so hence -2 + count += sum(1 for _ in f)-2 + return count \ No newline at end of file diff --git a/libs/test.py b/libs/test.py new file mode 100644 index 0000000..da9a064 --- /dev/null +++ b/libs/test.py @@ -0,0 +1,45 @@ +import subprocess +import random +import socket +import struct +from threading import Thread + +maxPingTimeout = 3 + +class ScanTask: + def __init__(self): + self.running = True + + def terminate(self): + self.running = False + + def run(self): + while True: + address = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) + + pingCommand = f"ping {address} -c 1 -W {maxPingTimeout}" + + try: + subprocess.check_output(pingCommand.split(" ")) + # print(f"{address}: FOUND") + except subprocess.CalledProcessError: + # print(f"{address}: FAIL") + continue + + nmapCommand = f"sudo nmap {address} -O --send-eth --privileged -v -sS" + + try: + print(subprocess.check_output(nmapCommand.split(" ")).decode()) + except subprocess.CalledProcessError: + continue + +threads = [] + +for i in range(0,500): + c = ScanTask() + t = Thread(target = c.run) + t.start() + # threads.push(c) + +for thread in threads: + thread.join() \ No newline at end of file diff --git a/main.py b/main.py new file mode 100755 index 0000000..eb8ddaa --- /dev/null +++ b/main.py @@ -0,0 +1,110 @@ +import src.web as web +import src.utils as utils +import src.modules as modules +from sys import argv + +webserv = web.webserv() +moduleMaster = modules.moduleMaster() + + +def main(): + + if not utils.pathExists('data'): + utils.makeDir('data') + + if not utils.pathExists('data/creds.json'): + if input("No credentials file was found, \nwould you like to create one? (Y/n): ").lower() in ["yes", "y", ""]: + utils.writeFile('data/creds.json', utils.genDefaultAccounts()) + + if webserv.secure and ( not utils.pathExists('data/ssl.crt') or not utils.pathExists('data/ssl.key') ): + if input("No ssl key/cert was found, \nwould you like to create them? (Y/n): ").lower() in ["yes", "y", ""]: + + if not utils.pathExists('data/ssl.key'): + utils.genKey(utils.getRoot('data/')) + + if not utils.pathExists('data/ssl.crt'): + utils.genCert(utils.getRoot('data/')) + + moduleMaster.addModules(webserv) + webserv.init() + moduleMaster.initModules(webserv) + webserv.start() + moduleMaster.runModules() + + # for m in modules: + # m.module.main() + +def printHelp(): + print(""" + Modulator usage: + + -h -? --help - Print this help information + -v --verbose - Print verbose information, default: false + -u --unsecure - Use http instead of https, default: false + -p --port - Set port of the webserver, default: 80 or 44 + -h --host - Set host of the webserver, default: 0.0.0.0 + + --defaultTab - Set the default tab on visit, default: 'main' + --title - Set the title of the html pages, default: 'Modulator' + + Examples: + + $ python3 ./main.py + $ python3 ./main.py -vo 127.0.0.1 + $ python3 ./main.py -p 12345 -h 192.168.0.123 + """) + +if __name__ == '__main__': + + i = 1 + + while i < len(argv): + arg = argv[i] + sargs = list(arg) + if sargs[0] == '-' and sargs[1] == '-': + match arg: + case '--help': + printHelp() + exit() + case '--verbose': + webserv.verbose = True + i+=1; continue + case '--unsecure': + webserv.secure = False + if webserv.port == 443: + webserv.port = 80 + i+=1; continue + case '--port': + webserv.port = int(argv[i+1]) + i+=2; continue + case '--host': + webserv.host = str(argv[i+1]) + i+=2; continue + case '--defaultTab': + webserv.defaultTab = str(argv[i+1]) + i+=2; continue + case '--title': + webserv.title = str(argv[i+1]) + i+=2; continue + elif sargs[0] == '-': + for sarg in sargs: + match sarg: + case 'h' | '?': + printHelp() + exit() + case 'v': + webserv.verbose = True + case 'u': + webserv.secure = False + if webserv.port == 443: + webserv.port = 80 + case 'p': + webserv.port = int(argv[i+1]) + i+=1; continue + case 'o': + webserv.host = str(argv[i+1]) + i+=1; continue + + i += 1 + + main() diff --git a/modules/main/Dashboard.html b/modules/main/Dashboard.html new file mode 100755 index 0000000..ca8e801 --- /dev/null +++ b/modules/main/Dashboard.html @@ -0,0 +1,19 @@ + + +
+
+ + \ No newline at end of file diff --git a/modules/main/adminSettings.html b/modules/main/adminSettings.html new file mode 100755 index 0000000..8bcd8a5 --- /dev/null +++ b/modules/main/adminSettings.html @@ -0,0 +1,248 @@ +
+

User Settings

+

Users

+ + + + + + + + + + + +
UsernameCreatedPass UpdatedGroupsManage
+

Sessions

+ + + + + + + + + + + +
UsernameAddressPathExpiresManage
+ +
+ \ No newline at end of file diff --git a/modules/main/main.py b/modules/main/main.py new file mode 100755 index 0000000..aa6a990 --- /dev/null +++ b/modules/main/main.py @@ -0,0 +1,206 @@ +import libs.scanutils as scanutils +mm = None + +def dashboardMetrics(ac): + ac.send('Scanner-Metrics', { + "scanCount": scanutils.countScannedIps() + }) + +def init(moduleMaster): + global mm + mm = moduleMaster + + mm.addPageEventListener('/main/dashboard', dashboardMetrics) + + # User settings + mm.addAuthEventListener('logout', logout) + mm.addAuthEventListener('unauth', unauth) + + mm.addAuthEventListener('passwordChangeRequest', changePassword) + + # Admin settings + mm.addAuthEventListener('addUserRequest', addUser) + mm.addAuthEventListener('disconnectAllSessions', disconnectAllSessions) + mm.addAuthEventListener('changeGroupsRequest', changeGroups) + mm.addAuthEventListener('deleteUserRequest', deleteUser) + # mm.addAuthEventListener('login', disconnectAllSessions) + + mm.addPageEventListener('/main/User', loadSessions) + mm.addPageEventListener('/main/Admin', loadSessionsAdmin) + + + +def main(): + pass + + + +def logout(ac, data): + mm.unauth(ac) + + + +def unauth(ac, data): + removeClient = mm.getAuthClientByID(data['data']) + if removeClient == None: + return + if removeClient.user != ac.user and not mm.userInGroup(ac, "Admins"): + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + mm.unauth(removeClient) + mm.sendPopupSuccess(ac.rawClient, "Success", "Client removed!") + if(ac.currentPage == "/main/Admin" and mm.userInGroup(ac, "Admins")): + loadSessionsAdmin(ac) + else: + loadSessions(ac) + + + +def loadSessions(ac): + obj = [] + for client in mm.authServer.clients: + if client.user != ac.user: + continue + obj.append({ + 'username': client.username, + 'address': client.rawClient.address, + 'currentPage': client.currentPage, + 'clientid': client.rawClient.clientid, + 'timeout': client.timeout + }) + # obj.append(client.session) + ac.send('sessions', obj) + + + +def changePassword(ac, data): + # If the account is not an admin, and the username is the same, and the password is correct => Change password + # If the account is not an admin, and the username is the same, and the password not correct and => Incorrect Password + # If the account is not an admin, and the username is not the same => Access denied + # If the account is an admin, and the username is the same, and the password is correct => Change password + # If the account is an admin, and the username is the same, and the password is not correct => Incorrect Password + # If the account is an admin, and the username is not the same => Change password + + isAdmin = mm.userInGroup(ac, 'Admins') + correctName = ac.user.id == data['data']['id'] + + + if isAdmin and correctName and not 'old' in data['data']: + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + + if isAdmin or correctName: + if not isAdmin and ac.user.sha256passwordhash != data['data']['old']: + mm.sendPopupError(ac.rawClient, "Error", "Incorrect Password") + return + elif isAdmin and correctName and ac.user.sha256passwordhash != data['data']['old']: + mm.sendPopupError(ac.rawClient, "Error", "Incorrect Password") + return + else: + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + user = mm.getUserById(data['data']['id']) + if user == None: + mm.sendPopupError(ac.rawClient, "Error", "Invalid id") + return + + mm.setUserPassword(user, data['data']['new']) + mm.sendPopupSuccess(ac.rawClient, "Success", "Password updated!") + + if isAdmin: + loadSessionsAdmin(ac) + + + +def loadSessionsAdmin(ac): + if not mm.userInGroup(ac, 'Admins'): + return + + obj = { + 'users': [], + 'sessions': [] + } + for client in mm.authServer.clients: + obj['sessions'].append({ + 'username': client.username, + 'address': client.rawClient.address, + 'currentPage': client.currentPage, + 'clientid': client.rawClient.clientid, + 'timeout': client.timeout + }) + for user in mm.authServer.users: + obj['users'].append({ + 'username': user.username, + 'permGroups': user.permGroups, + 'id': user.id, + 'created': user.created, + 'passwordUpdated': user.passwordUpdated + }) + ac.send('sessions', obj) + + + +def addUser(ac, data): + if not mm.userInGroup(ac, 'Admins'): + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + mm.addUser( + data['data']['username'], + data['data']['groups'], + data['data']['password']) + loadSessionsAdmin(ac) + + + +def disconnectAllSessions(ac, data): + if not mm.userInGroup(ac, 'Admins'): + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + user = mm.getUserById(data['data']['id']) + + for client in mm.authServer.clients: + if client.user == user: + mm.unauth(client) + loadSessionsAdmin(ac) + + + +def changeGroups(ac, data): + if not mm.userInGroup(ac, 'Admins'): + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + user = mm.getUserById(data['data']['id']) + if user == None: + mm.sendPopupError(ac.rawClient, "Error", "Invalid id") + return + if user == ac.user: + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + mm.setUserGroups(user, data['data']['groups']) + mm.sendPopupSuccess(ac.rawClient, "Success", "Groups updated!") + loadSessionsAdmin(ac) + + + +def deleteUser(ac, data): + if not mm.userInGroup(ac, 'Admins'): + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + user = mm.getUserById(data['data']['id']) + if user == None: + mm.sendPopupError(ac.rawClient, "Error", "Invalid id") + return + if user == ac.user: + mm.sendPopupError(ac.rawClient, "Error", "You are not authorised") + return + + mm.deleteUser(user) + mm.sendPopupSuccess(ac.rawClient, "Success", "User deleted!") + loadSessionsAdmin(ac) \ No newline at end of file diff --git a/modules/main/module.json b/modules/main/module.json new file mode 100755 index 0000000..d828bfa --- /dev/null +++ b/modules/main/module.json @@ -0,0 +1,38 @@ +{ + "name": "main", + "creators": ["ASTATIN3"], + "version": "1.0", + "entrypoint": "modules/main/main.py", + "tabs": [ + { + "name": "main", + "defaultPage": "dashboard", + "pages": [ + { + "type": "page", + "name": "dashboard", + "requiredPermGroup": "", + "location": "modules/main/Dashboard.html" + }, + { + "type": "folder", + "name": "Settings", + "pages": [ + { + "type": "page", + "name": "User", + "requiredPermGroup": "", + "location": "modules/main/userSettings.html" + }, + { + "type": "page", + "name": "Admin", + "requiredPermGroup": "Admins", + "location": "modules/main/adminSettings.html" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/main/userSettings.html b/modules/main/userSettings.html new file mode 100755 index 0000000..9cf6c49 --- /dev/null +++ b/modules/main/userSettings.html @@ -0,0 +1,105 @@ +
+

User Settings

+
+

Sessions

+ + + + + + + + + + + +
UsernameAddressPathExpiresManage
+ + +
+ \ No newline at end of file diff --git a/modules/scan/main.py b/modules/scan/main.py new file mode 100755 index 0000000..15e6134 --- /dev/null +++ b/modules/scan/main.py @@ -0,0 +1,46 @@ +import libs.scanner as scan +import time + +mm = None + + +def loadSettings(ac): + ac.send('Scanner-Settings', mm.vars['Scanner-Settings']) + + +def setSettings(ac, data): + mm.vars['Scanner-Settings'] = data['data'] + + +def startScanner(ac, data): + scan.start(mm.vars['Scanner-Settings']) + + +def stopScanner(ac, data): + scan.stop() + + + +def init(moduleMaster): + global mm + mm = moduleMaster + + mm.vars['Scanner-Settings'] = { + "range": [[0,0,0,0], [255,255,255,255]], + "numJobs": 50, + "maxPingTimeout": 3, + "output": "./data/scan.txt" + } + + mm.addPageEventListener('Scanner-LoadSettings', loadSettings) + + mm.addAuthEventListener('Scanner-StartScanner', startScanner) + mm.addAuthEventListener('Scanner-StopScanner', stopScanner) + +def main(): + while True: + if scan.processStarted(): + print("eee") + # print(scan.getStdout()) + # print("eee") + time.sleep(1) \ No newline at end of file diff --git a/modules/scan/module.json b/modules/scan/module.json new file mode 100755 index 0000000..6edda6e --- /dev/null +++ b/modules/scan/module.json @@ -0,0 +1,20 @@ +{ + "name": "Scan", + "creators": ["ASTATIN3"], + "version": "1.0", + "entrypoint": "modules/scan/main.py", + "tabs": [ + { + "name": "Scan", + "defaultPage": "Scan", + "pages": [ + { + "type": "page", + "name": "Scan", + "requiredPermGroup": "", + "location": "modules/scan/scan.html" + } + ] + } + ] +} \ No newline at end of file diff --git a/modules/scan/scan.html b/modules/scan/scan.html new file mode 100755 index 0000000..e695c4e --- /dev/null +++ b/modules/scan/scan.html @@ -0,0 +1,23 @@ + +
+

This is a very simple example module!

+ + +
+ + \ No newline at end of file diff --git a/paused.conf b/paused.conf new file mode 100644 index 0000000..62b2b0c --- /dev/null +++ b/paused.conf @@ -0,0 +1,150 @@ + +# resume information +resume-index = 275723 +seed = 11690600974265531384 +rate = 1000 +shard = 1/1 +nocapture = servername + +output-filename = ./data/scan-1713225964108.txt +output-format = grepable + +adapter-ip = 192.168.0.241 +# TARGET SELECTION (IP, PORTS, EXCLUDES) +ports = 1,3-4,6-7,9,13,17,19-26,30,32-33,37,42-43,49,53,70,79-85,88-90,99-100,106,109-111,113,119,125,135,139,143-144,146,161,163,179,199,211-212,222,254-256,259,264,280,301,306,311,340,366,389,406-407,416-417,425,427,443-445,458,464-465,481,497,500,512-515,524,541,543-545,548,554-555,563,587,593,616-617,625,631,636,646,648,666-668,683,687,691,700,705,711,714,720,722,726,749,765,777,783,787,800-801,808,843,873,880,888,898,900-903,911-912,981,987,990,992-993,995,999-1002,1007,1009-1011,1021-1100,1102,1104-1108,1110-1114,1117,1119,1121-1124,1126,1130-1132,1137-1138,1141,1145,1147-1149,1151-1152,1154,1163-1166,1169,1174-1175,1183,1185-1187,1192,1198-1199,1201,1213,1216-1218,1233-1234,1236,1244,1247-1248,1259,1271-1272,1277,1287,1296,1300-1301,1309-1311,1322,1328,1334,1352,1417,1433-1434,1443,1455,1461,1494,1500-1501,1503,1521,1524,1533,1556,1580,1583,1594,1600,1641,1658,1666,1687-1688,1700,1717-1721,1723,1755,1761,1782-1783,1801,1805,1812,1839-1840,1862-1864,1875,1900,1914,1935,1947,1971-1972,1974,1984,1998-2010,2013,2020-2022,2030,2033-2035,2038,2040-2043,2045-2049,2065,2068,2099-2100,2103,2105-2107,2111,2119,2121,2126,2135,2144,2160-2161,2170,2179,2190-2191,2196,2200,2222,2251,2260,2288,2301,2323,2366,2381-2383,2393-2394,2399,2401,2492,2500,2522,2525,2557,2601-2602,2604-2605,2607-2608,2638,2701-2702,2710,2717-2718,2725,2800,2809,2811,2869,2875,2909-2910,2920,2967-2968,2998,3000-3001,3003,3005-3007,3011,3013,3017,3030-3031,3052,3071,3077,3128,3168,3211,3221,3260-3261,3268-3269,3283,3300-3301,3306,3322-3325,3333,3351,3367,3369-3372,3389-3390,3404,3476,3493,3517,3527,3546,3551,3580,3659,3689-3690,3703,3737,3766,3784,3800-3801,3809,3814,3826-3828,3851,3869,3871,3878,3880,3889,3905,3914,3918,3920,3945,3971,3986,3995,3998,4000-4006,4045,4111,4125-4126,4129,4224,4242,4279,4321,4343,4443-4446,4449,4550,4567,4662,4848,4899-4900,4998,5000-5004,5009,5030,5033,5050-5051,5054,5060-5061,5080,5087,5100-5102,5120,5190,5200,5214,5221-5222,5225-5226,5269,5280,5298,5357,5405,5414,5431-5432,5440,5500,5510,5544,5550,5555,5560,5566,5631,5633,5666,5678-5679,5718,5730,5800-5802,5810-5811,5815,5822,5825,5850,5859,5862,5877,5900-5904,5906-5907,5910-5911,5915,5922,5925,5950,5952,5959-5963,5987-5989,5998-6007,6009,6025,6059,6100-6101,6106,6112,6123,6129,6156,6346,6389,6502,6510,6543,6547,6565-6567,6580,6646,6666-6669,6689,6692,6699,6779,6788-6789,6792,6839,6881,6901,6969,7000-7002,7004,7007,7019,7025,7070,7100,7103,7106,7200-7201,7402,7435,7443,7496,7512,7625,7627,7676,7741,7777-7778,7800,7911,7920-7921,7937-7938,7999-8002,8007-8011,8021-8022,8031,8042,8045,8080-8090,8093,8099-8100,8180-8181,8192-8194,8200,8222,8254,8290-8292,8300,8333,8383,8400,8402,8443,8500,8600,8649,8651-8652,8654,8701,8800,8873,8888,8899,8994,9000-9003,9009-9011,9040,9050,9071,9080-9081,9090-9091,9099-9103,9110-9111,9200,9207,9220,9290,9415,9418,9485,9500,9502-9503,9535,9575,9593-9595,9618,9666,9876-9878,9898,9900,9917,9929,9943-9944,9968,9998-10004,10009-10010,10012,10024-10025,10082,10180,10215,10243,10566,10616-10617,10621,10626,10628-10629,10778,11110-11111,11967,12000,12174,12265,12345,13456,13722,13782-13783,14000,14238,14441-14442,15000,15002-15004,15660,15742,16000-16001,16012,16016,16018,16080,16113,16992-16993,17877,17988,18040,18101,18988,19101,19283,19315,19350,19780,19801,19842,20000,20005,20031,20221-20222,20828,21571,22939,23502,24444,24800,25734-25735,26214,27000,27352-27353,27355-27356,27715,28201,30000,30718,30951,31038,31337,32768-32785,33354,33899,34571-34573,35500,38292,40193,40911,41511,42510,44176,44442-44443,44501,45100,48080,49152-49161,49163,49165,49167,49175-49176,49400,49999-50003,50006,50300,50389,50500,50636,50800,51103,51493,52673,52822,52848,52869,54045,54328,55055-55056,55555,55600,56737-56738,57294,57797,58080,60020,60443,61532,61900,62078,63331,64623,64680,65000,65129,65389 +range = 1.0.0.0-4.53.200.255 +range = 4.53.202.0-5.152.178.255 +range = 5.152.180.0-5.255.255.255 +range = 7.0.0.0-8.12.161.255 +range = 8.12.165.0-8.14.83.255 +range = 8.14.88.0-8.14.144.255 +range = 8.14.148.0-8.17.249.255 +range = 8.17.253.0-9.255.255.255 +range = 11.0.0.0-20.255.255.255 +range = 23.0.0.0-23.26.255.255 +range = 23.28.0.0-23.231.127.255 +range = 23.232.0.0-23.255.255.255 +range = 24.16.0.0-24.87.255.255 +range = 24.89.0.0-24.111.255.255 +range = 24.112.128.0-24.131.255.255 +range = 24.132.3.0-24.187.255.255 +range = 24.192.0.0-37.72.171.255 +range = 37.72.174.0-38.72.199.255 +range = 38.72.204.0-50.93.191.255 +range = 50.93.198.0-50.115.127.255 +range = 50.115.144.0-50.116.255.255 +range = 50.117.128.0-50.118.127.255 +range = 50.119.0.0-63.141.221.255 +range = 63.141.223.0-64.62.252.255 +range = 64.62.254.0-64.92.95.255 +range = 64.92.128.0-64.145.78.255 +range = 64.145.80.0/23 +range = 64.145.84.0-64.158.145.255 +range = 64.158.148.0-65.49.23.255 +range = 65.49.25.0-65.49.92.255 +range = 65.49.94.0-65.162.191.255 +range = 65.162.196.0-66.79.159.255 +range = 66.79.192.0-66.160.190.255 +range = 66.160.192.0-68.68.95.255 +range = 68.68.112.0-69.46.63.255 +range = 69.46.96.0-69.176.79.255 +range = 69.176.96.0-72.13.79.255 +range = 72.13.96.0-72.52.75.255 +range = 72.52.77.0-74.82.42.255 +range = 74.82.44.0-74.82.159.255 +range = 74.82.192.0-74.114.87.255 +range = 74.114.92.0-74.114.255.255 +range = 74.115.1.0/24 +range = 74.115.3.0/24 +range = 74.115.5.0-74.122.99.255 +range = 74.122.104.0-75.126.255.255 +range = 75.127.1.0-100.63.255.255 +range = 100.128.0.0-103.251.90.255 +range = 103.251.92.0-108.171.31.255 +range = 108.171.33.0-108.171.41.255 +range = 108.171.43.0-108.171.51.255 +range = 108.171.53.0-108.171.61.255 +range = 108.171.63.0-118.193.77.255 +range = 118.193.80.0-126.255.255.255 +range = 128.0.0.0-130.93.15.255 +range = 130.93.18.0-131.214.255.255 +range = 131.216.0.0-134.3.255.255 +range = 134.5.0.0-135.255.255.255 +range = 136.1.0.0-142.110.255.255 +range = 142.112.0.0-142.251.255.255 +range = 142.253.0.0-146.82.55.92 +range = 146.82.55.94-149.54.135.255 +range = 149.54.144.0/21 +range = 149.54.160.0-153.10.255.255 +range = 153.12.0.0-165.159.255.255 +range = 165.161.0.0-166.87.255.255 +range = 166.89.0.0-169.253.255.255 +range = 169.255.0.0-172.15.255.255 +range = 172.32.0.0-172.251.255.255 +range = 172.253.0.0-173.245.63.255 +range = 173.245.96.0-173.245.193.255 +range = 173.245.196.0-173.245.219.255 +range = 173.245.224.0-173.252.191.255 +range = 173.253.0.0-178.18.15.255 +range = 178.18.20.0-178.18.25.255 +range = 178.18.30.0-183.182.21.255 +range = 183.182.23.0-191.255.255.255 +range = 192.0.1.0/24 +range = 192.0.3.0-192.12.18.255 +range = 192.12.20.0-192.31.42.255 +range = 192.31.44.0-192.41.207.255 +range = 192.41.209.0-192.43.242.255 +range = 192.43.244.0-192.54.248.255 +range = 192.54.250.0-192.88.98.255 +range = 192.88.100.0-192.92.113.255 +range = 192.92.115.0-192.155.159.255 +range = 192.155.192.0-192.167.255.255 +range = 192.169.0.0-192.176.255.255 +range = 192.178.0.0-192.185.255.255 +range = 192.186.64.0-192.249.63.255 +range = 192.249.80.0-192.250.239.255 +range = 192.251.0.0-194.77.40.241 +range = 194.77.40.243-194.77.40.245 +range = 194.77.40.247-194.110.213.255 +range = 194.110.215.0-198.12.119.255 +range = 198.12.123.0-198.17.255.255 +range = 198.20.0.0-198.51.99.255 +range = 198.51.101.0-198.144.239.255 +range = 198.145.0.0-199.33.119.255 +range = 199.33.121.0-199.33.123.255 +range = 199.33.128.0-199.48.146.255 +range = 199.48.148.0-199.68.195.255 +range = 199.68.200.0-199.127.239.255 +range = 199.127.248.0-199.187.167.255 +range = 199.187.172.0-199.188.237.255 +range = 199.188.240.0-199.255.207.255 +range = 199.255.209.0-203.0.112.255 +range = 203.0.114.0-203.12.5.255 +range = 203.12.7.0-204.13.63.255 +range = 204.13.72.0-204.16.191.255 +range = 204.16.200.0-204.19.237.255 +range = 204.19.239.0-204.74.207.255 +range = 204.74.224.0-205.159.188.255 +range = 205.159.190.0-205.163.255.255 +range = 205.164.64.0-205.209.127.255 +range = 205.209.192.0-206.108.51.255 +range = 206.108.54.0-206.165.3.255 +range = 206.165.5.0-208.77.39.255 +range = 208.77.48.0-208.80.3.255 +range = 208.80.8.0-208.123.222.255 +range = 208.123.224.0-209.51.184.255 +range = 209.51.186.0-209.54.47.255 +range = 209.54.64.0-209.107.191.255 +range = 209.107.194.0-209.107.209.255 +range = 209.107.211.0/24 +range = 209.107.213.0-211.156.109.255 +range = 211.156.112.0-216.83.32.255 +range = 216.83.50.0/24 +range = 216.83.64.0-216.151.182.255 +range = 216.151.184.0-216.151.189.255 +range = 216.151.192.0-216.172.127.255 +range = 216.172.160.0-216.185.35.255 +range = 216.185.37.0-216.218.232.255 +range = 216.218.234.0-216.224.111.255 +range = 216.224.128.0-239.255.255.255 + diff --git a/src/auth.py b/src/auth.py new file mode 100755 index 0000000..68626e5 --- /dev/null +++ b/src/auth.py @@ -0,0 +1,192 @@ +import os +import base64 +import json +from hashlib import sha256 +from flask import Flask, render_template, Response +from flask import request, redirect, url_for, make_response + +import src.packets as packets +import src.utils as utils + +class authUser: + def __init__(self): + self.username = None + self.permGroups = [] + self.id = None + self.sha256passwordhash = None + self.passwordUpdated = None + self.created = None + +class authClient: + def __init__(self): + self.username = None + self.session = utils.randID(32) + self.currentPage = "/login" + self.user = None + + self.timeout = utils.getUnixTime() + (6 * 60 * 60 * 1000) + self.loginTime = utils.getUnixTime() + self.lastReauth = utils.getUnixTime() + + self.rawClient = None + + def send(self, type, data): + self.rawClient.send(type, data) + +class authServer: + def __init__(self, webserv): + self.rawServer = None + self.app = None + self.clients = [] + self.users = [] + + self.webserv = webserv + + self.pageListeners = [] + + self.reloadUsers() + self.rawServer = webserv.rawServer + self.initRawServer() + + def login(self, c, data): + if c.clientid != data['cid']: + c.send('error', 'invalidLoginRequest') + return + + if int(data['data']['salt']) > (utils.getUnixTime() + 5000): + c.send('error', 'invalidLoginRequest') + return + + logins = json.loads(utils.readFile(utils.getRoot('data/')+'creds.json')) + isValid = False + validAcc = None + for acc in self.users: + hash = utils.hash( + str(acc.username)+ + str(acc.sha256passwordhash)+ + str(data['data']['salt'])) + + if hash == str(data['data']['data']): + isValid = True + validAcc = acc + break + + if isValid: + if utils.getatribinarr(self.clients, 'rawClient', c): + c.send('error', 'prelogin') + return + + ac = authClient() + ac.username = validAcc.username + ac.user = validAcc + ac.rawClient = c + + self.clients.append(ac) + + c.send('loginSuccess', { + 'username': ac.username, + 'permGroups': ac.user.permGroups, + 'session': ac.session, + 'redir': f'/{self.webserv.defaultTab}/{self.webserv.defaultPage}', + 'timeout': ac.timeout + }) + + return + else: + c.send('error', 'invalidLogin') + return + + def validAc(self, ac): + if ac == None: + return False + if not (ac in self.clients): + return False + if ac.rawClient.address != request.remote_addr: + return False + if utils.getUnixTime() > ac.timeout: + return False + + return True + + def reauth(self, c, data): + session = data['data']['session'] + + ac = utils.getatribinarr(self.clients, 'session', session) + + if not self.validAc(ac): + c.send('error', 'invalidLoginRequest') + return + + ac.rawClient = c + ac.lastReauth = utils.getUnixTime() + + ac.send('reauth', { + 'username': ac.username, + 'id': ac.user.id, + 'permGroups': ac.user.permGroups, + 'created': ac.user.created, + 'passwordUpdated': ac.user.passwordUpdated, + 'timeout': ac.timeout + }) + + for pageListener in self.pageListeners: + if pageListener['page'] == ac.currentPage: + pageListener['func'](ac) + + def cookieLogin(self, request): + session = request.cookies.get('session') + if session == None: + return None + + ac = utils.getatribinarr(self.clients, 'session', session) + + if not self.validAc(ac): + return False + + ac.currentPage = request.path + + return True + + def validPermGroup(self, group, request): + session = request.cookies.get('session') + if session == None: + return False, None + + ac = utils.getatribinarr(self.clients, 'session', session) + + if not self.validAc(ac): + return False, None + if (group != "") and not (group in ac.user.permGroups): + return False, None + return True, ac.user.permGroups + + # def validPermGroupfromSession(self, group, request): + + + # def logout(self, ac): + # if not self.validAc(ac): + # return False + # self.clients.remove(ac) + # return True + + def unauth(self, ac): + self.clients.remove(ac) + + def initRawServer(self): + self.rawServer.addEventListener('login', self.login) + self.rawServer.addEventListener('reauth', self.reauth) + + def reloadUsers(self): + data = json.loads(utils.readFile(utils.getRoot('data/')+'creds.json')) + + self.users = [] + + for acc in data: + user = authUser() + user.username = acc['username'] + user.id = acc['id'] + user.sha256passwordhash = acc['sha256passwordhash'] + user.permGroups = acc['permGroups'] + user.created = acc['created'] + user.passwordUpdated = acc['passwordUpdated'] + self.users.append(user) \ No newline at end of file diff --git a/src/jsonpack.py b/src/jsonpack.py new file mode 100755 index 0000000..4840b57 --- /dev/null +++ b/src/jsonpack.py @@ -0,0 +1,273 @@ +# Copyright (c) 2013, Rodrigo González, Sapienlab All Rights Reserved. +# Available via MIT LICENSE. See https://github.com/roro89/jsonpack/blob/master/LICENSE.md for details. + +TOKEN_TRUE = -1 +TOKEN_FALSE = -2 +TOKEN_NULL = -3 +TOKEN_EMPTY_STRING = -4 +TOKEN_UNDEFINED = -5 + +def pack(json): + json = json if isinstance(json, str) else json + dictionary = { + 'strings': [], + 'integers': [], + 'floats': [] + } + + def recursiveAstBuilder(item): + if item is None: + return { + 'type': 'null', + 'index': TOKEN_NULL + } + if item == '': + return { + 'type': '', + 'index': TOKEN_UNDEFINED + } + if isinstance(item, list): + ast = ['@'] + for i in item: + ast.append(recursiveAstBuilder(i)) + return ast + if isinstance(item, dict): + ast = ['$'] + for key, value in item.items(): + ast.append(recursiveAstBuilder(key)) + ast.append(recursiveAstBuilder(value)) + return ast + if item == '': + return { + 'type': 'empty', + 'index': TOKEN_EMPTY_STRING + } + if type(item) == type(True): + return { + 'type': 'boolean', + 'index': TOKEN_TRUE if item else TOKEN_FALSE + } + if isinstance(item, str): + index = dictionary['strings'].index(item) if item in dictionary['strings'] else -1 + if index == -1: + dictionary['strings'].append((item)) + index = len(dictionary['strings']) - 1 + return { + 'type': 'strings', + 'index': index + } + if isinstance(item, int): + index = dictionary['integers'].index(item) if item in dictionary['integers'] else -1 + if index == -1: + dictionary['integers'].append(_base10To36(item)) + index = len(dictionary['integers']) - 1 + return { + 'type': 'integers', + 'index': index + } + if isinstance(item, float): + index = dictionary['floats'].index(item) if item in dictionary['floats'] else -1 + if index == -1: + dictionary['floats'].append(item) + index = len(dictionary['floats']) - 1 + return { + 'type': 'floats', + 'index': index + } + raise TypeError('Unexpected argument of type ' + str(type(item))) + + def recursiveParser(item): + if isinstance(item, list): + packed = item[0] + for i in item[1:]: + packed += recursiveParser(i) + '|' + return (packed[:-1] if packed[-1] == '|' else packed) + ']' + type = item['type'] + index = item['index'] + if type == 'strings': + return _base10To36(index) + if type == 'integers': + return _base10To36(stringLength + index) + if type == 'floats': + return _base10To36(stringLength + integerLength + index) + if type == 'boolean': + return str(index) + if type == 'null': + return str(TOKEN_NULL) + if type == '': + return str(TOKEN_UNDEFINED) + if type == 'empty': + return str(TOKEN_EMPTY_STRING) + raise TypeError('The item is alien!') + + ast = recursiveAstBuilder(json) + + stringLength = len(dictionary['strings']) + integerLength = len(dictionary['integers']) + floatLength = len(dictionary['floats']) + packed = '|'.join(dictionary['strings']) + packed += '^' + '|'.join(dictionary['integers']) + dictionary['floats'] = [str(n) for n in dictionary['floats']] + packed += '^' + '|'.join(dictionary['floats']) + packed += '^' + recursiveParser(ast) + + return packed + +def unpack(packed): + rawBuffers = packed.split('^') + dictionary = [] + buffer = rawBuffers[0] + if buffer != '': + buffer = buffer.split('|') + for i in buffer: + dictionary.append(_decode(i)) + buffer = rawBuffers[1] + if buffer != '': + buffer = buffer.split('|') + for i in buffer: + dictionary.append(_base36To10(i)) + buffer = rawBuffers[2] + if buffer != '': + buffer = buffer.split('|') + for i in buffer: + dictionary.append(float(i)) + tokens = [] + number36 = '' + for i in rawBuffers[3]: + if i in ['|', '$', '@', ']']: + if number36: + tokens.append(_base36To10(number36)) + number36 = '' + if i != '|': + tokens.append(i) + else: + number36 += i + tokensLength = len(tokens) + tokensIndex = 0 + + def recursiveUnpackerParser(): + nonlocal tokensIndex + # Maybe '$' (object) or '@' (array) + type = tokens[tokensIndex] + tokensIndex += 1 + # Parse an array + if type == '@': + node = [] + while tokensIndex < tokensLength: + value = tokens[tokensIndex] + if value == ']': + return node + if value == '@' or value == '$': + node.append(recursiveUnpackerParser()) + else: + if value == TOKEN_TRUE: + node.append(True) + elif value == TOKEN_FALSE: + node.append(False) + elif value == TOKEN_NULL: + node.append(None) + elif value == TOKEN_UNDEFINED: + node.append() + elif value == TOKEN_EMPTY_STRING: + node.append('') + else: + node.append(dictionary[value]) + tokensIndex += 1 + return node + # Parse an object + if type == '$': + node = {} + while tokensIndex < tokensLength: + key = tokens[tokensIndex] + if key == ']': + return node + if key == TOKEN_EMPTY_STRING: + key = '' + else: + key = dictionary[key] + tokensIndex += 1 + value = tokens[tokensIndex] + if value == '@' or value == '$': + node[key] = recursiveUnpackerParser() + else: + if value == TOKEN_TRUE: + node[key] = True + elif value == TOKEN_FALSE: + node[key] = False + elif value == TOKEN_NULL: + node[key] = None + elif value == TOKEN_UNDEFINED: + node[key] = None + elif value == TOKEN_EMPTY_STRING: + node[key] = '' + else: + node[key] = dictionary[value] + tokensIndex += 1 + return node + raise TypeError('Bad token ' + str(type) + ' isn\'t a type') + + return recursiveUnpackerParser() + +def _indexOfDictionary(dictionary, value): + if isinstance(value, bool): + return TOKEN_TRUE if value else TOKEN_FALSE + if value is None: + return TOKEN_NULL + if value == '': + return TOKEN_UNDEFINED + if value == '': + return TOKEN_EMPTY_STRING + if isinstance(value, str): + value = _encode(value) + index = dictionary['strings'].index(value) if value in dictionary['strings'] else -1 + if index == -1: + dictionary['strings'].append(value) + index = len(dictionary['strings']) - 1 + if type(value) not in [str, int]: + raise Error('The type is not a JSON type') + if isinstance(value, str): + value = _encode(value) + elif isinstance(value, int) and value % 1 == 0: + value = _base10To36(value) + value = _encode(value) if isinstance(value, str) else _base10To36(value) + index = dictionary[type(value)].index(value) if value in dictionary[type(value)] else -1 + if index == -1: + dictionary[type(value)].append(value) + index = len(dictionary[type(value)]) - 1 + return '+' + str(index) if type(value) == 'number' else index + +def _encode(string): + if not isinstance(string, str): + return string + return string.replace(' ', '+').replace('+', '%2B').replace('|', '%7C').replace('^', '%5E').replace('%', '%25') + +def _decode(string): + if not isinstance(string, str): + return string + return string.replace('+', ' ').replace('%2B', '+').replace('%7C', '|').replace('%5E', '^').replace('%25', '%') + +def _base10To36(number): + if not isinstance(number, (int, float)): + raise TypeError('number must be an integer') + is_negative = number < 0 + number = abs(number) + + alphabet, base36 = ['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', ''] + + while number: + number, i = divmod(number, 36) + base36 = alphabet[i] + base36 + if is_negative: + base36 = '-' + base36 + + return base36 or alphabet[0] + + +def _base36To10(number): + return int(number, 36) + +def _indexOf(array, obj, start=0): + for i in range(start, len(array)): + if array[i] == obj: + return i + return -1 \ No newline at end of file diff --git a/src/modules.py b/src/modules.py new file mode 100755 index 0000000..843464d --- /dev/null +++ b/src/modules.py @@ -0,0 +1,280 @@ +import json +import importlib +import sys +import multiprocessing as mupr + +import src.web as web +import src.utils as utils + +class module(): + def __init__(self): + self.name = None + self.module = None + self.proc = None + self.rootdir = None + self.tabs = [] + + def add(self): + spec = importlib.util.spec_from_file_location(self.name, utils.getRoot(self.entrypoint)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + self.module = module + + def init(self, moduleMaster): + self.module.init(moduleMaster) + self.proc = mupr.Process(target=self.module.main) + + def run(self): + self.proc.start() + + def stop(self): + self.proc.stop() + + + +class moduleMaster(): + def __init__(self): + self.modules = [] + + self.webserv = None + self.app = None + self.rawServer = None + self.authServer = None + self.defaultPage = "" + self.vars = {} + + # self.addRawEventListener('test1', test1) + + def addModules(self, webserv): + self.webserv = webserv + + mdirs = utils.listSubdirs(utils.getRoot('modules/')) + + for mname in mdirs: + mjson = json.loads(open(utils.getRoot(f'modules/{mname}/module.json')).read()) + m = module() + m.name = mjson['name'] + m.entrypoint = mjson['entrypoint'] + + for tab in mjson['tabs']: + mtab = utils.getatribinarr(self.webserv.webtabs, 'name', tab['name']) + + if mtab == None: + mtab = web.webtab() + mtab.name = tab['name'] + m.tabs.append(mtab) + + def recursiveAdder(objs): + tmpPages = [] + for obj in objs: + if obj['type'] == 'folder': + folder = web.webpagefolder() + folder.name = obj['name'] + tmpTmpPages = recursiveAdder(obj['pages']) + for tmpobj in tmpTmpPages: + folder.pages.append(tmpobj) + tmpPages.append(folder) + else: + mpage = web.webpage() + mpage.name = obj['name'] + mpage.requiredPermGroup = obj['requiredPermGroup'] + mpage.location = obj['location'] + tmpPages.append(mpage) + return tmpPages + + tmpPages = recursiveAdder(tab['pages']) + + for obj in tmpPages: + mtab.pages.append(obj) + + mtab.defaultPage = tab['defaultPage'] + + self.webserv.webtabs.append(mtab) + + m.add() + self.modules.append(m) + + # for tab in webserv.webtabs: + # tab.compileHtml('User') + + def initModules(self, webserv): + self.webserv = webserv + self.app = webserv.app + self.rawServer = webserv.rawServer + self.authServer = webserv.authServer + + self.defaultPage = f'/{webserv.defaultTab}/{webserv.defaultPage}' + + for module in self.modules: + module.init(self) + + def runModules(self): + for module in self.modules: + module.run() + + + + + def reloadUsers(self): + self.authServer.reloadUsers() + + def editUser(self, user, varname, value): + path = utils.getRoot('data/')+'creds.json' + logins = json.loads(utils.readFile(path)) + for userdata in logins: + if userdata['username'] == user.username: + userdata[varname] = value + + utils.writeFile(path, json.dumps(logins, sort_keys=True, indent=2)) + + def setUserPassword(self, user, hash): + for ac in self.authServer.clients: + if ac.user == user: + self.unauth(ac) + self.editUser(user, 'sha256passwordhash', hash) + self.editUser(user, 'passwordUpdated', utils.getUnixTime()) + self.authServer.reloadUsers() + + def setUserGroups(self, user, groups): + for ac in self.authServer.clients: + if ac.user == user: + self.unauth(ac) + self.editUser(user, 'permGroups', groups) + self.authServer.reloadUsers() + + def addUser(self, user, groups, hash): + path = utils.getRoot('data/')+'creds.json' + time = utils.getUnixTime() + logins = json.loads(utils.readFile(path)) + logins.append({ + 'username': user, + 'permGroups': groups, + 'created': time, + 'id': utils.randID(16), + 'passwordUpdated': time, + 'sha256passwordhash': hash + }) + utils.writeFile(path, json.dumps(logins, sort_keys=True, indent=2)) + self.authServer.reloadUsers() + + def deleteUser(self, user): + path = utils.getRoot('data/')+'creds.json' + time = utils.getUnixTime() + logins = json.loads(utils.readFile(path)) + for login in logins: + if login['id'] == user.id: + logins.remove(login) + utils.writeFile(path, json.dumps(logins, sort_keys=True, indent=2)) + self.authServer.reloadUsers() + + def userInGroup(self, ac, group): + if not self.authServer.validAc(ac): + return False + if (group != "") and not (group in ac.user.permGroups): + return False + return True + + + + def getUserByName(self, name): + returnArr = [] + for user in self.authServer.users: + if user.username == name: + returnArr.append(user) + return returnArr + + def getUserById(self, id): + return utils.getatribinarr(self.authServer.users, 'id', id) + + + + + + def getRawClients(self): + return self.rawServer.clients + + def getAuthClients(self): + return self.authServer.clients + + def addRawEventListener(self, eventName, func): + self.rawServer.eventListeners.append({ + 'type': eventName, + 'func': func + }) + + def addAuthEventListener(self, eventName, func): + def tmpfunc(c, data): + if not c in self.rawServer.clients: + return + ac = utils.getatribinarr(self.authServer.clients, 'rawClient', c) + if ac == None: + return + if not self.authServer.validAc(ac): + return + + func(ac, data) + + self.rawServer.addEventListener(eventName, tmpfunc) + + def getRawClientByID(self, ID): + return utils.getatribinarr(self.rawServer.clients, 'clientid', ID) + + def getAuthClientByID(self, ID): + c = utils.getatribinarr(self.rawServer.clients, 'clientid', ID) + if c == None: + return None + return utils.getatribinarr(self.authServer.clients, 'rawClient', c) + + def addPageEventListener(self, page, func): + self.authServer.pageListeners.append({ + 'page': page, + 'func': func + }) + + + + def getVar(self, varName): + return self.vars[varname] + + def setVar(self, varName, val): + self.vars[varName] = val + + + def unauth(self, ac): + ac.send('redir', { + "location": "/" + }) + self.authServer.unauth(ac) + + + def sendPopupInfo(self, c, title, msg): + c.send('popupInfo', { + 'title': title, + 'msg': msg + }) + + def sendPopupSuccess(self, c, title, msg): + c.send('popupSuccess', { + 'title': title, + 'msg': msg + }) + + def sendPopupWarning(self, c, title, msg): + c.send('popupWarning', { + 'title': title, + 'msg': msg + }) + + def sendPopupError(self, c, title, msg): + c.send('popupError', { + 'title': title, + 'msg': msg + }) + + def sendPopupColor(self, c, title, msg, color, isDark): + c.send('popupColor', { + 'title': title, + 'msg': msg, + 'color': color, + 'isDark': isDark + }) \ No newline at end of file diff --git a/src/packets.py b/src/packets.py new file mode 100755 index 0000000..8eecf1f --- /dev/null +++ b/src/packets.py @@ -0,0 +1,113 @@ +import os +import base64 +from hashlib import sha256 +from flask import Flask, render_template, Response +from flask import request, redirect, url_for, make_response + +import src.jsonpack as jsonpack +import src.utils as utils + +import queue + +class rawClient: + def __init__(self, rawServer): + self.clientid = utils.randID(32) + self.messages = queue.Queue() + self.rawServer = rawServer + self.address = None + def send(self, type, msg): + self.rawServer.sendClient(self, jsonpack.pack({ + 'type': type, + 'data': msg, + 'cid': self.clientid + })) + +#Credit to https://github.com/MaxHalford/flask-sse-no-deps +class rawServer: + + def __init__(self, webserv): + self.eventListeners = [] + self.clients = [] + self.webserv = webserv + self.app = webserv.app + # self.app = app + + def listen(self): + c = rawClient(self) + self.clients.append(c) + return self.clients[-1] + + def broadcast(self, msg): + # We go in reverse order because we might have to delete an element, which will shift the + # indices backward + ssedata = format_sse(msg) + for i in reversed(range(len(clients))): + try: + self.clients[i].messages.put_nowait(ssedata) + except queue.Full: + del self.clients[i] + + def sendClient(self, c, msg): + if c not in self.clients: + return + ssedata = format_sse(msg) + c.messages.put_nowait(ssedata) + + def clientByCID(self, cid): + for c in self.clients: + if c.clientid == cid: + return c + return None + + def addEventListener(self, eventName, func): + self.eventListeners.append({ + 'type': eventName, + 'func': func + }) + +def format_sse(data: str, event=None) -> str: + #Formats a string and an event name in order to follow the event stream convention. + msg = f'data: {data}\n\n' + if event is not None: + msg = f'event: {event}\n{msg}' + return msg + + +def startRawListener(webserv): + server = rawServer(webserv) + # app.rawServer = server + + @webserv.app.route('/listen', methods=['GET', 'POST']) + def listen(): + + if request.method == 'GET': + + c = server.listen() # returns a queue.Queue + c.address = request.remote_addr + c.send('clidata', { + 'cid': c.clientid + }) + + def stream(): + while True: + msg = c.messages.get() # blocks until a new message arrives + yield msg + + return Response(stream(), mimetype='text/event-stream') + if request.method == 'POST': + + data = jsonpack.unpack(request.data.decode("utf-8")) + + if data['cid'] == None: + return {}, 400 + c = utils.getatribinarr(server.clients, 'clientid', data['cid']) + if c == None: + return {}, 400 + + for event in server.eventListeners: + if event['type'] == data['type']: + event['func'](c, data) + + return {}, 200 + + return server \ No newline at end of file diff --git a/src/utils.py b/src/utils.py new file mode 100755 index 0000000..fe5180f --- /dev/null +++ b/src/utils.py @@ -0,0 +1,132 @@ +import os +import json +import string +import random +from hashlib import sha256 +from datetime import datetime + +from sys import platform + +import subprocess + +def getRoot(path): + rootdir = os.getcwd() + f'/{path}' + + if platform in ['nt', 'win32', 'win64']: + rootdir = rootdir.split(':')[1].replace('\\', '/') + + return rootdir + +def pathExists(path): + return os.path.exists(path) + +def makeDir(path): + if not os.path.exists(path): + os.makedirs(path) + +def listSubdirs(folder): + if os.path.exists(folder): + return os.listdir(folder) + else: + return [] + +def writeFile(path, data): + if not os.path.exists(path): + with open(path, mode='a'): pass + with open(path, 'w') as f: + f.write(data) + +def readFile(path): + if not os.path.exists(path): + return '' + try: + with open(path) as f: + return f.read() + except: + return '' + +def delFile(path): + if os.path.exists(path): + os.remove(path) + +def getUnixTime(): + return round((datetime.utcnow() - datetime(1970, 1, 1)).total_seconds() * 1000) + +def hash(data): + return sha256(data.encode('utf-8')).hexdigest().upper() + +def getatribinarr(arr, atribname, atrib): + for i in range(0,len(arr),1): + if getattr(arr[i], atribname) == atrib: + return arr[i] + return None + +def randID(length): + letters = string.hexdigits + return ''.join(random.choice(letters) for i in range(length)) + +def genDefaultAccounts(): + userPassword = randID(16) + adminPassword = randID(16) + + print('#################################################') + print('New Credentials - THESE ONLY WILL BE PRINTED ONCE') + print('########') + print('Username: User') + print(f'Password: {userPassword}') + print('########') + print('Username: Admin') + print(f'Password: {adminPassword}') + print('#################################################') + + time = getUnixTime() + + return json.dumps( + [ + { + 'username': 'User', + 'permGroups': [ + 'Users' + ], + 'id': randID(16), + 'created': time, + 'passwordUpdated': time, + 'sha256passwordhash': hash(userPassword) + }, + { + 'username': 'Admin', + 'permGroups': [ + 'Users', + 'Admins' + ], + 'id': randID(16), + 'created': time, + 'passwordUpdated': time, + 'sha256passwordhash': hash(adminPassword) + } + ], sort_keys=True, indent=2) + +def genKey(path): + subprocess.run(['openssl', 'genrsa', '-out', f'{path}ssl.key', '2048']) + +def genCert(path): + writeFile(f'{path}/ssl.cnf', +"""[req] +default_bits = 2048 +prompt = no +default_md = sha256 +encrypt_key = no +distinguished_name = dn + +[dn] +C = ID +O = Local Digital Cert Authority +OU = www.ca.local +CN = Self-Signed Root CA +""") + + subprocess.run(['openssl', 'req', '-new', '-key', f'{path}ssl.key', '-out', f'{path}ssl.csr', '-config', f'{path}ssl.cnf']) + subprocess.run(['openssl', 'x509', '-req', '-days', '36500', '-in', f'{path}ssl.csr', '-signkey', f'{path}ssl.key', '-out', f'{path}ssl.crt']) + + delFile(f'{path}ssl.cnf') + delFile(f'{path}ssl.csr') \ No newline at end of file diff --git a/src/web.py b/src/web.py new file mode 100755 index 0000000..0a9a1ed --- /dev/null +++ b/src/web.py @@ -0,0 +1,187 @@ +import os +import src.utils as utils +import multiprocessing as mupr + +from flask import Flask, render_template, Response +from flask import request, redirect, url_for, make_response + +import src.jsonpack as jsonpack +import src.packets as packets +import src.auth as auth + +webroot = utils.getRoot('html/') + +app = Flask(__name__, + static_url_path=webroot, + static_folder=webroot, + template_folder=webroot) + +class webtab(): + def __init__(self): + self.name = None + self.pages = [] + self.defaultPage = '' + self.html = '' + + def compileHtml(self, permGroups): + html = '' + for page in self.pages: + html += page.compileHtml(self.name, permGroups) + return html + + def addPage(self, page): + self.pages.append(page) + +class webpagefolder(): + def __init__(self): + self.name = None + self.pages = [] + + def compileHtml(self, tabname, permGroups): + html = '
' + self.name + '
    ' + for page in self.pages: + html += page.compileHtml(tabname, permGroups) + html += '
' + return html + +class webpage(): + def __init__(self): + self.name = None + self.requiredPermGroup = '' + self.location = None + + def compileHtml(self, tabname, permGroups): + html = '' +\ + self.name + else: + html += f'>{self.name}' + return html + '' + +@app.route('/') +def index(): + isValid = app.webserv.authServer.cookieLogin(request) + if not isValid: + return redirect("/login", code=302) + else: + return redirect(f'/{app.webserv.defaultTab}/{app.webserv.defaultPage}', code=302) + +@app.route('/login') +def loginPage(): + isValid = app.webserv.authServer.cookieLogin(request) + if isValid: + return redirect(f'/{app.webserv.defaultTab}/{app.webserv.defaultPage}', code=302) + + return make_response(open(f'{webroot}nav.html', 'r').read() + .replace('', open(f'{webroot}login.html', 'r').read()) + .replace('', 'Login') + .replace('', app.webserv.title) + .replace('', '/login')) + +def recursivePageFinder(pagename, objs): + returnVal = None + for obj in objs: + if isinstance(obj, webpagefolder): + tmp = recursivePageFinder(pagename, obj.pages) + if tmp != None: + returnVal = tmp + else: + if obj.name == pagename: + returnVal = obj + return returnVal + +@app.route('//') +def page(tabname, pagename): + + isValid = app.webserv.authServer.cookieLogin(request) + if not isValid: + return redirect("/login", code=302) + + try: + + tab = utils.getatribinarr(app.webserv.webtabs, 'name', tabname) + page = recursivePageFinder(pagename, tab.pages) + + # print(page.requiredPermGroup) + + isValid, permGroups = app.webserv.authServer.validPermGroup(page.requiredPermGroup, request) + + if not isValid: + return redirect(f'/{tab.name}/{tab.defaultPage}', code=302) + + return make_response(open(utils.getRoot('html/nav.html'), 'r').read() + .replace('', open(utils.getRoot(page.location), 'r').read()) + .replace('', app.webserv.tabHtml) + .replace('', tab.compileHtml(permGroups)) + .replace('', app.webserv.title) + .replace('', f'/{app.webserv.defaultTab}/{app.webserv.defaultPage}')) + except: + return redirect("/", code=302) + +@app.route('/src/') +def src(file): + return app.send_static_file(f'src/{file}') + +@app.errorhandler(404) +def err404(err): + return redirect("/", code=302) + + +class webserv(): + def __init__(self): + self.title = 'Polyboard' + self.port = 443 + self.host = '0.0.0.0' + self.verbose = False + self.secure = True + self.tabHtml = '' + self.webtabs = [] + self.defaultTab = 'main' + self.defaultPage = '' + + self.app = None + + def init(self): + if not self.verbose: + import logging + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + + if self.secure: + dataroot = utils.getRoot("data/") + sslcontext = (f'{dataroot}ssl.crt', f'{dataroot}ssl.key') + else: + sslcontext = None + + def tabHtml(path, name): + return f'{name}' + + for tab in self.webtabs: + if tab.name == self.defaultTab: + self.tabHtml = tabHtml(f'/{tab.name}/{tab.defaultPage}', tab.name) + self.tabHtml + self.defaultPage = tab.defaultPage + else: + self.tabHtml += tabHtml(f'/{tab.name}/{tab.defaultPage}', tab.name) + + app.webserv = self + self.app = app + self.rawServer = packets.startRawListener(self) + self.authServer = auth.authServer(self) + self.proc = mupr.Process(target=app.run, kwargs=dict(debug=self.verbose, port=self.port, host=self.host, ssl_context=sslcontext)) + + + def start(self): + self.proc.start() + + # return self.rawServer + + def stop(self): + self.proc.terminate() + + # def sendfatal(self, err): + # self.rawServer.broadcast(jsonpack.pack({ + # 'type': 'error', + # 'severity': 'fatal', + # 'error': err + # })) \ No newline at end of file