Initial commit
This commit is contained in:
commit
e5a2fb87e1
154
.gitignore
vendored
Normal file
154
.gitignore
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# vistawire-py
|
||||
|
||||
VistA RPC wire protocol implementation in Python.
|
||||
|
||||
## Test client
|
||||
|
||||
```shell
|
||||
vistawire.py host port
|
||||
```
|
17
XUIAMSSOi.py
Normal file
17
XUIAMSSOi.py
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import ctypes
|
||||
|
||||
# Load DLL
|
||||
XUIAMSSOi = ctypes.WinDLL('C:\\Program Files (x86)\\Micro Focus\\Reflection\\XUIAMSSOi.dll')
|
||||
XUIAMSSOi.MySsoTokenVBA.restype = ctypes.c_long
|
||||
XUIAMSSOi.MySsoTokenVBA.argtypes = (ctypes.c_wchar_p, ctypes.c_long)
|
||||
|
||||
# Authenticate against smartcard
|
||||
def MySsoTokenVBA(bufsize: int=15000) -> str:
|
||||
buf = ctypes.create_unicode_buffer(bufsize)
|
||||
sz = XUIAMSSOi.MySsoTokenVBA(buf, bufsize)
|
||||
if sz <= bufsize:
|
||||
return buf.value.encode('utf-16')[2:].decode('latin-1')
|
||||
else:
|
||||
return MySsoTokenVBA(sz)
|
355
vistawire.py
Normal file
355
vistawire.py
Normal file
@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import math
|
||||
import random
|
||||
import socket
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from typing import Any, Union, Iterable, Generator, Tuple, Sequence, Callable, Optional
|
||||
|
||||
# protocol token [XWB]VTER: [XWB] = NS broker [XWB], V = version 1, T = type 1, E = envelope size 3, R = XWBPRT 0
|
||||
RPCRequest = namedtuple('RPCRequest', ('header', 'version', 'type', 'envelope', 'rt', 'command', 'broker', 'method', 'params', 'encoding'))
|
||||
|
||||
bEOT = b'\x04'
|
||||
|
||||
class RPCExc(RuntimeError): pass
|
||||
class RPCExcFormat(ValueError, RPCExc): pass
|
||||
class RPCExcServerError(RPCExc): pass
|
||||
class RPCExcInvalidResult(RPCExc): pass
|
||||
|
||||
class RPCType(object):
|
||||
LITERAL = b'0'
|
||||
REFERENCE = b'1'
|
||||
LIST = b'2'
|
||||
GLOBAL = b'3'
|
||||
EMPTY = b'4'
|
||||
STREAM = b'5'
|
||||
def __init__(self, value: Any, magic: bytes=None):
|
||||
self.magic = magic
|
||||
self.value = value.value if isinstance(value, RPCType) else value
|
||||
|
||||
class RPCDict(dict):
|
||||
def __init__(self, iterable: Iterable, magic: bytes=RPCType.LIST):
|
||||
dict.__init__(self, iterable)
|
||||
self.magic = magic
|
||||
|
||||
class RPCList(list):
|
||||
def __init__(self, iterable: Iterable, magic: bytes=RPCType.LIST, start: int=1):
|
||||
list.__init__(self, iterable)
|
||||
self.magic = magic
|
||||
self.start = start
|
||||
def __add__(self, rhs: list) -> 'RPCList':
|
||||
return RPCList(list.__add__(self, rhs), magic=self.magic, start=self.start)
|
||||
|
||||
class StatefulClient(object):
|
||||
def __init__(self, sock: socket.socket, end: bytes=bEOT, minsz: int=1024, maxsz: int=32768, encoding: str='latin-1'):
|
||||
self._rpc = client(sock, end=end, minsz=minsz, maxsz=maxsz)
|
||||
self._sock = sock
|
||||
self._context = 'XUS SIGNON'
|
||||
self._encoding = encoding
|
||||
def __call__(self, method: Union[str, bytes], *params: Any, throw: bool=True) -> Any:
|
||||
if isinstance(method, bytes):
|
||||
try:
|
||||
res = self._rpc(method)
|
||||
if res == b'\x00\x001':
|
||||
request = load(method, encoding=self._encoding)
|
||||
if request.method == 'XWB CREATE CONTEXT':
|
||||
self._context = decrypt(request.params[0])
|
||||
except Exception as ex:
|
||||
warnings.warn(f"Error executing {method}: {ex}")
|
||||
return res
|
||||
if (fn := getattr(self, (method := method.strip(' _:')), None)):
|
||||
return fn(*params, context=context, restore=restore)
|
||||
if (method := method.replace('_', ' ').upper()) == 'XWB CREATE CONTEXT':
|
||||
res = r_unpack(self._rpc(dump(method, *params, encoding=self._encoding)), throw=throw, encoding=self._encoding)
|
||||
if res == '1':
|
||||
self._context = decrypt(params[0])
|
||||
return res
|
||||
return r_unpack(self._rpc(dump(method, *params, encoding=self._encoding)), throw=throw, encoding=self._encoding)
|
||||
def setcontext(self, *contexts: str):
|
||||
if len(contexts) > 0 and self._context not in contexts:
|
||||
for context in contexts:
|
||||
if r_unpack(self._rpc(dump('XWB CREATE CONTEXT', encrypt_trivial(context), encoding=self._encoding)), encoding=self._encoding) == '1':
|
||||
self._context = context
|
||||
return context
|
||||
@contextmanager
|
||||
def context(self):
|
||||
try:
|
||||
yield (ctx := self._context)
|
||||
finally:
|
||||
if self._context != ctx:
|
||||
res = r_unpack(self._rpc(dump('XWB CREATE CONTEXT', encrypt_trivial(ctx), encoding=self._encoding)), encoding=self._encoding)
|
||||
if res != '1':
|
||||
raise RPCExcInvalidResult('XWB CREATE CONTEXT', ctx, res)
|
||||
self._context = ctx
|
||||
|
||||
def load(data: bytes, encoding: str='latin-1') -> RPCRequest:
|
||||
if data.startswith(b'['):
|
||||
offset = data.index(b']') + 1
|
||||
header = data[:offset]
|
||||
version = data[offset: offset + 1]
|
||||
type = data[offset + 1: offset + 2]
|
||||
envelope = int(chr(data[offset + 2]))
|
||||
rt = data[offset + 3: offset + 4]
|
||||
command = data[offset + 4] == 0x34 # ord(b'4')
|
||||
offset += 5
|
||||
broker = None
|
||||
if not command:
|
||||
broker, offset = s_unpack(data, offset, encoding=encoding)
|
||||
method, offset = s_unpack(data, offset, encoding=encoding)
|
||||
params = None
|
||||
if offset < (sz := len(data)):
|
||||
if data[offset] != 0x35:
|
||||
raise RPCExcFormat(f"Char '5' expected at #{offset}, got: {chr(data[offset])}") # ord(b'5')
|
||||
offset += 1
|
||||
params = []
|
||||
while offset < sz and data[offset: offset + 2] != b'4f':
|
||||
value, offset = l_unpack(data, offset, envelope=envelope, encoding=encoding)
|
||||
params.append(value)
|
||||
params = tuple(params)
|
||||
return RPCRequest(header=header, version=version, type=type, envelope=envelope, rt=rt, command=command, broker=broker, method=method, params=params, encoding=encoding)
|
||||
raise RPCExcFormat(f"Invalid format {data}")
|
||||
|
||||
def dump(method: Union[str, RPCRequest], *params: Any, header: bytes=b'[XWB]', version: bytes=b'1', type: bytes=b'1', envelope: int=0, rt: bytes=b'0', command: bool=False, broker: str='0', encoding: str='latin-1') -> bytes:
|
||||
if isinstance(method, RPCRequest):
|
||||
params = method.params
|
||||
header = method.header
|
||||
version = method.version
|
||||
type = method.type
|
||||
envelope = method.envelope
|
||||
rt = method.rt
|
||||
command = method.command
|
||||
broker = method.broker
|
||||
encoding = method.encoding
|
||||
method = method.method
|
||||
envelope = max(3, math.ceil(math.log10(max(1, max(l_pack_maxlen(arg, encoding=encoding) for arg in params)))) if envelope < 1 and params is not None and len(params) else envelope)
|
||||
return header + version + type + str(envelope).encode(encoding) + rt + (b'4' if command else (b'2' + s_pack(broker, encoding=encoding))) + s_pack(method, encoding=encoding) + ((b'5' + (b''.join(l_pack(param, envelope=envelope, encoding=encoding) for param in params) if len(params) > 0 else b'4f')) if params is not None else b'')
|
||||
|
||||
def s_pack(value: str, encoding: str='latin-1') -> bytes:
|
||||
encoded = value.encode(encoding)
|
||||
if len(encoded) <= 255:
|
||||
return bytes((len(encoded),)) + encoded
|
||||
raise RPCExcFormat('cannot s-pack string longer than 255 bytes: ' + repr(value))
|
||||
|
||||
def l_pack(value: Any, envelope: int=3, wrapped: bool=True, magic: Optional[bytes]=None, encoding: str='latin-1') -> bytes:
|
||||
if isinstance(value, dict):
|
||||
bare = b't'.join(l_pack(k, envelope=envelope, wrapped=False, encoding=encoding) + l_pack(v, envelope=envelope, wrapped=False, encoding=encoding) for k, v in value.items())
|
||||
return ((magic or getattr(value, 'magic', b'2')) + bare + b'f') if wrapped else bare
|
||||
elif not isinstance(value, str) and hasattr(value, '__iter__'):
|
||||
bare = b't'.join(l_pack(k, envelope=envelope, wrapped=False, encoding=encoding) + l_pack(v, envelope=envelope, wrapped=False, encoding=encoding) for k, v in enumerate(value, start=getattr(value, 'start', 1)))
|
||||
return ((magic or getattr(value, 'magic', b'2')) + bare + b'f') if wrapped else bare
|
||||
elif isinstance(value, RPCType):
|
||||
return l_pack(value.value, envelope=envelope, magic=value.magic, encoding=encoding)
|
||||
else:
|
||||
encoded = str(value).encode(encoding)
|
||||
if len(encoded) <= 10**envelope - 1:
|
||||
bare = str(len(encoded)).zfill(envelope).encode(encoding) + encoded
|
||||
return ((magic or b'0') + bare + b'f') if wrapped else bare
|
||||
raise RPCExcFormat(f'cannot l-pack string longer than {10**envelope - 1} bytes with an envelope of {envelope}: ' + repr(value))
|
||||
|
||||
def l_pack_maxlen(value: Any, encoding: str='latin-1') -> int:
|
||||
if isinstance(value, dict):
|
||||
return max(max(l_pack_maxlen(k, encoding=encoding) for k in value.keys()), max(l_pack_maxlen(v, encoding=encoding) for v in value.values())) if len(value) > 0 else 0
|
||||
elif not isinstance(value, str) and hasattr(value, '__iter__'):
|
||||
return max(len(str(len(value))), max(l_pack_maxlen(v, encoding=encoding) for v in value)) if len(value) > 0 else 0
|
||||
else:
|
||||
return len(str(value).encode(encoding))
|
||||
|
||||
def s_unpack(data: bytes, offset: int=0, encoding='latin-1') -> Tuple[str, int]:
|
||||
n = data[offset]
|
||||
return data[offset + 1: (end := offset + 1 + n)].decode(encoding), end
|
||||
|
||||
def l_unpack(data: bytes, offset: int=0, envelope: int=3, bare: bool=False, encoding='latin-1') -> Tuple[Any, int]:
|
||||
if bare:
|
||||
n = int(data[offset: offset + envelope].decode(encoding))
|
||||
return data[offset + envelope: (end := offset + envelope + n)].decode(encoding), end
|
||||
magic = data[offset: offset + 1]
|
||||
if magic == b'0':
|
||||
value, offset = l_unpack(data, offset + 1, envelope=envelope, bare=True, encoding=encoding)
|
||||
if data[offset] == 0x66: # ord(b'f')
|
||||
return value, offset + 1
|
||||
raise RPCExcFormat(f"Char 'f' expected, got: {chr(data[offset])}")
|
||||
elif magic in b'23':
|
||||
res = {}
|
||||
while True:
|
||||
key, offset = l_unpack(data, offset + 1, envelope=envelope, bare=True, encoding=encoding)
|
||||
if data[offset] in b'012345':
|
||||
value, offset = l_unpack(data, offset, envelope=envelope, bare=True, encoding=encoding)
|
||||
res[key] = value
|
||||
else:
|
||||
warnings.warn(f"Invalid list in {data}")
|
||||
if (suffix := data[offset]) == 0x66: # ord(b'f')
|
||||
return dictlist(res, magic=magic), offset + 1
|
||||
elif suffix != 0x74: # ord(b't')
|
||||
raise RPCExcFormat(f"Char 't' or 'f' expected, got: {chr(data[offset])}")
|
||||
raise RPCExcFormat(f"Unknown data type {magic}")
|
||||
|
||||
def dictlist(value: dict, start: Optional[int]=None, magic: bytes=RPCType.LIST) -> Union[RPCDict, RPCList]:
|
||||
index = None
|
||||
try:
|
||||
for k in value.keys():
|
||||
if index is not None:
|
||||
if int(k) - index == 1:
|
||||
index += 1
|
||||
else:
|
||||
return RPCDict(value, magic=magic)
|
||||
else:
|
||||
index = start = int(k)
|
||||
return RPCList(value.values(), magic=magic) if start is None else RPCList(value.values(), magic=magic, start=start)
|
||||
except ValueError:
|
||||
return RPCDict(value, magic=magic)
|
||||
|
||||
def r_unpack(data: bytes, throw: bool=True, encoding: str='latin-1') -> Any:
|
||||
if data[:2] == b'\x00\x00':
|
||||
if len(data) > 2 and data[2] == 0x18: # 0x18 is CAN
|
||||
if throw:
|
||||
raise RPCExcServerError(data[3:].decode(encoding))
|
||||
return RPCExcServerError(data[3:].decode(encoding))
|
||||
elif data[-1] == 0x1f: # 0x1f is US
|
||||
return r_unpack_table(data[2:-1].decode(encoding).split('\x1e')) # 0x1e is RS
|
||||
elif data[-2:] == b'\r\n':
|
||||
return tuple(data[2:-2].decode(encoding).split('\r\n'))
|
||||
else:
|
||||
return data[2:].decode(encoding) if len(data) > 2 else None
|
||||
if throw:
|
||||
raise RPCExcFormat(data)
|
||||
return RPCExcFormat(data)
|
||||
|
||||
def r_unpack_table(rows: Sequence[str]) -> Union[Tuple[dict, ...], Tuple[tuple, ...]]:
|
||||
# table: ROW\x1eROW\x1eROW\x1eROW\x1eROW\x1e\x1f; row: COL^COL^COL^COL^COL; header field: [IT]\d{5}.+
|
||||
if len(rows) > 0 and len(hdr := rows[0]) > 0 and hdr[0] in ('I', 'T') and hdr[1:6].isdecimal():
|
||||
header = [field[6:] for field in rows[0].split('^')]
|
||||
return tuple(dict(zip(header, row.split('^'))) for row in rows[1:] if len(row) > 0)
|
||||
else:
|
||||
return tuple(tuple(row.split('^')) for row in rows if len(row) > 0)
|
||||
|
||||
cipherpad = (
|
||||
'wkEo-ZJt!dG)49K{nX1BS$vH<&:Myf*>Ae0jQW=;|#PsO`\'%+rmb[gpqN,l6/hFC@DcUa ]z~R}"V\\iIxu?872.(TYL5_3',
|
||||
'rKv`R;M/9BqAF%&tSs#Vh)dO1DZP> *fX\'u[.4lY=-mg_ci802N7LTG<]!CWo:3?{+,5Q}(@jaExn$~p\\IyHwzU"|k6Jeb',
|
||||
'\\pV(ZJk"WQmCn!Y,y@1d+~8s?[lNMxgHEt=uw|X:qSLjAI*}6zoF{T3#;ca)/h5%`P4$r]G\'9e2if_>UDKb7<v0&- RBO.',
|
||||
'depjt3g4W)qD0V~NJar\\B "?OYhcu[<Ms%Z`RIL_6:]AX-zG.#}$@vk7/5x&*m;(yb2Fn+l\'PwUof1K{9,|EQi>H=CT8S!',
|
||||
'NZW:1}K$byP;jk)7\'`x90B|cq@iSsEnu,(l-hf.&Y_?J#R]+voQXU8mrV[!p4tg~OMez CAaGFD6H53%L/dT2<*>"{\\wI=',
|
||||
'vCiJ<oZ9|phXVNn)m K`t/SI%]A5qOWe\\&?;jT~M!fz1l>[D_0xR32c*4.P"G{r7}E8wUgyudF+6-:B=$(sY,LkbHa#\'@Q',
|
||||
'hvMX,\'4Ty;[a8/{6l~F_V"}qLI\\!@x(D7bRmUH]W15J%N0BYPkrs&9:$)Zj>u|zwQ=ieC-oGA.#?tfdcO3gp`S+En K2*<',
|
||||
'jd!W5[];4\'<C$/&x|rZ(k{>?ghBzIFN}fAK"#`p_TqtD*1E37XGVs@0nmSe+Y6Qyo-aUu%i8c=H2vJ\\) R:MLb.9,wlO~P',
|
||||
'2ThtjEM+!=xXb)7,ZV{*ci3"8@_l-HS69L>]\\AUF/Q%:qD?1~m(yvO0e\'<#o$p4dnIzKP|`NrkaGg.ufCRB[; sJYwW}5&',
|
||||
'vB\\5/zl-9y:Pj|=(R\'7QJI *&CTX"p0]_3.idcuOefVU#omwNZ`$Fs?L+1Sk<,b)hM4A6[Y%aDrg@~KqEW8t>H};n!2xG{',
|
||||
'sFz0Bo@_HfnK>LR}qWXV+D6`Y28=4Cm~G/7-5A\\b9!a#rP.l&M$hc3ijQk;),TvUd<[:I"u1\'NZSOw]*gxtE{eJp|y (?%',
|
||||
'M@,D}|LJyGO8`$*ZqH .j>c~h<d=fimszv[#-53F!+a;NC\'6T91IV?(0x&/{B)w"]Q\\YUWprk4:ol%g2nE7teRKbAPuS_X',
|
||||
'.mjY#_0*H<B=Q+FML6]s;r2:e8R}[ic&KA 1w{)vV5d,$u"~xD/Pg?IyfthO@CzWp%!`N4Z\'3-(o|J9XUE7k\\TlqSb>anG',
|
||||
'xVa1\']_GU<X`|\\NgM?LS9{"jT%s$}y[nvtlefB2RKJW~(/cIDCPow4,>#zm+:5b@06O3Ap8=*7ZFY!H-uEQk; .q)i&rhd',
|
||||
'I]Jz7AG@QX."%3Lq>METUo{Pp_ |a6<0dYVSv8:b)~W9NK`(r\'4fs&wim\\kReC2hg=HOj$1B*/nxt,;c#y+![?lFuZ-5D}',
|
||||
'Rr(Ge6F Hx>q$m&C%M~Tn,:"o\'tX/*yP.{lZ!YkiVhuw_<KE5a[;}W0gjsz3]@7cI2\\QN?f#4p|vb1OUBD9)=-LJA+d`S8',
|
||||
'I~k>y|m};d)-7DZ"Fe/Y<B:xwojR,Vh]O0Sc[`$sg8GXE!1&Qrzp._W%TNK(=J 3i*2abuHA4C\'?Mv\\Pq{n#56LftUl@9+',
|
||||
'~A*>9 WidFN,1KsmwQ)GJM{I4:C%}#Ep(?HB/r;t.&U8o|l[\'Lg"2hRDyZ5`nbf]qjc0!zS-TkYO<_=76a\\X@$Pe3+xVvu',
|
||||
'yYgjf"5VdHc#uA,W1i+v\'6|@pr{n;DJ!8(btPGaQM.LT3oe?NB/&9>Z`-}02*%x<7lsqz4OS ~E$\\R]KI[:UwC_=h)kXmF',
|
||||
'5:iar.{YU7mBZR@-K|2 "+~`M%8sq4JhPo<_X\\Sg3WC;Tuxz,fvEQ1p9=w}FAI&j/keD0c?)LN6OHV]lGy\'$*>nd[(tb!#'
|
||||
)
|
||||
cipherpad_reversed = tuple({c: i for i, c in enumerate(m)} for m in cipherpad)
|
||||
|
||||
def encrypt(plaintext: str) -> str:
|
||||
associator_idx = identifier_idx = random.randrange(l := len(cipherpad))
|
||||
while identifier_idx == associator_idx:
|
||||
identifier_idx = random.randrange(l)
|
||||
associator = cipherpad_reversed[associator_idx]
|
||||
identifier = cipherpad[identifier_idx]
|
||||
return chr(associator_idx + 32) + ''.join(identifier[associator[i]] for i in plaintext) + chr(identifier_idx + 32)
|
||||
|
||||
def encrypt_trivial(plaintext: str) -> str:
|
||||
return f' {plaintext} '
|
||||
|
||||
def decrypt(ciphertext: str) -> str:
|
||||
associator_idx = ord(ciphertext[-1]) - 32
|
||||
identifier_idx = ord(ciphertext[0]) - 32
|
||||
associator = cipherpad_reversed[associator_idx]
|
||||
identifier = cipherpad[identifier_idx]
|
||||
return ''.join(identifier[associator[i]] for i in ciphertext[1:-1])
|
||||
|
||||
class sockiter(object):
|
||||
def __init__(self, sock: socket.socket, end: bytes=bEOT, minsz: int=1024, maxsz: int=32768):
|
||||
self.gen = recv_msg(sock, end=end, minsz=minsz, maxsz=maxsz)
|
||||
self.sock = sock
|
||||
self.end = end
|
||||
def __call__(self, msg: bytes) -> int:
|
||||
return send_msg(self.sock, msg, end=self.end)
|
||||
def __iter__(self) -> Generator[bytes, None, None]:
|
||||
return self.gen
|
||||
|
||||
def client(sock: socket.socket, end: bytes=bEOT, minsz: int=1024, maxsz: int=32768) -> Callable[[bytes], bytes]:
|
||||
def gen() -> Generator[bytes, Optional[bytes], None]:
|
||||
responses = recv_msg(sock, end=end, minsz=minsz, maxsz=maxsz)
|
||||
request = yield
|
||||
while True:
|
||||
send_msg(sock, request, end=end)
|
||||
request = yield next(responses)
|
||||
gen = gen()
|
||||
next(gen)
|
||||
return gen.send
|
||||
|
||||
def send_msg(sock: socket.socket, msg: bytes, end: bytes=bEOT) -> int:
|
||||
return sock.send(msg + end)
|
||||
|
||||
def recv_msg(sock: socket.socket, end: bytes=bEOT, minsz: int=1024, maxsz: int=32768) -> Generator[bytes, None, None]:
|
||||
buf = b''
|
||||
bufsz = minsz
|
||||
while True:
|
||||
if len(data := sock.recv(bufsz)) > 0:
|
||||
buf += data
|
||||
while (idx := buf.find(end)) >= 0:
|
||||
if idx > 0:
|
||||
yield buf[:idx]
|
||||
bufsz = minsz
|
||||
elif bufsz < maxsz:
|
||||
bufsz = _x if (_x := bufsz << 1) < maxsz else maxsz
|
||||
buf = buf[idx + 1:]
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys, shlex, getpass, XUIAMSSOi
|
||||
try:
|
||||
import readline
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
host = 'localhost'
|
||||
port = 19000
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
host = sys.argv[1]
|
||||
if len(sys.argv) > 2:
|
||||
port = int(sys.argv[2])
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect((host, port))
|
||||
call = client(sock)
|
||||
print((x := f'VistA {host}:{port}'), '='*len(x), sep='\n')
|
||||
|
||||
try:
|
||||
print('RPC> TCPConnect')
|
||||
print(res := r_unpack(call(dump('TCPConnect', sock.getsockname()[0], '0', socket.gethostname(), command=True)), throw=False))
|
||||
if res == 'accept':
|
||||
print('RPC> XUS SIGNON SETUP')
|
||||
print(res := r_unpack(call(dump('XUS SIGNON SETUP', '', '1')), throw=False))
|
||||
retry = True
|
||||
while retry:
|
||||
if token := XUIAMSSOi.MySsoTokenVBA():
|
||||
print('RPC> XUS ESSO VALIDATE')
|
||||
print(res := r_unpack(call(dump('XUS ESSO VALIDATE', RPCList((token[i:i+200] for i in range(0, len(token), 200)), magic=RPCType.GLOBAL, start=0))), throw=False))
|
||||
else:
|
||||
token = getpass.getpass('ACCESS CODE: ') + ';' + getpass.getpass('VERIFY CODE: ')
|
||||
print('RPC> XUS AV CODE')
|
||||
print(res := r_unpack(call(dump('XUS AV CODE', encrypt_trivial(token))), throw=False))
|
||||
if res[0] != '0':
|
||||
retry = False
|
||||
while True:
|
||||
data = input('RPC> ').strip()
|
||||
if len(data) > 0:
|
||||
data = shlex.split(data)
|
||||
data[0] = data[0].replace('_', ' ').upper()
|
||||
print(r_unpack(call(dump(*data)), throw=False))
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
print('RPC> #BYE#')
|
||||
print(r_unpack(call(dump('#BYE#', command=True)), throw=False))
|
Loading…
Reference in New Issue
Block a user