github.com/iDigitalFlame/xmt@v0.5.4/tools/config.py (about)

     1  #!/usr/bin/python3
     2  # Copyright (C) 2020 - 2023 iDigitalFlame
     3  #
     4  # This program is free software: you can redistribute it and/or modify
     5  # it under the terms of the GNU General Public License as published by
     6  # the Free Software Foundation, either version 3 of the License, or
     7  # any later version.
     8  #
     9  # This program is distributed in the hope that it will be useful,
    10  # but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  # GNU General Public License for more details.
    13  #
    14  # You should have received a copy of the GNU General Public License
    15  # along with this program.  If not, see <https://www.gnu.org/licenses/>.
    16  #
    17  
    18  from zlib import crc32
    19  from shlex import split
    20  from io import StringIO
    21  from hashlib import sha512
    22  from datetime import datetime
    23  from json import dumps, loads
    24  from secrets import token_bytes
    25  from traceback import format_exc
    26  from argparse import ArgumentParser
    27  from base64 import b64decode, b64encode
    28  from sys import argv, exit, stderr, stdin, stdout
    29  
    30  HELP_TEXT = """XMT cfg.Config Builder v1.5 Release
    31  
    32  Usage: {binary} [add|append] <options>
    33  
    34  GROUP MODE:
    35    To enable adding multiple Profiles under a config (Group Mode),
    36    add the "add" or "append" word before the command options. This
    37    will append the options as a separate group in the Profile.
    38  
    39    To overrite the group, omit the "add" or "append" value (the
    40    default option).
    41  
    42    In Group Mode, the -f/--file/-o/--out arguments may behave
    43    differently. If no output file is specified and "input" points to
    44    a valid file, the "input" WILL ALSO be treated as the "output". The
    45    same behavior occurs if no input is specified but "output" points
    46    to a valid file, then the output file will be read in as input
    47    before being appended to.
    48  
    49    To disable this behavior, just specify the output and input files
    50    when using Group Mode.
    51  
    52  BASIC ARGUMENTS:
    53    -h                            Show this help message and exit.
    54    --help
    55  
    56  INPUT/OUTPUT ARGUMENTS:
    57    -f                <file>
    58    --in              <file>      Input file path. Use '-' for stdin.
    59                                    See the 'Group Mode' section for the
    60                                    modifiers to this argument.
    61    -o                <file>
    62    --out             <file>      Output file path. Stdout is used if
    63                                    empty. See the 'Group Mode' section
    64                                    for the modifiers to this argument.
    65    -j
    66    --json                        Output in JSON format. Omit for raw
    67                                    binary. (Or base64 when output to
    68                                    stdout.)
    69    -I                            Accept stdin input as commands. Each
    70    --stdin                         line from stdin will be treated as a
    71                                    'append' line to the supplied config.
    72                                    Input and Output are ignored and are
    73                                    only set via the command line.
    74                                    This option disables using stdin for
    75                                    Config data.
    76  
    77  OPERATION ARGUMENTS:
    78    -p
    79    --print                       List values contained in the file
    80                                    input. Fails if no input is found or
    81                                    invalid. Output format can be modified
    82                                    using -j/-p.
    83  
    84  BUILD ARGUMENTS:
    85   SYSTEM:
    86    --host            <hostname>  Hostname hint, used for implants only.
    87    --sleep           <secs|mod>  Sleep time period. Defaults to seconds
    88                                    for integers, but can take modifiers
    89                                    such as 's', 'h', 'm'. (ex: 2m or 3s).
    90    --jitter          <jitter %>  Jitter as a percentage [0-100]. Values
    91                                    greater than 100 fail. The '%' symbol
    92                                    may be included or omitted.
    93    --weight          <weight>    Group weight value [0-100]. Takes effect
    94                                    only if multiple Profiles exist. Only
    95                                    affects the Group it is used in.
    96    --selector        <selector>  Use the specified selector to indicate
    97                                    which order the Profile Groups should
    98                                    be used in. Takes effect globally and
    99                                    only needs to be used once in ANY
   100                                    group.
   101  
   102                                    See SELECTOR VALUES for more info.
   103    --killdate        <ISO-8601>  Specify a time in ISO-8601 format that
   104                                    be used to indicate when the implant
   105                                    will cease to function. The time should
   106                                    be specified in ISO-8601 format which is
   107                                    "YYYY-MM-DDTHH:MM:SS". The seconds may
   108                                    be omitted.
   109    --wh-days         <S-M-S str> Specify a Working hours value that targets
   110                                    specific days. This may be used in combonation
   111                                    with the "--wh-start" and "--wh-end" arguments.
   112                                    The accepted values are "SMTWRFS". Note:
   113                                    Sunday is the first day and must be the first
   114                                    'S' to be parsed correctly. All other chars
   115                                    may be out of order if needed.
   116    --wh-start        <HH:MM>     Specify a time in an HOURS:MINS format that
   117                                    specifies when the implant may start connecting
   118                                    with the C2 Server. This setting takes affect
   119                                    if there is a day or end value set.
   120    --wh-end          <HH:MM>     Specify a time in an HOURS:MINS format that
   121                                    specifies when the implant will stop connecting
   122                                    with the C2 Server. This setting will apply
   123                                    regardless of day or start setting and if a
   124                                    day or start time does NOT exist, this will
   125                                    instruct the implant to wait until midnight to
   126                                    try again.
   127  
   128   CONNECTION HINTS (Max 1 per Profile Group):
   129    --tcp                         Use the TCP Connection hint.
   130    --tls                         Use the TLS Connection hint.
   131    --udp                         Use the UDP Connection hint.
   132    --icmp                        Use the ICMP (Ping) Connection hint.
   133    --pipe                        Use the Windows Named Pipes or UNIX file
   134                                    pipes Connection hint.
   135    --tls-insecure
   136    -K                            Use the TLSNoVerify Connection hint.
   137  
   138    --ip              <protocol>  Use the IP Connection hint with the
   139                                    specified protocol number [0-255].
   140    --wc2-url         <url>         Use the WC2 Connection hint with the
   141                                    URL expression or static string.
   142                                    This can be used with other WC2
   143                                    arguments without an error.
   144    --wc2-host        <host>      Use the WC2 Connection hint with the
   145                                    Host expression or static string.
   146                                    This can be used with other WC2
   147                                    arguments without an error.
   148    --wc2-agent       <agent>     Use the WC2 Connection hint with the
   149                                    User-Agent expression or static string.
   150                                    This can be used with other WC2
   151                                    arguments without an error.
   152    --wc2-header      <key>=<val>
   153    -H                <key>=<val> Use the WC2 Connection hint with the
   154                                    HTTP header expression or static string in
   155                                    a key=value formnat. This value will be
   156                                    parsed and will fail if 'key' is empty or
   157                                    no '=' is present in the string. This may be
   158                                    specified multiple times. This can be used
   159                                    with other WC2 arguments without an error.
   160  
   161    --mtls                        Use Mutual TLS Authentication (mTLS) with a TLS
   162                                    Connection hint. This just enables the flag for
   163                                    client auth and will fail if '--tls-pem' and
   164                                    '--tls-key' are empty or not specified.
   165    --tls-ver         <version>   Use the TLS version specified when using a TLS
   166                                    Connection hint. This will set the version
   167                                    required and can be used by itself. A value of
   168                                    zero means TLSv1. Can be used with other TLS
   169                                    options.
   170    --tls-ca          <file|pem>  Use the provided certificate to verify the server
   171                                    (for clients) or verify clients (for the server).
   172                                    Can be used on it's own and with '--mtls'.
   173                                    This argument can take a file path to a PEM
   174                                    formatted certificate or raw base64 encoded PEM
   175                                    data.
   176    --tls-pem         <file|pem>  Use the provided certificate for the generated
   177                                    TLS socket. This can be used for client or
   178                                    server listeners. Requires '--tls-key'.
   179                                    This argument can take a file path to a PEM
   180                                    formatted certificate or raw base64 encoded PEM
   181                                    data.
   182    --tls-key         <file|pem>  Use the provided certificate key for the generated
   183                                    TLS socket. This can be used for client or
   184                                    server listeners. Requires '--tls-pem'.
   185                                    This argument can take a file path to a PEM
   186                                    formatted certificate private key or a raw
   187                                    base64 encoded PEM private key.
   188  
   189   WRAPPERS (Multiple different types may be used):
   190    --hex                         Use the HEX Wrapper.
   191    --zlib                        Use the Zlib compression Wrapper.
   192    --gzip                        Use the Gzip compression Wrapper.
   193    --b64                         Use the Base64 encoding Wrapper.
   194    --xor             [key]       Encrypt with the XOR Wrapper using the provided
   195                                    key string. If omitted the key will be a
   196                                    randomly generated 64 byte array.
   197    --cbk             [key]       Encrypt with the XOR Wrapper using the provided
   198                                    key string. If omitted the key will be a
   199                                    randomly seeded ABCD from a 64 byte array.
   200    --aes             [key]       Encrypt with the AES Wrapper using the provided
   201                                    key string. If omitted the key will be a
   202                                    randomly generated 32 byte array. The AES IV
   203                                    may be supplied using the '--aes-iv' argument.
   204                                    If not specified a 16 byte IV will be generated.
   205    --aes-iv          [iv]        Encrypt with the AES Wrapper using the provided
   206                                    IV string. If omitted the IV will be a
   207                                    randomly generated 16 byte array. The AES key
   208                                    may be supplied using the '--aes-key' argument.
   209                                    If not specified a 32 byte key will be generated.
   210  
   211   TRANSFORMS (Max 1 per Profile Group):
   212    --b64t            [shift]     Transform the data using a Base64 Transform. An
   213                                    option shift value [0-255] may be specified, but
   214                                    if omitted will not shift.
   215    --dns             [domain,*]  Use the DNS Packet Transform. optional DNS
   216                                    domain names may be specified (seperated by space)
   217                                    that will be used in the packets. This option may
   218                                    be used more than once to specify more domains.
   219    -D                [domain,*]
   220  
   221  SELECTOR VALUES
   222   last                           Switch Profile Group ONLY if the last
   223                                    attempt failed, the default setting.
   224   random                         Switch Profile Group in a random order
   225                                    on EVERY connection attempt.
   226   round-robin                    Switch Profile Group in a weighted order
   227                                    on EVERY connection attempt. Affected
   228                                    by Group Weights (lower is better).
   229   semi-random                    Switch Profile Group in a random order
   230                                    dependent on a 25% chance. If the
   231                                    chance fails (75%), do not switch.
   232   semi-round-robin               Switch Profile Group in a weighted order
   233                                    dependent on a 25% chance. If the chance
   234                                    fails (75%), do not switch. Affected by
   235                                    Group Weights (lower is better).
   236  """
   237  
   238  
   239  class Cfg:
   240      class Const:
   241          HOST = 0xA0
   242          SLEEP = 0xA1
   243          JITTER = 0xA2
   244          WEIGHT = 0xA3
   245          KEYPIN = 0xA6
   246          KILLDATE = 0xA4
   247          WORKHOURS = 0xA5
   248          SEPARATOR = 0xFA
   249  
   250          LAST_VALID = 0xAA
   251          ROUND_ROBIN = 0xAB
   252          RANDOM = 0xAC
   253          SEMI_ROUND_ROBIN = 0xAD
   254          SEMI_RANDOM = 0xAE
   255  
   256          TCP = 0xC0
   257          TLS = 0xC1
   258          UDP = 0xC2
   259          ICMP = 0xC3
   260          PIPE = 0xC4
   261          TLS_INSECURE = 0xC5
   262  
   263          IP = 0xB0
   264          WC2 = 0xB1
   265          TLS_EX = 0xB2
   266          MTLS = 0xB3
   267          TLS_CA = 0xB4
   268          TLS_CERT = 0xB5
   269  
   270          HEX = 0xD0
   271          ZLIB = 0xD1
   272          GZIP = 0xD2
   273          B64 = 0xD3
   274          XOR = 0xD4
   275          CBK = 0xD5
   276          AES = 0xD6
   277  
   278          B64T = 0xE0
   279          DNS = 0xE1
   280          B64S = 0xE2
   281  
   282          NAMES = {
   283              HOST: "host",
   284              SLEEP: "sleep",
   285              JITTER: "jitter",
   286              WEIGHT: "weight",
   287              KEYPIN: "keypin",
   288              KILLDATE: "killdate",
   289              WORKHOURS: "workhours",
   290              SEPARATOR: "|",
   291              LAST_VALID: "select-last",
   292              ROUND_ROBIN: "select-round-robin",
   293              RANDOM: "select-random",
   294              SEMI_ROUND_ROBIN: "select-semi-round-robin",
   295              SEMI_RANDOM: "select-semi-random",
   296              TCP: "tcp",
   297              TLS: "tls",
   298              UDP: "udp",
   299              ICMP: "icmp",
   300              PIPE: "pipe",
   301              TLS_INSECURE: "tls-insecure",
   302              IP: "ip",
   303              WC2: "wc2",
   304              TLS_EX: "tls-ex",
   305              MTLS: "mtls",
   306              TLS_CA: "tls-ca",
   307              TLS_CERT: "tls-cert",
   308              HEX: "hex",
   309              ZLIB: "zlib",
   310              GZIP: "gzip",
   311              B64: "base64",
   312              XOR: "xor",
   313              CBK: "cbk",
   314              AES: "aes",
   315              B64T: "b64t",
   316              DNS: "dns",
   317              B64S: "b64s",
   318          }
   319          NAMES_TO_ID = {
   320              "host": HOST,
   321              "sleep": SLEEP,
   322              "jitter": JITTER,
   323              "weight": WEIGHT,
   324              "keypin": KEYPIN,
   325              "killdate": KILLDATE,
   326              "workhours": WORKHOURS,
   327              "|": SEPARATOR,
   328              "select-last": LAST_VALID,
   329              "select-round-robin": ROUND_ROBIN,
   330              "select-random": RANDOM,
   331              "select-semi-round-robin": SEMI_ROUND_ROBIN,
   332              "select-semi-random": SEMI_RANDOM,
   333              "tcp": TCP,
   334              "tls": TLS,
   335              "udp": UDP,
   336              "icmp": ICMP,
   337              "pipe": PIPE,
   338              "tls-insecure": TLS_INSECURE,
   339              "ip": IP,
   340              "wc2": WC2,
   341              "tls-ex": TLS_EX,
   342              "mtls": MTLS,
   343              "tls-ca": TLS_CA,
   344              "tls-cert": TLS_CERT,
   345              "hex": HEX,
   346              "zlib": ZLIB,
   347              "gzip": GZIP,
   348              "base64": B64,
   349              "xor": XOR,
   350              "cbk": CBK,
   351              "aes": AES,
   352              "b64t": B64T,
   353              "dns": DNS,
   354              "b64s": B64S,
   355          }
   356          WRAPPERS = ["hex", "zlib", "gzip", "b64", "xor", "aes", "cbk"]
   357  
   358          @staticmethod
   359          def as_single(v):
   360              if not isinstance(v, int):
   361                  raise ValueError("single: invalid bit")
   362              s = Setting(1)
   363              s[0] = v
   364              return s
   365  
   366      @staticmethod
   367      def host(v):
   368          if not Utils.nes(v):
   369              raise ValueError("host: invalid name object")
   370          f = v.encode("UTF-8")
   371          n = len(f)
   372          if n > 0xFFFF:
   373              n = 0xFFFF
   374          s = Setting(3 + n)
   375          s[0] = Cfg.Const.HOST
   376          s[1] = (n >> 8) & 0xFF
   377          s[2] = n & 0xFF
   378          for x in range(0, n):
   379              s[x + 3] = f[x]
   380          del n, f
   381          return s
   382  
   383      @staticmethod
   384      def sleep(t):
   385          if Utils.nes(t):
   386              t = Utils.str_to_dur(t)
   387          if not isinstance(t, int) or t <= 0:
   388              raise ValueError("sleep: invalid duration")
   389          s = Setting(9)
   390          s[0] = Cfg.Const.SLEEP
   391          s[1] = (t >> 56) & 0xFF
   392          s[2] = (t >> 48) & 0xFF
   393          s[3] = (t >> 40) & 0xFF
   394          s[4] = (t >> 32) & 0xFF
   395          s[5] = (t >> 24) & 0xFF
   396          s[6] = (t >> 16) & 0xFF
   397          s[7] = (t >> 8) & 0xFF
   398          s[8] = t & 0xFF
   399          return s
   400  
   401      @staticmethod
   402      def jitter(p):
   403          if Utils.nes(p):
   404              if "%" in p:
   405                  p = p.replace("%", "")
   406              try:
   407                  p = int(p)
   408              except ValueError:
   409                  raise ValueError("jitter: invalid percentage")
   410          if not isinstance(p, int) or p < 0 or p > 100:
   411              raise ValueError("jitter: invalid percentage")
   412          s = Setting(2)
   413          s[0] = Cfg.Const.JITTER
   414          s[1] = p & 0xFF
   415          return s
   416  
   417      @staticmethod
   418      def weight(p):
   419          if Utils.nes(p):
   420              try:
   421                  p = int(p)
   422              except ValueError:
   423                  raise ValueError("weight: invalid value")
   424          if not isinstance(p, int) or p < 0 or p > 100:
   425              raise ValueError("weight: invalid value")
   426          s = Setting(2)
   427          s[0] = Cfg.Const.WEIGHT
   428          s[1] = p & 0xFF
   429          return s
   430  
   431      @staticmethod
   432      def wrap_hex():
   433          return Cfg.Const.as_single(Cfg.Const.HEX)
   434  
   435      @staticmethod
   436      def wrap_b64():
   437          return Cfg.Const.as_single(Cfg.Const.B64)
   438  
   439      @staticmethod
   440      def key_pin(v):
   441          if Utils.nes(v):
   442              v = Utils.public_key_hash(v)
   443          if not isinstance(v, int):
   444              raise ValueError("key_pin: invalid hash")
   445          s = Setting(5)
   446          s[0] = Cfg.Const.KEYPIN
   447          s[1] = (v >> 24) & 0xFF
   448          s[2] = (v >> 16) & 0xFF
   449          s[3] = (v >> 8) & 0xFF
   450          s[4] = v & 0xFF
   451          return s
   452  
   453      @staticmethod
   454      def wrap_zlib():
   455          return Cfg.Const.as_single(Cfg.Const.ZLIB)
   456  
   457      @staticmethod
   458      def wrap_gzip():
   459          return Cfg.Const.as_single(Cfg.Const.GZIP)
   460  
   461      @staticmethod
   462      def separator():
   463          return Cfg.Const.as_single(Cfg.Const.SEPARATOR)
   464  
   465      @staticmethod
   466      def killdate(v):
   467          if v is None:
   468              t = 0
   469          elif isinstance(v, datetime):
   470              t = int(v.timestamp())
   471          elif not isinstance(v, str):
   472              raise ValueError("killdate: invalid date value type")
   473          elif len(v) == 0:
   474              t = 0
   475          else:
   476              t = int(datetime.fromisoformat(v).timestamp())
   477          s = Setting(9)
   478          s[0] = Cfg.Const.KILLDATE
   479          s[1] = (t >> 56) & 0xFF
   480          s[2] = (t >> 48) & 0xFF
   481          s[3] = (t >> 40) & 0xFF
   482          s[4] = (t >> 32) & 0xFF
   483          s[5] = (t >> 24) & 0xFF
   484          s[6] = (t >> 16) & 0xFF
   485          s[7] = (t >> 8) & 0xFF
   486          s[8] = t & 0xFF
   487          del t
   488          return s
   489  
   490      @staticmethod
   491      def connect_ip(p):
   492          if not isinstance(p, int) or p <= 0 or p > 0xFF:
   493              raise ValueError("ip: invalid protocol")
   494          s = Setting(2)
   495          s[0] = Cfg.Const.IP
   496          s[1] = p & 0xFF
   497          return s
   498  
   499      @staticmethod
   500      def connect_tcp():
   501          return Cfg.Const.as_single(Cfg.Const.TCP)
   502  
   503      @staticmethod
   504      def connect_tls():
   505          return Cfg.Const.as_single(Cfg.Const.TLS)
   506  
   507      @staticmethod
   508      def connect_udp():
   509          return Cfg.Const.as_single(Cfg.Const.UDP)
   510  
   511      @staticmethod
   512      def connect_icmp():
   513          return Cfg.Const.as_single(Cfg.Const.ICMP)
   514  
   515      @staticmethod
   516      def connect_pipe():
   517          return Cfg.Const.as_single(Cfg.Const.PIPE)
   518  
   519      @staticmethod
   520      def transform_b64():
   521          return Cfg.Const.as_single(Cfg.Const.B64T)
   522  
   523      @staticmethod
   524      def transform_dns(n):
   525          if not isinstance(n, list):
   526              raise ValueError("dns: invalid names list")
   527          s = Setting(2)
   528          s[0] = Cfg.Const.DNS
   529          s[1] = len(n) & 0xFF
   530          for i in n:
   531              if not Utils.nes(i):
   532                  raise ValueError("dns: invalid name value")
   533              if len(i) > 0xFF:
   534                  i = i[:0xFF]
   535              s.append(len(i) & 0xFF)
   536              s += i.encode("UTF-8")
   537          return s
   538  
   539      @staticmethod
   540      def selector_random():
   541          return Cfg.Const.as_single(Cfg.Const.RANDOM)
   542  
   543      @staticmethod
   544      def connect_tls_ex(v):
   545          if not isinstance(v, int) or v <= 0 or v > 0xFF:
   546              raise ValueError("tls-ex: invalid version")
   547          s = Setting(2)
   548          s[0] = Cfg.Const.TLS_EX
   549          s[1] = v & 0xFF
   550          return s
   551  
   552      @staticmethod
   553      def wrap_xor(key=None):
   554          if isinstance(key, list) and len(key) > 0:
   555              key = key[0]
   556          if key is None:
   557              key = token_bytes(64)
   558          elif Utils.nes(key):
   559              key = key.encode("UTF-8")
   560          elif not isinstance(key, (bytes, bytearray)):
   561              raise ValueError("xor: invalid KEY value")
   562          n = len(key)
   563          if n > 0xFFFF:
   564              n = 0xFFFF
   565          s = Setting(3 + n)
   566          s[0] = Cfg.Const.XOR
   567          s[1] = (n >> 8) & 0xFF
   568          s[2] = n & 0xFF
   569          for x in range(0, n):
   570              s[x + 3] = key[x]
   571          del n
   572          return s
   573  
   574      @staticmethod
   575      def connect_tls_ca(v, ca):
   576          if isinstance(ca, (bytes, bytearray)):
   577              f = ca
   578          elif Utils.nes(ca):
   579              f = ca.encode("UTF-8")
   580          else:
   581              raise ValueError("tls-ca: invalid CA")
   582          if not isinstance(v, int) or v <= 0 or v > 0xFF:
   583              raise ValueError("tls-ca: invalid version")
   584          n = len(f)
   585          if n > 0xFFFF:
   586              n = 0xFFFF
   587          s = Setting(4 + n)
   588          s[0] = Cfg.Const.TLS_CA
   589          s[1] = v & 0xFF
   590          s[2] = (n >> 8) & 0xFF
   591          s[3] = n & 0xFF
   592          for x in range(0, n):
   593              s[x + 4] = f[x]
   594          del f, n
   595          return s
   596  
   597      @staticmethod
   598      def selector_last_valid():
   599          return Cfg.Const.as_single(Cfg.Const.LAST_VALID)
   600  
   601      @staticmethod
   602      def transform_b64_shift(v):
   603          if not isinstance(v, int) or v <= 0 or v > 0xFF:
   604              raise ValueError("base64s: invalid shift")
   605          s = Setting(2)
   606          s[0] = Cfg.Const.B64S
   607          s[1] = v & 0xFF
   608          return s
   609  
   610      @staticmethod
   611      def selector_round_robin():
   612          return Cfg.Const.as_single(Cfg.Const.ROUND_ROBIN)
   613  
   614      @staticmethod
   615      def selector_semi_random():
   616          return Cfg.Const.as_single(Cfg.Const.SEMI_RANDOM)
   617  
   618      @staticmethod
   619      def connect_tls_insecure():
   620          return Cfg.Const.as_single(Cfg.Const.TLS_INSECURE)
   621  
   622      @staticmethod
   623      def select_semi_round_robin():
   624          return Cfg.Const.as_single(Cfg.Const.SEMI_ROUND_ROBIN)
   625  
   626      @staticmethod
   627      def wrap_aes(key=None, iv=None):
   628          if isinstance(iv, list) and len(iv) > 0:
   629              iv = iv[0]
   630          if isinstance(key, list) and len(key) > 0:
   631              key = key[0]
   632          if key is None:
   633              key = token_bytes(32)
   634          elif Utils.nes(key):
   635              key = key.encode("UTF-8")
   636          elif not isinstance(key, (bytes, bytearray)):
   637              raise ValueError("aes: invalid KEY value")
   638          if iv is None:
   639              iv = token_bytes(16)
   640          elif Utils.nes(iv):
   641              iv = iv.encode("UTF-8")
   642          elif not isinstance(iv, (bytes, bytearray)):
   643              raise ValueError("aes: invalid IV value")
   644          if len(key) > 32:
   645              raise ValueError("aes: invalid KEY size")
   646          if len(iv) != 16:
   647              raise ValueError("aes: invalid IV size")
   648          s = Setting(3 + len(key) + len(iv))
   649          s[0] = Cfg.Const.AES
   650          s[1] = len(key) & 0xFF
   651          s[2] = len(iv) & 0xFF
   652          for x in range(0, len(key)):
   653              s[x + 3] = key[x]
   654          for x in range(0, len(iv)):
   655              s[x + len(key) + 3] = iv[x]
   656          return s
   657  
   658      @staticmethod
   659      def workhours(days, start, end):
   660          if not Utils.nes(days) and not Utils.nes(start) and not Utils.nes(end):
   661              raise ValueError("workhours: empty values specified")
   662          d = 0
   663          if Utils.nes(days):
   664              d = Utils.parse_weekdays(days)
   665          h, j = 0, 0
   666          if Utils.nes(start):
   667              if ":" not in start:
   668                  raise ValueError("workhours: invalid start format")
   669              x = start.split(":")
   670              if len(x) != 2:
   671                  raise ValueError("workhours: invalid start format")
   672              try:
   673                  h, j = int(x[0]), int(x[1])
   674              except ValueError:
   675                  raise ValueError("workhours: invalid start format")
   676              del x
   677              if h > 23 or j > 59:
   678                  raise ValueError("workhours: invalid start format")
   679          n, m = 0, 0
   680          if Utils.nes(end):
   681              if ":" not in end:
   682                  raise ValueError("workhours: invalid end format")
   683              x = end.split(":")
   684              if len(x) != 2:
   685                  raise ValueError("workhours: invalid end format")
   686              try:
   687                  n, m = int(x[0]), int(x[1])
   688              except ValueError:
   689                  raise ValueError("workhours: invalid end format")
   690              del x
   691              if n > 23 or m > 59:
   692                  raise ValueError("workhours: invalid end format")
   693          s = Setting(6)
   694          s[0] = Cfg.Const.WORKHOURS
   695          s[1] = d
   696          s[2] = h
   697          s[3] = j
   698          s[4] = n
   699          s[5] = m
   700          del d, h, j, n, m
   701          return s
   702  
   703      @staticmethod
   704      def connect_mtls(v, ca, pem, key):
   705          if isinstance(ca, (bytes, bytearray)):
   706              f = ca
   707          elif Utils.nes(ca):
   708              f = ca.encode("UTF-8")
   709          else:
   710              raise ValueError("mtls: invalid CA")
   711          if isinstance(pem, (bytes, bytearray)):
   712              if len(pem) == 0:
   713                  raise ValueError("mtls: invalid PEM")
   714              p = pem
   715          elif Utils.nes(pem):
   716              p = pem.encode("UTF-8")
   717          else:
   718              raise ValueError("mtls: invalid PEM")
   719          if isinstance(key, (bytes, bytearray)):
   720              if len(key) == 0:
   721                  raise ValueError("mtls: invalid KEY")
   722              k = key
   723          elif Utils.nes(key):
   724              k = key.encode("UTF-8")
   725          else:
   726              raise ValueError("mtls: invalid KEY")
   727          if not isinstance(v, int) or v <= 0 or v > 0xFF:
   728              raise ValueError("mtls invalid version")
   729          if len(p) == 0 or len(k) == 0:
   730              raise ValueError("mtls: invalid PEM or KEY version")
   731          o = len(f)
   732          if o > 0xFFFF:
   733              o = 0xFFFF
   734          n = len(p)
   735          if n > 0xFFFF:
   736              n = 0xFFFF
   737          m = len(k)
   738          if m > 0xFFFF:
   739              m = 0xFFFF
   740          s = Setting(8 + o + n + m)
   741          s[0] = Cfg.Const.MTLS
   742          s[1] = v & 0xFF
   743          s[2] = (o >> 8) & 0xFF
   744          s[3] = o & 0xFF
   745          s[2] = (n >> 8) & 0xFF
   746          s[3] = n & 0xFF
   747          s[4] = (m >> 8) & 0xFF
   748          s[5] = m & 0xFF
   749          for x in range(0, n):
   750              s[x + 8] = f[x]
   751          for x in range(0, n):
   752              s[x + o + 8] = p[x]
   753          for x in range(0, m):
   754              s[x + o + n + 8] = k[x]
   755          del f, p, k, o, n, m
   756          return s
   757  
   758      @staticmethod
   759      def connect_tls_cert(v, pem, key):
   760          if isinstance(pem, (bytes, bytearray)):
   761              if len(pem) == 0:
   762                  raise ValueError("tls-cert: invalid PEM")
   763              p = pem
   764          elif Utils.nes(pem):
   765              p = pem.encode("UTF-8")
   766          else:
   767              raise ValueError("tls-cert: invalid PEM")
   768          if isinstance(key, (bytes, bytearray)):
   769              if len(key) == 0:
   770                  raise ValueError("tls-cert: invalid KEY")
   771              k = key
   772          elif Utils.nes(key):
   773              k = key.encode("UTF-8")
   774          else:
   775              raise ValueError("tls-cert: invalid KEY")
   776          if not isinstance(v, int) or v <= 0 or v > 0xFF:
   777              raise ValueError("tls-cert: invalid version")
   778          if len(p) == 0 or len(k) == 0:
   779              raise ValueError("tls-cert: invalid PEM or KEY version")
   780          n = len(p)
   781          if n > 0xFFFF:
   782              n = 0xFFFF
   783          m = len(k)
   784          if m > 0xFFFF:
   785              m = 0xFFFF
   786          s = Setting(6 + n + m)
   787          s[0] = Cfg.Const.TLS_CA
   788          s[1] = v & 0xFF
   789          s[2] = (n >> 8) & 0xFF
   790          s[3] = n & 0xFF
   791          s[4] = (m >> 8) & 0xFF
   792          s[5] = m & 0xFF
   793          for x in range(0, n):
   794              s[x + 6] = p[x]
   795          for x in range(0, m):
   796              s[x + n + 6] = k[x]
   797          del p, k, n, m
   798          return s
   799  
   800      @staticmethod
   801      def connect_wc2(u, h, a, head=None):
   802          if Utils.nes(u):
   803              c = u.encode("UTF-8")
   804          else:
   805              c = bytearray()
   806          if Utils.nes(h):
   807              v = h.encode("UTF-8")
   808          else:
   809              v = bytearray()
   810          if Utils.nes(a):
   811              b = a.encode("UTF-8")
   812          else:
   813              b = bytearray()
   814          j = len(c)
   815          if j > 0xFFFF:
   816              j = 0xFFFF
   817          k = len(v)
   818          if k > 0xFFFF:
   819              k = 0xFFFF
   820          n = len(b)
   821          if n > 0xFFFF:
   822              n = 0xFFFF
   823          s = Setting(8 + j + k + n)
   824          s[0] = Cfg.Const.WC2
   825          s[1] = (j >> 8) & 0xFF
   826          s[2] = j & 0xFF
   827          s[3] = (k >> 8) & 0xFF
   828          s[4] = k & 0xFF
   829          s[5] = (n >> 8) & 0xFF
   830          s[6] = n & 0xFF
   831          for x in range(0, j):
   832              s[x + 8] = c[x]
   833          for x in range(0, k):
   834              s[x + j + 8] = v[x]
   835          for x in range(0, n):
   836              s[x + j + k + 8] = b[x]
   837          del j, k, n, c, v, b
   838          if not isinstance(head, dict):
   839              s[7] = 0
   840              return s
   841          i = 0
   842          s[7] = len(head) & 0xFF
   843          for k, v in head.items():
   844              if i >= 0xFF:
   845                  break
   846              if not Utils.nes(k):
   847                  raise ValueError("wc2: invalid header")
   848              if Utils.nes(v):
   849                  z = v.encode("UTF-8")
   850              else:
   851                  z = bytearray()
   852              o = k.encode("UTF-8")
   853              f = len(o)
   854              if f > 0xFF:
   855                  f = 0xFF
   856              g = len(z)
   857              if g > 0xFF:
   858                  g = 0xFF
   859              s.append(f & 0xFF)
   860              s.append(g & 0xFF)
   861              s.extend(o)
   862              s.extend(z)
   863              i += 1
   864              del o, z, f, g
   865          return s
   866  
   867      @staticmethod
   868      def wrap_cbk(a=None, b=None, c=None, d=None, size=128, key=None):
   869          if (
   870              not isinstance(a, int)
   871              and not isinstance(b, int)
   872              and not isinstance(c, int)
   873              and not isinstance(d, int)
   874          ):
   875              if Utils.nes(key):
   876                  v = key.encode("UTF-8")
   877              elif isinstance(key, (bytes, bytearray)):
   878                  v = key
   879              else:
   880                  v = token_bytes(64)
   881              if len(v) == 0:
   882                  v - token_bytes(64)
   883              h = sha512()
   884              for _ in range(0, 256):
   885                  h.update(v)
   886              del v
   887              n = crc32(h.digest()).to_bytes(4, byteorder="big", signed=False)
   888              del h
   889              a = n[0]
   890              b = n[1]
   891              c = n[2]
   892              d = n[3]
   893              del n
   894          if (
   895              not isinstance(a, int)
   896              or not isinstance(b, int)
   897              or not isinstance(c, int)
   898              or not isinstance(d, int)
   899              or a < 0
   900              or a > 0xFF
   901              or b < 0
   902              or b > 0xFF
   903              or c < 0
   904              or c > 0xFF
   905              or d < 0
   906              or d > 0xFF
   907          ):
   908              raise ValueError("cbk: invalid ABCD keys")
   909          if not isinstance(size, int) or size not in [16, 32, 64, 128]:
   910              raise ValueError("cbk: invalid size")
   911          s = Setting(6)
   912          s[0] = Cfg.Const.CBK
   913          s[1] = size & 0xFF
   914          s[2] = a
   915          s[3] = b
   916          s[4] = c
   917          s[5] = d
   918          return s
   919  
   920  
   921  class Utils:
   922      UNITS = {
   923          "ns": 1,
   924          "us": 1000,
   925          "µs": 1000,
   926          "μs": 1000,
   927          "ms": 1000000,
   928          "s": 1000000000,
   929          "m": 60000000000,
   930          "h": 3600000000000,
   931      }
   932  
   933      @staticmethod
   934      def dur_to_str(v):
   935          b = bytearray(32)
   936          n = len(b) - 1
   937          b[n] = ord("s")
   938          n, v = Utils._fmt_frac(b, n, v)
   939          n = Utils._fmt_int(b, n, v % 60)
   940          v /= 60
   941          if int(v) > 0:
   942              n -= 1
   943              b[n] = ord("m")
   944              n = Utils._fmt_int(b, n, v % 60)
   945              v /= 60
   946              if int(v) > 0:
   947                  n -= 1
   948                  b[n] = ord("h")
   949                  n = Utils._fmt_int(b, n, v)
   950          return b[n:].decode("UTF-8")
   951  
   952      @staticmethod
   953      def str_to_dur(s):
   954          if not Utils.nes(s):
   955              raise ValueError("str2dur: invalid duration")
   956          if s == "0":
   957              return 0
   958          d = 0
   959          while len(s) > 0:
   960              v = 0
   961              f = 0
   962              z = 1
   963              if not (s[0] == "." or (ord("0") <= ord(s[0]) and ord(s[0]) <= ord("9"))):
   964                  raise ValueError("str2dur: invalid duration")
   965              p = len(s)
   966              v, s = Utils._leading_int(s)
   967              r = p != len(s)
   968              y = False
   969              if len(s) > 0 and s[0] == ".":
   970                  s = s[1:]
   971                  p = len(s)
   972                  f, z, s = Utils._leading_fraction(s)
   973                  y = p != len(s)
   974              if not r and not y:
   975                  raise ValueError("str2dur: invalid duration")
   976              del r, y
   977              i = 0
   978              while i < len(s):
   979                  c = ord(s[i])
   980                  if c == ord(".") or (ord("0") <= c and c <= ord("9")):
   981                      break
   982                  i += 1
   983              if i == 0:
   984                  u = "s"
   985              else:
   986                  u = s[:i]
   987                  s = s[i:]
   988              del i
   989              if u not in Utils.UNITS:
   990                  raise ValueError("str2dur: unknown unit")
   991              e = Utils.UNITS[u]
   992              del u
   993              if v > (((1 << 63) - 1) / e):
   994                  raise ValueError("str2dur: invalid duration")
   995              v *= int(e)
   996              if f > 0:
   997                  v += int(float(f) * float(float(e) / float(z)))
   998                  if v < 0:
   999                      raise ValueError("str2dur: invalid duration")
  1000              del e
  1001              d += v
  1002              if d < 0:
  1003                  raise ValueError("str2dur: invalid duration")
  1004              del v, f, z
  1005          return d
  1006  
  1007      @staticmethod
  1008      def to_weekdays(v):
  1009          if not isinstance(v, (int, float)) or v == 0 or v > 126:
  1010              return "SMTWRFS"
  1011          r = ""
  1012          if v & 1 != 0:
  1013              r += "S"
  1014          if v & 2 != 0:
  1015              r += "M"
  1016          if v & 4 != 0:
  1017              r += "T"
  1018          if v & 8 != 0:
  1019              r += "W"
  1020          if v & 16 != 0:
  1021              r += "R"
  1022          if v & 32 != 0:
  1023              r += "F"
  1024          if v & 64 != 0:
  1025              r += "S"
  1026          return r
  1027  
  1028      @staticmethod
  1029      def _leading_int(s):
  1030          i = 0
  1031          x = 0
  1032          while i < len(s):
  1033              c = ord(s[i])
  1034              if c < ord("0") or c > ord("9"):
  1035                  break
  1036              if x > (((1 << 63) - 1) / 10):
  1037                  raise OverflowError()
  1038              x = int(x * 10) + int(c) - ord("0")
  1039              if x < 0:
  1040                  raise OverflowError()
  1041              i += 1
  1042          return x, s[i:]
  1043  
  1044      @staticmethod
  1045      def parse_weekdays(v):
  1046          if not Utils.nes(v):
  1047              return 0
  1048          d = 0
  1049          for x in range(0, len(v)):
  1050              if v[x] == "s" or v[x] == "S":
  1051                  if x == 0:
  1052                      d |= 1
  1053                  else:
  1054                      d |= 64
  1055              elif v[x] == "m" or v[x] == "M":
  1056                  d |= 2
  1057              elif v[x] == "t" or v[x] == "T":
  1058                  d |= 4
  1059              elif v[x] == "w" or v[x] == "W":
  1060                  d |= 8
  1061              elif v[x] == "r" or v[x] == "R":
  1062                  d |= 16
  1063              elif v[x] == "f" or v[x] == "F":
  1064                  d |= 32
  1065              else:
  1066                  raise ValueError("bad weekday char")
  1067          return d
  1068  
  1069      @staticmethod
  1070      def _fmt_int(b, s, v):
  1071          if int(v) == 0:
  1072              s -= 1
  1073              b[s] = ord("0")
  1074              return s
  1075          while int(v) > 0:
  1076              s -= 1
  1077              b[s] = int(v % 10) + ord("0")
  1078              v /= 10
  1079          return s
  1080  
  1081      @staticmethod
  1082      def split_dns_names(v):
  1083          n = list()
  1084          for e in v:
  1085              if not isinstance(e, list):
  1086                  raise ValueError("dns: invalid argument")
  1087              if len(e) == 0:
  1088                  continue
  1089              for s in e:
  1090                  if not Utils.nes(s):
  1091                      raise ValueError("dns: invalid value")
  1092                  if "," not in s:
  1093                      n.append(s)
  1094                      continue
  1095                  for x in s.split(","):
  1096                      h = x.strip()
  1097                      if len(h) > 0:
  1098                          n.append(h)
  1099                      del h
  1100          return n
  1101  
  1102      @staticmethod
  1103      def _fmt_frac(b, s, v):
  1104          p = False
  1105          for _ in range(0, 9):
  1106              d = v % 10
  1107              p = p or d != 0
  1108              if p:
  1109                  s -= 1
  1110                  b[s] = int(d) + ord("0")
  1111              v /= 10
  1112          if p:
  1113              s -= 1
  1114              b[s] = ord(".")
  1115          del p
  1116          return s, v
  1117  
  1118      @staticmethod
  1119      def read_file_input(v):
  1120          if v.strip() == "-" and not stdin.isatty():
  1121              if hasattr(stdin, "buffer"):
  1122                  b = stdin.buffer.read()
  1123              else:
  1124                  b = stdin.read()
  1125              stdin.close()
  1126          else:
  1127              with open(v, "rb") as f:
  1128                  b = f.read()
  1129          if len(b) == 0:
  1130              raise ValueError("input: empty input data")
  1131          return Config(b)
  1132  
  1133      @staticmethod
  1134      def public_key_hash(s):
  1135          if ":" not in s:
  1136              return int(s, 16)
  1137          if (len(s) + 1) % 3 != 0:
  1138              raise ValueError("public_key_hash: invalid padded key length")
  1139          v, h = s.split(":"), 2166136261
  1140          for i in v:
  1141              if len(i) != 2:
  1142                  raise ValueError(f'public_key_hash: invalid key entry "{i}"')
  1143              x = int(i, 16)
  1144              h *= 16777619
  1145              h = h & 0xFFFFFFFF
  1146              h ^= x
  1147              h = h & 0xFFFFFFFF
  1148              del x
  1149          del v
  1150          return h
  1151  
  1152      @staticmethod
  1153      def _leading_fraction(s):
  1154          i = 0
  1155          x = 0
  1156          v = 1
  1157          o = False
  1158          while i < len(s):
  1159              c = ord(s[i])
  1160              if c < ord("0") or c > ord("9"):
  1161                  break
  1162              if o:
  1163                  continue
  1164              if x > (((1 << 63) - 1) / 10):
  1165                  o = True
  1166                  continue
  1167              y = int(x * 10) + int(c) - ord("0")
  1168              if y < 0:
  1169                  o = True
  1170                  continue
  1171              x = y
  1172              v *= 10
  1173              i += 1
  1174              del y
  1175          del o
  1176          return x, v, s[1:]
  1177  
  1178      @staticmethod
  1179      def parse_wc2_headers(v):
  1180          if not isinstance(v, list) or len(v) == 0:
  1181              return None
  1182          d = dict()
  1183          for e in v:
  1184              Utils._parse_wc2_header(d, e, False)
  1185          if len(d) == 0:
  1186              return None
  1187          return d
  1188  
  1189      @staticmethod
  1190      def nes(s, min=0, max=-1):
  1191          if max > min:
  1192              return isinstance(s, str) and len(s) < max and len(s) > min
  1193          return isinstance(s, str) and len(s) > min
  1194  
  1195      @staticmethod
  1196      def _parse_wc2_header(d, e, r):
  1197          if isinstance(e, str):
  1198              if len(e) == 0 or "=" not in e:
  1199                  raise ValueError("wc2: invalid header")
  1200              p = e.find("=")
  1201              if p == 0 or p == len(e) - 1:
  1202                  raise ValueError("wc2: empty header")
  1203              d[e[:p].strip()] = e[p + 1 :].strip()
  1204              return
  1205          if isinstance(e, list) and len(e) > 0:
  1206              if r:
  1207                  raise ValueError("wc2: too many nested lists")
  1208              for v in e:
  1209                  Utils._parse_wc2_header(d, v, True)
  1210              return
  1211          raise ValueError("wc2: Invalid header")
  1212  
  1213      @staticmethod
  1214      def parse_tls(ca, pem, key, mtls, ver):
  1215          a = None
  1216          p = None
  1217          k = None
  1218          if isinstance(ca, str) and len(ca) > 0:
  1219              try:
  1220                  a = b64decode(ca, validate=True)
  1221              except ValueError:
  1222                  with open(ca, "rb") as f:
  1223                      a = f.read()
  1224          if isinstance(pem, str) and len(pem) > 0:
  1225              try:
  1226                  a = b64decode(pem, validate=True)
  1227              except ValueError:
  1228                  with open(pem, "rb") as f:
  1229                      p = f.read()
  1230          if isinstance(key, str) and len(key) > 0:
  1231              try:
  1232                  a = b64decode(key, validate=True)
  1233              except ValueError:
  1234                  with open(key, "rb") as f:
  1235                      k = f.read()
  1236          if mtls and (p is None or k is None or a is None):
  1237              raise ValueError("mtls: CA, PEM and KEY must be provided")
  1238          if (p is not None and k is None) or (k is not None and p is None):
  1239              raise ValueError("tls-cert: PEM and KEY must be provided")
  1240          if not isinstance(ver, int):
  1241              ver = 0
  1242          if a is None and p is None and k is None:
  1243              return Cfg.connect_tls_ex(ver)
  1244          if a is not None and p is None and k is None:
  1245              return Cfg.connect_tls_ca(ver, a)
  1246          if a is None:
  1247              return Cfg.connect_tls_certs(ver, p, k)
  1248          return Cfg.connect_mtls(ver, a, p, k)
  1249  
  1250      @staticmethod
  1251      def write_file_output(c, v, pretty, json):
  1252          f = stdout
  1253          if Utils.nes(v) and v != "-":
  1254              if not pretty and not json:
  1255                  f = open(v, "wb")
  1256              else:
  1257                  f = open(v, "w")
  1258          try:
  1259              if pretty or json:
  1260                  return print(
  1261                      dumps(c.json(), sort_keys=False, indent=(4 if pretty else None)),
  1262                      file=f,
  1263                  )
  1264              if f == stdout and not f.isatty():
  1265                  return f.buffer.write(c)
  1266              if f.mode == "wb":
  1267                  return f.write(c)
  1268              f.write(b64encode(c).decode("UTF-8"))
  1269          finally:
  1270              if f == stdout:
  1271                  print(end="")
  1272              else:
  1273                  f.close()
  1274              del f
  1275  
  1276  
  1277  class Config(bytearray):
  1278      __slots__ = ("_connector", "_transform")
  1279  
  1280      def __init__(self, b=None):
  1281          self._connector = False
  1282          self._transform = False
  1283          if isinstance(b, str) and len(b) > 0:
  1284              if b[0] == "[" and b[-1].strip() == "]":
  1285                  return self.parse(b)
  1286              return self.extend(b64decode(b, validate=True))
  1287          if isinstance(b, (bytes, bytearray)) and len(b) > 0:
  1288              if b[0] == 91 and b.decode("UTF-8", "ignore").strip()[-1] == "]":
  1289                  return self.parse(b.decode("UTF-8"))
  1290              return self.extend(b)
  1291  
  1292      def json(self):
  1293          i = 0
  1294          n = 0
  1295          e = list()
  1296          r = list()
  1297          while n >= 0 and n < len(self):
  1298              n = self.next(i)
  1299              if self[i] not in Cfg.Const.NAMES:
  1300                  raise ValueError(f"json: invalid setting id {self[i]}")
  1301              if self[i] == Cfg.Const.SEPARATOR:
  1302                  i = n
  1303                  if len(e) == 0:
  1304                      i = n
  1305                      continue
  1306                  if n == len(self):
  1307                      break
  1308                  r.append(e)
  1309                  e = list()
  1310                  continue
  1311              o = None
  1312              if Setting.is_single(self[i]):
  1313                  pass
  1314              elif self[i] == Cfg.Const.HOST:
  1315                  if i + 3 >= n:
  1316                      raise ValueError("host: invalid setting")
  1317                  v = (int(self[i + 2]) | int(self[i + 1]) << 8) + i
  1318                  if v > n or v < i:
  1319                      raise ValueError("host: invalid setting")
  1320                  o = self[i + 3 : v + 3].decode("UTF-8")
  1321                  del v
  1322              elif self[i] == Cfg.Const.SLEEP:
  1323                  if i + 8 >= n:
  1324                      raise ValueError("sleep: invalid setting")
  1325                  o = Utils.dur_to_str(
  1326                      (
  1327                          int(self[i + 8])
  1328                          | int(self[i + 7]) << 8
  1329                          | int(self[i + 6]) << 16
  1330                          | int(self[i + 5]) << 24
  1331                          | int(self[i + 4]) << 32
  1332                          | int(self[i + 3]) << 40
  1333                          | int(self[i + 2]) << 48
  1334                          | int(self[i + 1]) << 56
  1335                      )
  1336                  )
  1337              elif self[i] == Cfg.Const.KILLDATE:
  1338                  if i + 8 >= n:
  1339                      raise ValueError("killdate: invalid setting")
  1340                  u = (
  1341                      int(self[i + 8])
  1342                      | int(self[i + 7]) << 8
  1343                      | int(self[i + 6]) << 16
  1344                      | int(self[i + 5]) << 24
  1345                      | int(self[i + 4]) << 32
  1346                      | int(self[i + 3]) << 40
  1347                      | int(self[i + 2]) << 48
  1348                      | int(self[i + 1]) << 56
  1349                  )
  1350                  if u == 0:
  1351                      o = ""
  1352                  else:
  1353                      o = datetime.fromtimestamp(u).isoformat()
  1354              elif self[i] == Cfg.Const.KEYPIN:
  1355                  u = (
  1356                      int(self[i + 4])
  1357                      | int(self[i + 3]) << 8
  1358                      | int(self[i + 2]) << 16
  1359                      | int(self[i + 1]) << 24
  1360                  )
  1361                  o = hex(u)[2:].upper()
  1362              elif self[i] == Cfg.Const.WORKHOURS:
  1363                  if i + 5 >= n:
  1364                      raise ValueError("workhours: invalid setting")
  1365                  o = {
  1366                      "start_hour": self[i + 2],
  1367                      "start_min": self[i + 3],
  1368                      "end_hour": self[i + 4],
  1369                      "end_min": self[i + 5],
  1370                      "days": Utils.to_weekdays(self[i + 1]),
  1371                  }
  1372              elif (
  1373                  self[i] == Cfg.Const.IP
  1374                  or self[i] == Cfg.Const.B64S
  1375                  or self[i] == Cfg.Const.JITTER
  1376                  or self[i] == Cfg.Const.WEIGHT
  1377                  or self[i] == Cfg.Const.TLS_EX
  1378              ):
  1379                  if i + 1 >= n:
  1380                      raise ValueError("invalid setting")
  1381                  o = int(self[i + 1])
  1382              elif self[i] == Cfg.Const.WC2:
  1383                  if i + 7 >= n:
  1384                      raise ValueError("wc2: invalid setting")
  1385                  z = i + 8
  1386                  v = (int(self[i + 2]) | int(self[i + 1]) << 8) + i + 8
  1387                  if v > n or z > n or z < i or v < i:
  1388                      raise ValueError("wc2: invalid setting")
  1389                  o = dict()
  1390                  if v > z:
  1391                      o["url"] = self[z:v].decode("UTF-8")
  1392                  z = v
  1393                  v = (int(self[i + 4]) | int(self[i + 3]) << 8) + v
  1394                  if v > z:
  1395                      if v > n or z > n or v < z or z < i or v < i:
  1396                          raise ValueError("wc2: invalid setting")
  1397                      o["host"] = self[z:v].decode("UTF-8")
  1398                  z = v
  1399                  v = (int(self[i + 6]) | int(self[i + 5]) << 8) + v
  1400                  if v > z:
  1401                      if v > n or z > n or v < z or z < i or v < i:
  1402                          raise ValueError("wc2: invalid setting")
  1403                      o["agent"] = self[z:v].decode("UTF-8")
  1404                  if self[i + 7] > 0:
  1405                      o["headers"] = dict()
  1406                      j = 0
  1407                      while v < n and z < n and j < n:
  1408                          j = int(self[v]) + v + 2
  1409                          z = v + 2
  1410                          v = int(self[v + 1]) + j
  1411                          if (
  1412                              z == j
  1413                              or z > n
  1414                              or j > n
  1415                              or v > n
  1416                              or v < j
  1417                              or j < z
  1418                              or z < i
  1419                              or j < i
  1420                              or v < i
  1421                          ):
  1422                              raise ValueError("wc2: invalid header")
  1423                          o["headers"][self[z:j].decode("UTF-8")] = self[j:v].decode(
  1424                              "UTF-8"
  1425                          )
  1426                      del j
  1427                  del z, v
  1428              elif self[i] == Cfg.Const.MTLS:
  1429                  if i + 7 >= n:
  1430                      raise ValueError("mtls: invalid setting")
  1431                  a = (int(self[i + 3]) | int(self[i + 2]) << 8) + i + 8
  1432                  p = (int(self[i + 5]) | int(self[i + 4]) << 8) + a
  1433                  k = (int(self[i + 7]) | int(self[i + 6]) << 8) + p
  1434                  if a > n or p > n or k > n or p < a or k < p or a < i or p < i or k < i:
  1435                      raise ValueError("mtls: invalid setting")
  1436                  o = {"version": int(self[i + 1])}
  1437                  o["ca"] = b64encode(self[i + 8 : a]).decode("UTF-8")
  1438                  o["pem"] = b64encode(self[a:p]).decode("UTF-8")
  1439                  o["key"] = b64encode(self[p:k]).decode("UTF-8")
  1440                  del a, p, k
  1441              elif self[i] == Cfg.Const.TLS_CA:
  1442                  if i + 4 >= n:
  1443                      raise ValueError("tls-ca: invalid setting")
  1444                  a = (int(self[i + 3]) | int(self[i + 2]) << 8) + i + 4
  1445                  if a > n or a < i:
  1446                      raise ValueError("tls-ca: invalid setting")
  1447                  o = {"version": int(self[i + 1])}
  1448                  o["ca"] = b64encode(self[i + 4 : a]).decode("UTF-8")
  1449                  del a
  1450              elif self[i] == Cfg.Const.TLS_CERT:
  1451                  if i + 6 >= n:
  1452                      raise ValueError("tls-cert: invalid setting")
  1453                  p = (int(self[i + 3]) | int(self[i + 2]) << 8) + i + 6
  1454                  k = (int(self[i + 5]) | int(self[i + 4]) << 8) + p
  1455                  if p > n or k > n or p < i or k < i or k < p:
  1456                      raise ValueError("tls-cert: invalid setting")
  1457                  o = {"version": int(self[i + 1])}
  1458                  o["pem"] = b64encode(self[i + 6 : p]).decode("UTF-8")
  1459                  o["key"] = b64encode(self[p:k]).decode("UTF-8")
  1460                  del p, k
  1461              elif self[i] == Cfg.Const.XOR:
  1462                  if i + 3 >= n:
  1463                      raise ValueError("xor: invalid setting")
  1464                  k = (int(self[i + 2]) | int(self[i + 1]) << 8) + i
  1465                  if k > n or k < i:
  1466                      raise ValueError("xor: invalid setting")
  1467                  o = b64encode(self[i + 3 : k + 3]).decode("UTF-8")
  1468                  del k
  1469              elif self[i] == Cfg.Const.CBK:
  1470                  if i + 5 >= n:
  1471                      raise ValueError("cbk: invalid setting")
  1472                  o = {
  1473                      "size": int(self[i + 1]),
  1474                      "A": int(self[i + 2]),
  1475                      "B": int(self[i + 3]),
  1476                      "C": int(self[i + 4]),
  1477                      "D": int(self[i + 5]),
  1478                  }
  1479              elif self[i] == Cfg.Const.AES:
  1480                  if i + 3 >= n:
  1481                      raise ValueError("aes: invalid setting")
  1482                  v = int(self[i + 1]) + i + 3
  1483                  z = int(self[i + 2]) + v
  1484                  if v == z or i + 3 == v or v > n or z > n or z < i or v < i or z < v:
  1485                      raise ValueError("aes: invalid KEY/IV values")
  1486                  o = {
  1487                      "key": b64encode(self[i + 3 : v]).decode("UTF-8"),
  1488                      "iv": b64encode(self[v:z]).decode("UTF-8"),
  1489                  }
  1490                  del v, z
  1491              elif self[i] == Cfg.Const.DNS:
  1492                  if i + 1 >= n:
  1493                      raise ValueError("dns: invalid setting")
  1494                  o = list()
  1495                  v = i + 2
  1496                  z = v
  1497                  for _ in range(self[i + 1], 0, -1):
  1498                      v += int(self[v]) + 1
  1499                      if (
  1500                          z + 1 > v
  1501                          or z + 1 == v
  1502                          or v < z
  1503                          or v > n
  1504                          or z > n
  1505                          or z < i
  1506                          or v < i
  1507                      ):
  1508                          raise ValueError("dns: invalid name")
  1509                      o.append(self[z + 1 : v].decode("UTF-8"))
  1510                      z = v
  1511                  del v, z
  1512              y = {"type": Cfg.Const.NAMES[self[i]]}
  1513              if o is not None:
  1514                  y["args"] = o
  1515              del o
  1516              e.append(y)
  1517              i = n
  1518          if len(e) > 0:
  1519              r.append(e)
  1520          return r
  1521  
  1522      def add(self, s):
  1523          if not isinstance(s, Setting):
  1524              raise ValueError("add: cannot add a non-Settings object")
  1525          if not s._is_valid():
  1526              raise ValueError("add: invalid Settings object")
  1527          if s[0] == Cfg.Const.SEPARATOR:
  1528              self._connector = False
  1529              self._transform = False
  1530          if s._is_connector():
  1531              if self._connector:
  1532                  raise ValueError("add: attempted to add multiple Connection hints")
  1533              self._connector = True
  1534          if s._is_transform():
  1535              if self._transform:
  1536                  raise ValueError("add: attempted to add multiple Transforms")
  1537              self._transform = True
  1538          if s.single():
  1539              return self.append(s[0])
  1540          self += s
  1541          # for i in s:
  1542          #    self.append(i)
  1543  
  1544      def __str__(self):
  1545          i = 0
  1546          n = 0
  1547          b = StringIO()
  1548          while n >= 0 and n < len(self):
  1549              n = self.next(i)
  1550              if i > 0:
  1551                  b.write(",")
  1552              b.write(Setting(self[i:n]).__str__())
  1553              i = n
  1554          s = b.getvalue()
  1555          b.close()
  1556          del b
  1557          return s
  1558  
  1559      def read(self, b):
  1560          if not isinstance(b, (bytes, bytearray)):
  1561              raise ValueError("read: invalid raw type")
  1562          self.extend(b)
  1563  
  1564      def next(self, i):
  1565          if i > len(self) or i < 0:
  1566              return -1
  1567          if Setting.is_single(self[i]):
  1568              return i + 1
  1569          if (
  1570              self[i] == Cfg.Const.IP
  1571              or self[i] == Cfg.Const.B64S
  1572              or self[i] == Cfg.Const.JITTER
  1573              or self[i] == Cfg.Const.WEIGHT
  1574              or self[i] == Cfg.Const.TLS_EX
  1575          ):
  1576              return i + 2
  1577          if self[i] == Cfg.Const.CBK or self[i] == Cfg.Const.WORKHOURS:
  1578              return i + 6
  1579          if self[i] == Cfg.Const.SLEEP or self[i] == Cfg.Const.KILLDATE:
  1580              return i + 9
  1581          if self[i] == Cfg.Const.KEYPIN:
  1582              return i + 5
  1583          if self[i] == Cfg.Const.WC2:
  1584              if i + 7 >= len(self):
  1585                  return -1
  1586              n = (
  1587                  i
  1588                  + 8
  1589                  + (int(self[i + 2]) | int(self[i + 1]) << 8)
  1590                  + (int(self[i + 4]) | int(self[i + 3]) << 8)
  1591                  + (int(self[i + 6]) | int(self[i + 5]) << 8)
  1592              )
  1593              if self[i + 7] == 0 or n >= len(self):
  1594                  return n
  1595              for _ in range(self[i + 7], 0, -1):
  1596                  if n >= len(self) or n < 0:
  1597                      return -1
  1598                  n += int(self[n]) + int(self[n + 1]) + 2
  1599              return n
  1600          if self[i] == Cfg.Const.XOR or self[i] == Cfg.Const.HOST:
  1601              if i + 3 >= len(self):
  1602                  return -1
  1603              return i + 3 + int(self[i + 2]) | int(self[i + 1]) << 8
  1604          if self[i] == Cfg.Const.AES:
  1605              if i + 2 >= len(self):
  1606                  return -1
  1607              return i + 3 + int(self[i + 1]) + int(self[i + 2])
  1608          if self[i] == Cfg.Const.MTLS:
  1609              if i + 7 >= len(self):
  1610                  return -1
  1611              return (
  1612                  i
  1613                  + 8
  1614                  + (int(self[i + 3]) | int(self[i + 2]) << 8)
  1615                  + (int(self[i + 5]) | int(self[i + 4]) << 8)
  1616                  + (int(self[i + 7]) | int(self[i + 6]) << 8)
  1617              )
  1618          if self[i] == Cfg.Const.TLS_CA:
  1619              if i + 4 >= len(self):
  1620                  return -1
  1621              return i + 4 + int(self[i + 3]) | int(self[i + 2]) << 8
  1622          if self[i] == Cfg.Const.TLS_CERT:
  1623              if i + 6 >= len(self):
  1624                  return -1
  1625              return (
  1626                  i
  1627                  + 6
  1628                  + (int(self[i + 3]) | int(self[i + 2]) << 8)
  1629                  + (int(self[i + 5]) | int(self[i + 4]) << 8)
  1630              )
  1631          if self[i] == Cfg.Const.DNS:
  1632              if i + 1 >= len(self):
  1633                  return -1
  1634              n = i + 2
  1635              for _ in range(self[i + 1], 0, -1):
  1636                  n += int(self[n]) + 1
  1637              return n
  1638          return -1
  1639  
  1640      def parse(self, j):
  1641          v = loads(j)
  1642          if not isinstance(v, list):
  1643              raise ValueError("parse: invalid JSON value")
  1644          if len(v) == 0:
  1645              return
  1646          for x in range(0, len(v)):
  1647              if not isinstance(v[x], list):
  1648                  raise ValueError("parse: invalid JSON value")
  1649              for e in v[x]:
  1650                  self._parse_inner(e)
  1651              if x + 1 < len(v):
  1652                  self.add(Cfg.separator())
  1653          del v
  1654  
  1655      @staticmethod
  1656      def from_file(file):
  1657          with open(file, "rb") as f:
  1658              return Config(f.read())
  1659  
  1660      def _parse_inner(self, x):
  1661          if not isinstance(x, dict) or len(x) == 0:
  1662              raise ValueError("parse: invalid JSON value")
  1663          if "type" not in x or x["type"].lower() not in Cfg.Const.NAMES_TO_ID:
  1664              raise ValueError("parse: invalid JSON value")
  1665          m = Cfg.Const.NAMES_TO_ID[x["type"].lower()]
  1666          if m == Cfg.Const.SEPARATOR:
  1667              raise ValueError("parse: unexpected separator")
  1668          if Setting.is_single(m):
  1669              return self.add(Cfg.Const.as_single(m))
  1670          if "args" not in x:
  1671              raise ValueError("parse: invalid JSON payload")
  1672          p = x["args"]
  1673          if m == Cfg.Const.HOST:
  1674              if not Utils.nes(p):
  1675                  raise ValueError("host: invalid JSON value")
  1676              return self.add(Cfg.host(p))
  1677          if m == Cfg.Const.SLEEP:
  1678              if not Utils.nes(p):
  1679                  raise ValueError("sleep: invalid JSON value")
  1680              return self.add(Cfg.sleep(p))
  1681          if m == Cfg.Const.JITTER:
  1682              if not isinstance(p, int):
  1683                  raise ValueError("jitter: invalid JSON value")
  1684              if p < 0 or p > 100:
  1685                  raise ValueError("jitter: invalid JSON value")
  1686              return self.add(Cfg.jitter(p))
  1687          if m == Cfg.Const.WEIGHT:
  1688              if not isinstance(p, int):
  1689                  raise ValueError("weight: invalid JSON value")
  1690              if p < 0:
  1691                  raise ValueError("weight: invalid JSON value")
  1692              return self.add(Cfg.weight(p))
  1693          if m == Cfg.Const.KILLDATE:
  1694              if not Utils.nes(p):
  1695                  raise ValueError("killdate: invalid JSON value")
  1696              return self.add(Cfg.killdate(p))
  1697          if m == Cfg.Const.KEYPIN:
  1698              if not Utils.nes(p):
  1699                  raise ValueError("keypin: invalid JSON value")
  1700              return self.add(Cfg.key_pin(int(p, 16)))
  1701          if m == Cfg.Const.WORKHOURS:
  1702              if not isinstance(p, dict):
  1703                  raise ValueError("workhours: invalid JSON value")
  1704              h, j = p.get("start_hour"), p.get("start_min")
  1705              n, m = p.get("start_hour"), p.get("start_min")
  1706              if h is None:
  1707                  h = 0
  1708              elif not isinstance(h, (int, float)):
  1709                  raise ValueError("workhours: invalid JSON value")
  1710              if j is None:
  1711                  j = 0
  1712              elif not isinstance(j, (int, float)):
  1713                  raise ValueError("workhours: invalid JSON value")
  1714              if n is None:
  1715                  m = 0
  1716              elif not isinstance(n, (int, float)):
  1717                  raise ValueError("workhours: invalid JSON value")
  1718              if m is None:
  1719                  m = 0
  1720              elif not isinstance(m, (int, float)):
  1721                  raise ValueError("workhours: invalid JSON value")
  1722              if h > 23 or j > 59 or n > 23 or m > 59:
  1723                  raise ValueError("workhours: invalid JSON value")
  1724              d = Utils.parse_weekdays(p.get("days", ""))
  1725              s = Setting(6)
  1726              s[0] = Cfg.Const.WORKHOURS
  1727              s[1] = d
  1728              s[2] = h
  1729              s[3] = j
  1730              s[4] = n
  1731              s[5] = m
  1732              del d, h, j, n, m
  1733              return self.add(s)
  1734          if m == Cfg.Const.IP:
  1735              if not isinstance(p, int):
  1736                  raise ValueError("ip: invalid JSON value")
  1737              if p < 0:
  1738                  raise ValueError("ip: invalid JSON value")
  1739              return self.add(Cfg.connect_ip(p))
  1740          if m == Cfg.Const.WC2:
  1741              if not isinstance(p, dict):
  1742                  raise ValueError("wc2: invalid JSON value")
  1743              u = p.get("url")
  1744              h = p.get("host")
  1745              a = p.get("agent")
  1746              j = p.get("headers")
  1747              if j is not None and not isinstance(j, dict):
  1748                  raise ValueError("wc2: invalid JSON header value")
  1749              self.add(Cfg.connect_wc2(u, h, a, j))
  1750              del u, h, a, j
  1751              return
  1752          if m == Cfg.Const.TLS_EX:
  1753              if not isinstance(p, int) and p > 0:
  1754                  raise ValueError("tls-ex: invalid JSON value")
  1755              if p < 0:
  1756                  raise ValueError("tls-ex: invalid JSON value")
  1757              return self.add(Cfg.connect_tls_ex(p))
  1758          if m == Cfg.Const.MTLS:
  1759              if not isinstance(p, dict):
  1760                  raise ValueError("mtls: invalid JSON value")
  1761              a = p.get("ca")
  1762              y = p.get("pem")
  1763              k = p.get("key")
  1764              n = p.get("version", 0)
  1765              if not Utils.nes(y) or not Utils.nes(k):
  1766                  raise ValueError("mtls: invalid JSON PEM/KEY values")
  1767              if n is not None and not isinstance(n, int):
  1768                  raise ValueError("mtls: invalid JSON version value")
  1769              self.add(
  1770                  Cfg.connect_mtls(
  1771                      n,
  1772                      b64decode(a, validate=True),
  1773                      b64decode(y, validate=True),
  1774                      b64decode(k, validate=True),
  1775                  )
  1776              )
  1777              del a, y, k, n
  1778              return
  1779          if m == Cfg.Const.TLS_CA:
  1780              if not isinstance(p, dict):
  1781                  raise ValueError("tls-ca: invalid JSON value")
  1782              a = p.get("ca")
  1783              n = p.get("version", 0)
  1784              if n is not None and not isinstance(n, int):
  1785                  raise ValueError("tls-ca: invalid JSON version value")
  1786              self.add(Cfg.connect_tls_ca(n, b64decode(a, validate=True)))
  1787              del a, n
  1788              return
  1789          if m == Cfg.Const.TLS_CERT:
  1790              if not isinstance(p, dict):
  1791                  raise ValueError("tls-cert: invalid JSON value")
  1792              y = p.get("pem")
  1793              k = p.get("key")
  1794              n = p.get("version", 0)
  1795              if not Utils.nes(y) or not Utils.nes(k):
  1796                  raise ValueError("tls-cert: invalid JSON PEM/KEY values")
  1797              if n is not None and not isinstance(n, int):
  1798                  raise ValueError("tls-cert: invalid JSON version value")
  1799              self.add(
  1800                  Cfg.connect_tls_certs(
  1801                      n, b64decode(y, validate=True), b64decode(k, validate=True)
  1802                  )
  1803              )
  1804              del y, k, n
  1805              return
  1806          if m == Cfg.Const.XOR:
  1807              if not Utils.nes(p):
  1808                  raise ValueError("xor: invalid JSON value")
  1809              return self.add(Cfg.wrap_xor(b64decode(p, validate=True)))
  1810          if m == Cfg.Const.AES:
  1811              if not isinstance(p, dict):
  1812                  raise ValueError("aes: invalid JSON value")
  1813              y = p.get("iv")
  1814              k = p.get("key")
  1815              if not Utils.nes(y) or not Utils.nes(k):
  1816                  raise ValueError("aes: invalid JSON KEY/IV values")
  1817              self.add(
  1818                  Cfg.wrap_aes(b64decode(k, validate=True), b64decode(y, validate=True))
  1819              )
  1820              del y, k
  1821              return
  1822          if m == Cfg.Const.CBK:
  1823              if not isinstance(p, dict):
  1824                  raise ValueError("aes: invalid JSON value")
  1825              A = p.get("A")
  1826              B = p.get("B")
  1827              C = p.get("C")
  1828              D = p.get("D")
  1829              z = p.get("size", 128)
  1830              if not isinstance(A, int):
  1831                  raise ValueError("cbk: invalid JSON A value")
  1832              if not isinstance(B, int):
  1833                  raise ValueError("cbk: invalid JSON B value")
  1834              if not isinstance(C, int):
  1835                  raise ValueError("cbk: invalid JSON C value")
  1836              if not isinstance(D, int):
  1837                  raise ValueError("cbk: invalid JSON D value")
  1838              self.add(Cfg.wrap_cbk(a=A, b=B, c=C, d=D, size=z))
  1839              del z, A, B, C, D
  1840              return
  1841          if m == Cfg.Const.DNS:
  1842              if not isinstance(p, list):  # or len(p) == 0: Omit to allow empty DNS
  1843                  raise ValueError("dns: invalid JSON value")
  1844              return self.add(Cfg.transform_dns(p))
  1845          if m == Cfg.Const.B64S:
  1846              if not isinstance(p, int):
  1847                  raise ValueError("b64s: invalid JSON value")
  1848              if p < 0:
  1849                  raise ValueError("b64s: invalid JSON value")
  1850              self.add(Cfg.transform_b64_shift(p))
  1851              del p
  1852              return
  1853          raise ValueError(f'unhandled value type: {x["type"].lower()}')
  1854  
  1855  
  1856  class Setting(bytearray):
  1857      def __str__(self):
  1858          if len(self) == 0 or self[0] == 0:
  1859              return "<invalid>"
  1860          if self[0] not in Cfg.Const.NAMES:
  1861              return "<invalid>"
  1862          return Cfg.Const.NAMES[self[0]]
  1863  
  1864      @staticmethod
  1865      def is_single(v):
  1866          if v == 0:
  1867              return False
  1868          if v == Cfg.Const.B64T or v == Cfg.Const.SEPARATOR:
  1869              return True
  1870          if v >= Cfg.Const.LAST_VALID and v <= Cfg.Const.SEMI_RANDOM:
  1871              return True
  1872          if v >= Cfg.Const.TCP and v <= Cfg.Const.TLS_INSECURE:
  1873              return True
  1874          if v >= Cfg.Const.HEX and v <= Cfg.Const.B64:
  1875              return True
  1876          return False
  1877  
  1878      def single(self):
  1879          if len(self) == 0 or self[0] == 0:
  1880              return False
  1881          return Setting.is_single(self[0])
  1882  
  1883      def _is_valid(self):
  1884          return len(self) > 0 and self[0] > 0
  1885  
  1886      def _is_connector(self):
  1887          if len(self) == 0 or self[0] == 0:
  1888              return False
  1889          if self[0] >= Cfg.Const.IP and self[0] <= Cfg.Const.TLS_CERT:
  1890              return True
  1891          return self[0] >= Cfg.Const.TCP and self[0] <= Cfg.Const.TLS_INSECURE
  1892  
  1893      def _is_transform(self):
  1894          if len(self) == 0 or self[0] == 0:
  1895              return False
  1896          return self[0] >= Cfg.Const.B64T and self[0] <= Cfg.Const.B64S
  1897  
  1898  
  1899  class _Builder(ArgumentParser):
  1900      def __init__(self):
  1901          ArgumentParser.__init__(self, description="XMT c2.Config Tool")
  1902          self.add_argument(
  1903              nargs="?",
  1904              dest="action",
  1905              default="",
  1906              metavar="action",
  1907              choices=["", "add", "append"],
  1908          )
  1909          self.add_argument("-j", "--json", dest="json", action="store_true")
  1910          self.add_argument("-p", "--print", dest="print", action="store_true")
  1911          self.add_argument("-I", "--stdin", dest="stdin", action="store_true")
  1912  
  1913          self.add_argument("-f", "--in", type=str, dest="input")
  1914          self.add_argument("-o", "--out", type=str, dest="output")
  1915  
  1916          self.add_argument("-T", "--host", type=str, dest="host")
  1917          self.add_argument("-S", "--sleep", type=str, dest="sleep")
  1918          self.add_argument("-J", "--jitter", type=int, dest="jitter")
  1919          self.add_argument("-W", "--weight", type=int, dest="weight")
  1920          self.add_argument(
  1921              "-X",
  1922              "--selector",
  1923              type=str,
  1924              dest="selector",
  1925              default=None,
  1926              choices=[
  1927                  "last",
  1928                  "random",
  1929                  "round-robin",
  1930                  "semi-random",
  1931                  "semi-round-robin",
  1932              ],
  1933          )
  1934  
  1935          self.add_argument("--killdate", type=str, dest="killdate")
  1936          self.add_argument("--wh-days", type=str, dest="workhours_days")
  1937          self.add_argument("--wh-start", type=str, dest="workhours_start")
  1938          self.add_argument("--wh-end", type=str, dest="workhours_end")
  1939          self.add_argument(
  1940              "-P",
  1941              "--keypin",
  1942              nargs="+",
  1943              type=str,
  1944              dest="key_pin",
  1945              action="append",
  1946              default=None,
  1947          )
  1948  
  1949          c = self.add_mutually_exclusive_group(required=False)
  1950          c.add_argument("--tcp", dest="tcp", action="store_true")
  1951          c.add_argument("--tls", dest="tls", action="store_true")
  1952          c.add_argument("--udp", dest="udp", action="store_true")
  1953          c.add_argument("--ip", type=int, dest="ip", default=None)
  1954          c.add_argument("--icmp", dest="icmp", action="store_true")
  1955          c.add_argument("--pipe", dest="pipe", action="store_true")
  1956          c.add_argument("-K", "--tls-insecure", dest="tls_insecure", action="store_true")
  1957          del c
  1958  
  1959          self.add_argument("--wc2-url", type=str, dest="wc2_url", default=None)
  1960          self.add_argument("--wc2-host", type=str, dest="wc2_host", default=None)
  1961          self.add_argument("--wc2-user", type=str, dest="wc2_agent", default=None)
  1962          self.add_argument("--wc2-server", dest="wc2_server", action="store_true")
  1963          self.add_argument(
  1964              "-H",
  1965              "--wc2_header",
  1966              nargs="+",
  1967              type=str,
  1968              dest="wc2_headers",
  1969              action="append",
  1970              default=None,
  1971          )
  1972          self.add_argument("--mtls", dest="mtls", action="store_true")
  1973          self.add_argument("--tls-ca", type=str, dest="tls_ca", default=None)
  1974          self.add_argument("--tls-ver", type=int, dest="tls_ver", default=None)
  1975          self.add_argument("--tls-pem", type=str, dest="tls_pem", default=None)
  1976          self.add_argument("--tls-key", type=str, dest="tls_key", default=None)
  1977  
  1978          self.add_argument("--hex", dest="hex", action="store_true")
  1979          self.add_argument("--b64", dest="b64", action="store_true")
  1980          self.add_argument("--zlib", dest="zlib", action="store_true")
  1981          self.add_argument("--gzip", dest="gzip", action="store_true")
  1982          self.add_argument("--xor", nargs="?", type=str, dest="xor", default=None)
  1983          self.add_argument("--cbk", nargs="?", type=str, dest="cbk", default=None)
  1984          self.add_argument("--aes", nargs="?", type=str, dest="aes", default=None)
  1985  
  1986          self.add_argument("--aes-iv", nargs="?", type=str, dest="aes_iv", default=None)
  1987          self.add_argument(
  1988              "--b64t", nargs="?", type=int, dest="b64t", action="append", default=None
  1989          )
  1990          self.add_argument(
  1991              "-D",
  1992              "--dns",
  1993              nargs="*",
  1994              type=str,
  1995              dest="dns",
  1996              action="append",
  1997              default=None,
  1998          )
  1999  
  2000      def run(self):
  2001          a = self.parse_args()
  2002          e = Utils.nes(a.action) and a.action[0] == "a"
  2003          if e and Utils.nes(a.input) and not Utils.nes(a.output) and a.input != "-":
  2004              a.output = a.input
  2005          if e and not Utils.nes(a.input) and Utils.nes(a.output):
  2006              a.input = a.output
  2007          if a.input:
  2008              c = Utils.read_file_input(a.input)
  2009          else:
  2010              c = Config()
  2011          if a.stdin and a.input != "-":
  2012              if stdin.isatty():
  2013                  raise ValueError("stdin: no input found")
  2014              if hasattr(stdin, "buffer"):
  2015                  b = stdin.buffer.read().decode("UTF-8")
  2016              else:
  2017                  b = stdin.read()
  2018              stdin.close()
  2019              for v in b.split("\n"):
  2020                  x = split(v)
  2021                  _Builder.build(c, super(__class__, self).parse_args(x), True, x)
  2022                  del x
  2023          elif not Utils.nes(a.output) or (not a.print and not a.json):
  2024              _Builder.build(c, a, e, argv)
  2025          if len(c) == 0:
  2026              return
  2027          Utils.write_file_output(c, a.output, a.print, a.json)
  2028          del e, a, c
  2029  
  2030      @staticmethod
  2031      def _organize(args):
  2032          a = False
  2033          w = list()
  2034          d = dict()
  2035          for i in range(0, len(args)):
  2036              if len(args[i]) < 3:
  2037                  continue
  2038              if args[i][0] != "-":
  2039                  continue
  2040              if args[i].lower() == "--aes":
  2041                  if a:
  2042                      continue
  2043                  a = True
  2044              if args[i].lower() == "--aes-iv":
  2045                  if a:
  2046                      continue
  2047                  v = "aes"
  2048                  a = True
  2049              else:
  2050                  v = args[i].lower()[2:]
  2051              if v not in Cfg.Const.WRAPPERS:
  2052                  continue
  2053              if v in d:
  2054                  raise ValueError('duplicate argument "--{v}" found')
  2055              w.append(v)
  2056              d[v] = len(w) - 1
  2057          e = [None] * len(w)
  2058          del w, a
  2059          return d, e
  2060  
  2061      def parse_args(self):
  2062          if len(argv) <= 1:
  2063              return self.print_help()
  2064          return super(__class__, self).parse_args()
  2065  
  2066      def print_help(self, file=None):
  2067          print(HELP_TEXT.format(binary=argv[0]), file=file)
  2068          exit(2)
  2069  
  2070      @staticmethod
  2071      def build(config, args, add, arv):
  2072          if add and len(config) > 0:
  2073              config.add(Cfg.separator())
  2074          p, w = _Builder._organize(arv)
  2075          if args.host:
  2076              config.add(Cfg.host(args.host))
  2077          if args.sleep:
  2078              config.add(Cfg.sleep(args.sleep))
  2079          if isinstance(args.jitter, int):
  2080              config.add(Cfg.jitter(args.jitter))
  2081          if isinstance(args.weight, int):
  2082              config.add(Cfg.weight(args.weight))
  2083          if args.killdate:
  2084              config.add(Cfg.killdate(args.killdate))
  2085          if args.workhours_days or args.workhours_start or args.workhours_end:
  2086              config.add(
  2087                  Cfg.workhours(
  2088                      args.workhours_days, args.workhours_start, args.workhours_end
  2089                  )
  2090              )
  2091          if args.key_pin and len(args.key_pin) > 0:
  2092              for i in args.key_pin:
  2093                  if isinstance(i, str):
  2094                      config.add(Cfg.key_pin(i))
  2095                  if not isinstance(i, list):
  2096                      continue
  2097                  for x in i:
  2098                      config.add(Cfg.key_pin(x))
  2099          if args.selector:
  2100              if args.selector == "last":
  2101                  config.add(Cfg.selector_last_valid())
  2102              elif args.selector == "random":
  2103                  config.add(Cfg.selector_random())
  2104              elif args.selector == "round-robin":
  2105                  config.add(Cfg.selector_round_robin())
  2106              elif args.selector == "semi-random":
  2107                  config.add(Cfg.selector_semi_random())
  2108              elif args.selector == "semi-round-robin":
  2109                  config.add(Cfg.selector_semi_round_robin())
  2110              else:
  2111                  raise ValueError("selector: invalid value")
  2112          if args.tcp:
  2113              config.add(Cfg.connect_tcp())
  2114          if args.tls:
  2115              config.add(Cfg.connect_tls())
  2116          if args.udp:
  2117              config.add(Cfg.connect_udp())
  2118          if args.icmp:
  2119              config.add(Cfg.connect_icmp())
  2120          if args.pipe:
  2121              config.add(Cfg.connect_pipe())
  2122          if args.tls_insecure:
  2123              config.add(Cfg.connect_tls_insecure())
  2124          if isinstance(args.ip, int):
  2125              config.add(Cfg.connect_ip(args.ip))
  2126          if args.wc2_url or args.wc2_host or args.wc2_agent or args.wc2_headers:
  2127              config.add(
  2128                  Cfg.connect_wc2(
  2129                      args.wc2_url,
  2130                      args.wc2_host,
  2131                      args.wc2_agent,
  2132                      Utils.parse_wc2_headers(args.wc2_headers),
  2133                  )
  2134              )
  2135          if args.tls_ca or args.tls_pem or args.tls_key:
  2136              config.add(
  2137                  Utils.parse_tls(
  2138                      args.tls_ca, args.tls_pem, args.tls_key, args.mtls, args.tls_ver
  2139                  )
  2140              )
  2141          elif args.mtls:
  2142              raise ValueError("mtls: missing CA, PEM and KEY values")
  2143          elif isinstance(args.tls_ver, int):
  2144              config.add(Cfg.connect_tls_ex(args.tls_ver))
  2145          if args.b64t and len(args.b64t) == 1:
  2146              if args.b64t[0] is None:
  2147                  config.add(Cfg.transform_b64())
  2148              else:
  2149                  config.add(Cfg.transform_b64_shift(int(args.b64t[0])))
  2150          if args.dns and len(args.dns) > 0:
  2151              if len(args.dns) == 1 and len(args.dns[0]) == 0:
  2152                  config.add(Cfg.transform_dns([]))
  2153              else:
  2154                  config.add(Cfg.transform_dns(Utils.split_dns_names(args.dns)))
  2155          if args.hex:
  2156              w[p["hex"]] = Cfg.wrap_hex()
  2157          if args.zlib:
  2158              w[p["zlib"]] = Cfg.wrap_zlib()
  2159          if args.gzip:
  2160              w[p["gzip"]] = Cfg.wrap_gzip()
  2161          if args.b64:
  2162              w[p["b64"]] = Cfg.wrap_b64()
  2163          if args.xor:
  2164              w[p["xor"]] = Cfg.wrap_xor(args.xor[0])
  2165          elif "xor" in p:
  2166              w[p["xor"]] = Cfg.wrap_xor()
  2167          if args.cbk:
  2168              w[p["cbk"]] = Cfg.wrap_cbk(key=args.cbk[0])
  2169          elif "cbk" in p:
  2170              w[p["cbk"]] = Cfg.wrap_cbk()
  2171          if args.aes or args.aes_iv:
  2172              w[p["aes"]] = Cfg.wrap_aes(args.aes, args.aes_iv)
  2173          elif "aes" in p:
  2174              w[p["aes"]] = Cfg.wrap_aes()
  2175          for i in w:
  2176              config.add(i)
  2177          del w, p
  2178  
  2179  
  2180  if __name__ == "__main__":
  2181      try:
  2182          _Builder().run()
  2183      except Exception as err:
  2184          print(f"Error: {err}\n{format_exc(3)}", file=stderr)
  2185          exit(1)