From b1ac351ad1dfce5e84763b2a68d5d00e849fe2b2 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 11 Jan 2026 12:54:26 +0100 Subject: [PATCH] =?UTF-8?q?=09ge=C3=A4ndert:=20=20=20=20=20=20=20README.md?= =?UTF-8?q?=20=09ge=C3=A4ndert:=20=20=20=20=20=20=20client/client.py=20=09?= =?UTF-8?q?gel=C3=B6scht:=20=20=20=20=20=20=20lib/=5F=5Fpycache=5F=5F/cryp?= =?UTF-8?q?to=5Futils.cpython-313.pyc=20=09gel=C3=B6scht:=20=20=20=20=20?= =?UTF-8?q?=20=20lib/=5F=5Fpycache=5F=5F/jebp=5Futils.cpython-313.pyc=20?= =?UTF-8?q?=09gel=C3=B6scht:=20=20=20=20=20=20=20lib/=5F=5Fpycache=5F=5F/t?= =?UTF-8?q?erminal=5Ftable.cpython-313.pyc=20=09ge=C3=A4ndert:=20=20=20=20?= =?UTF-8?q?=20=20=20lib/jebp=5Futils.py=20=09gel=C3=B6scht:=20=20=20=20=20?= =?UTF-8?q?=20=20server/clients=5Fmanagement/chclient.py=20=09gel=C3=B6sch?= =?UTF-8?q?t:=20=20=20=20=20=20=20server/clients=5Fmanagement/rmclient.py?= =?UTF-8?q?=20=09neue=20Datei:=20=20=20=20=20server/data/conf/client=5Fadm?= =?UTF-8?q?in=5Frights=20=09umbenannt:=20=20=20=20=20=20server/config/clie?= =?UTF-8?q?nts/fingerprints=20->=20server/data/conf/client=5Ffingerprints?= =?UTF-8?q?=20=09neue=20Datei:=20=20=20=20=20server/data/conf/topics=20=09?= =?UTF-8?q?ge=C3=A4ndert:=20=20=20=20=20=20=20server/server.py=20=09neue?= =?UTF-8?q?=20Datei:=20=20=20=20=20server/utils/clients=5Fmanagement/chcli?= =?UTF-8?q?ent.py=20=09neue=20Datei:=20=20=20=20=20server/utils/clients=5F?= =?UTF-8?q?management/lsclients.py=20=09umbenannt:=20=20=20=20=20=20server?= =?UTF-8?q?/clients=5Fmanagement/mkclient.py=20->=20server/utils/clients?= =?UTF-8?q?=5Fmanagement/mkclient.py=20=09neue=20Datei:=20=20=20=20=20serv?= =?UTF-8?q?er/utils/clients=5Fmanagement/rmclient.py=20=09umbenannt:=20=20?= =?UTF-8?q?=20=20=20=20server/clients=5Fmanagement/lsclients.py=20->=20ser?= =?UTF-8?q?ver/utils/topics=5Fmanagement/lstopics.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 195 +++++++++++++++--- client/client.py | 8 + lib/__pycache__/crypto_utils.cpython-313.pyc | Bin 2017 -> 0 bytes lib/__pycache__/jebp_utils.cpython-313.pyc | Bin 3945 -> 0 bytes .../terminal_table.cpython-313.pyc | Bin 5051 -> 0 bytes lib/jebp_utils.py | 2 +- server/clients_management/chclient.py | 19 -- server/clients_management/rmclient.py | 19 -- server/data/conf/client_admin_rights | 1 + .../conf/client_fingerprints} | Bin 12288 -> 12288 bytes server/data/conf/topics | Bin 0 -> 12288 bytes server/server.py | 43 +++- server/utils/clients_management/chclient.py | 56 +++++ server/utils/clients_management/lsclients.py | 19 ++ .../clients_management/mkclient.py | 2 +- server/utils/clients_management/rmclient.py | 43 ++++ .../topics_management/lstopics.py} | 7 +- 17 files changed, 341 insertions(+), 73 deletions(-) delete mode 100644 lib/__pycache__/crypto_utils.cpython-313.pyc delete mode 100644 lib/__pycache__/jebp_utils.cpython-313.pyc delete mode 100644 lib/__pycache__/terminal_table.cpython-313.pyc delete mode 100755 server/clients_management/chclient.py delete mode 100755 server/clients_management/rmclient.py create mode 100644 server/data/conf/client_admin_rights rename server/{config/clients/fingerprints => data/conf/client_fingerprints} (98%) create mode 100644 server/data/conf/topics create mode 100755 server/utils/clients_management/chclient.py create mode 100755 server/utils/clients_management/lsclients.py rename server/{ => utils}/clients_management/mkclient.py (88%) create mode 100755 server/utils/clients_management/rmclient.py rename server/{clients_management/lsclients.py => utils/topics_management/lstopics.py} (52%) diff --git a/README.md b/README.md index fcc0d70..9e39e06 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,167 @@ -# jeb - -jCloud Event Bus - -## jebp (jCloud Event Bus Protocol) - -### Messages -All messages begin with `0x01`, followed by the content length length. The content length length is the length of the content length and representated by one byte. Then follows the content length and after that the message content. - -### Handshake -1. Server: Server protocol, version -2. Encryption - 1. Client: Serialized client public key - 2. Server: Serialized server public key - 3. Client: Client nonce - 4. Server: Server nonce - 5. Client: Random bytes (encrypted) - 6. Server: received bytes from client (not encrypted) - - The client closes the connection if the received bytes from server (7.) do not match the generated bytes (6.) to prevent malfunction and test the encryption. -3. Server authentication - 1. Server: Server certificate - - The client closes the connection if it does not trust the server certificate. -4. Client authentication - 1. Client: Client certificate - - The server closes the connection if the client is unauthorized. \ No newline at end of file +# jeb + +jCloud Event Bus + +## jebp (jCloud Event Bus Protocol) + +### Messages +All messages begin with `0x01`, followed by the content length length. The content length length is the length of the content length and representated by one byte. Then follows the content length and after that the message content. + +### Handshake +1. Server: Server protocol, version +2. Encryption + 1. Client: Serialized client public key + 2. Server: Serialized server public key + 3. Client: Client nonce + 4. Server: Server nonce + 5. Client: Random bytes (encrypted) + 6. Server: received bytes from client (not encrypted) + + The client closes the connection if the received bytes from server (7.) do not match the generated bytes (6.) to prevent malfunction and test the encryption. +3. Server authentication + 1. Server: Server certificate + + The client closes the connection if it does not trust the server certificate. +4. Client authentication + 1. Client: Client certificate + + The server closes the connection if the client is unauthorized. + + +### Commands +#### `0x11`: Create topic +##### Request +`0x11`, parameters: +- ``: The name of the topic +##### Response +###### Status code +- `0xa1`: Topic successfully created +- `0xb4`: Unknown error creating the topic +- `0xb6`: Topic exists +- `0xb7`: No administrative rights +###### Content +Empty + +Requires administrative rights. + + +#### `0x12`: Create record +##### Request +`0x12`, parameters: +- ``: The name of the topic +- ``: The CRC +- ``: The timestamp of the record (the milliseconds since the epoch, formatted as `int64`). If it is `0`, the timestamp the record was is at will be saved. +- ``: The record content +##### Response +###### Status code +- `0xa1`: Record successfully created +- `0xb3`: Unknown error creating the record. Probably the CRC is invalid if the repetition byte is `0x31` +###### Content +Empty + + +#### `0x21`: Subscribe +##### Request +`0x21`, parameters: +- ``: The topics, comma-separated +##### Response +###### Status code +- `0xa2`: Successfully subscribed +- `0xb0`: Unknown error subscribing +- `0xb1`: At least one of the topics does not exist +###### Content +- Status code `0xa0`: Empty +- Status code `0xb1`: ``, ``: The topics that do not exist + + +#### `0x22`: Fetch records +##### Request +`0x22` +- ``: One byte, if `0xc0`, the `` is the timestamp of the first fetched event, if `0xc1` the offset. +- ``: The first event timestamp or offset +- ``: Maximum bytes sent +##### Response +###### Status code +- `0xa0`: Success +- `0xb1`: The offset `` does not exist + +#### `0x31`: Remove topic +##### Request +`0x31`, parameters: +- ``: The name of the topic +##### Response +###### Status code +- `0xa2`: Topic successfully removed +- `0xb8`: Unknown deletion error +- `0xb9`: Unknown deletion error. The client should attempt to perform the operation again. +###### Content +Empty + +Requires administrative rights. + + +### Status codes +The status code consists of two bytes. + +1\. Byte (status byte): The status + +2\. Byte (repetition byte): If the operation was unsuccessful, `0x31` if the client should try to perform the operation again, otherwise or if the operation was successful, `0x30`. + +In this documentation, the *status code* always means the status byte, unless otherwise stated. + +#### `0xa…`: Success +- `0xa0`: Reading successful +- `0xa1`: Creation successful +- `0xa2`: Deletion successful +#### `0xb…`: Error +- `0xb0`: Reading error +- `0xb1`: Reading error, object does not exist +- `0xb2`: Reading error, no permission +- `0xb3`: Creation error +- `0xb4`: Creation error, object already exists +- `0xb5`: Creation error, no permission +- `0xb6`: Deletion error +- `0xb7`: Deletion error, object does not exist +- `0xb8`: Deletion error, no permission + + + + +## Data storage +The topics consist of partitions and every partition consists of segments. A segment consists of three files, +- `.log` +- `.index` +- `.timeindex` + +``: The offset of the first record in the segment + +In the `.log` file, the records are saved. + +In the `.index` file, the positions (bytes) of every 1024th record are saved. + +In the `.timeindex` file, the timestampts of every 1024th record are saved. + +### Data format +#### `.log` +##### Record +| Field | Type | Length (bytes) | Description | +| - | - | - | - | +| **Offset** | `int64` | 8 | Offset | +| **Record Length** | `int32` | 4 | Length | +| **CRC** | `int32` | 4 | Checksum | +| **Attributes** | `byte` | 1 | Flags, e. g. Compression | +| **Timestamp** | `int64` | 8 | Milliseconds since Epoch | +| **Key Length** | `int32` | 4 | The length of the key | +| **Key** | `bytes` | -- | optional | +| **Payload length** | `int32` | 4 | The length of the payload | +| **Payload** | `bytes` | -- | Record payload | +| **Headers Count** | `int8` | 1 | Number of header pairs | +| **Header Key / Value** | `int32` | -- | Header pairs | + +##### Header pairs +| Field | Type | Length (bytes) | Description | +| - | - | - | - | +| Header key length | `int8` | 4 | Length of the header name (key) | +| Header key | `bytes` | -- | Header name (key) | +| Header value length | `int8` | 4 | Length of the header value | +| Header value | `bytes` | -- | Header value | \ No newline at end of file diff --git a/client/client.py b/client/client.py index ebdb73a..b076091 100755 --- a/client/client.py +++ b/client/client.py @@ -81,6 +81,14 @@ async def main(): certfile.close() await sendmsg(base64.b64decode(cert_data.replace(b'-----BEGIN CERTIFICATE-----', b'').replace(b'-----END CERTIFICATE-----', b'').strip()), writer, aesgcm, client_nonce) + msg = await readmsg(reader, aesgcm, server_nonce) + if msg == b'SCD': + print('LOGIN SUCCEEDED') + elif msg == b'ERR': + print('LOGIN FAILED') + + await sendmsg(b'\x11', writer, aesgcm, client_nonce) + print(await readmsg(reader, aesgcm, server_nonce)) except MessageFormatError: print('invalid message format') diff --git a/lib/__pycache__/crypto_utils.cpython-313.pyc b/lib/__pycache__/crypto_utils.cpython-313.pyc deleted file mode 100644 index 621c2a78c6353115abc321d37e299a232ca74dc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2017 zcmbVM-A~(A6u)*Hlf(&WqLczdEMj!j0}?=`E$W0eHK`C$8H4 z(?lYMp#8IaPj^BH{YD4f#&wj_uR%FP5|Wq_T4e~6r8-+;S3@MU$`Q^Vhf3j9p75Y^ zqlgSh;Vq_osk_x#FY#N^>zp(&igq~?lOkJDn7PmsyrRCB;ICn&m_LeF1u_1TZYVMy zixrXeculr6Y~MtWo8X=3v<2c2ZT4(swx{-#iGF(* zjh+>u3AET5Ei!>(p!=vi(C5;%clzt>BznZGi5#Jq@G@0wU?SUiyFv68?f3A$7nO>4 z))y9Ul;^!zqgkox3Y~`M2z_1_Ju&MQ@|s!4`CWO>tjt=9hIKWs;#PiFshLeRUy+qP zY^ZtM!iH_-cX1_O(<^y}>^E$4yJ_n+E7#cfF7@sK2Q_4!kU`j;hu?Y+!~r@- zb6R{l@oeVt%#++vdg-8aEDX8BIcGR`G(7i0n0vv^oq{(a0tk;M*lrR-=^#T5C=9=k z{^y_^q778;w^9#io4rVsFTlJ^Z*{Kqt%#g6!LaNz{PlT>S%OH2>>98qf(^w~b))L> zJ0_{iw#a+&4(^qG8(V~4+lv&tZruH)8w*~1-4D%Oe~Fjc0BgNLuw`L&-P8?RWPBWg zIFSV1BGct3#Jd+)G6E)wm~{)p0Xh~gyTXJcOq`^~-PDwmnsQS!PHN^PIp!usCn>tg z+fMR!JB;G*v{5L|g9!5^Ne|#u*_K~JoFeEWu7UDjd@s|7o@;#>asl#Z8L)vCJ8_#t zghrvu@u6a^rZ;R|xzi*q9Dr5=mZx`!S>N`t$Le;2LPlt$0Jl--`f(dl@rB5ZgI}8Z zz6%0KDkNNC(h(+I;ieol7tdBp46UnDXo)LRzMN|tY`4k)bGfybS+HAC5SnbLmL~6Ek@KQ* zt!fh8*6JQ#!$#HCJkFA9w#VtljwucR+doxcK@|rTh%4SHtPmPmLaE~M(#nTWdOTi* zQrf~|h{Wl1vVk$_BQ2C#O(qaG3oDT476mYy%?30d4AyST*rJ7tOoMS4eya!q2%dS{ z&0KRb*Pf=IU3q-v$;k7MjxvRVd%p|Gll1WSv)|5smp@LXjuY^W{1FYu`M+amAo1DG z7oQyQZ?%PRc+6+}nwap!ppsRI+|c%On*0FLHwRU+uG?^XD<@m~^*XkRt_05=f3v@j zL6GiKZ}!aXsXI9hM1UPFmt+it7Y<4WO=?2<>A(IF@^2i#6M4UGs?8c+Ci7sY1jpe4h diff --git a/lib/__pycache__/jebp_utils.cpython-313.pyc b/lib/__pycache__/jebp_utils.cpython-313.pyc deleted file mode 100644 index f1cb593b511080e882f1c30d6942aca1b08c9bf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3945 zcmdT{U2GHC6~5OV|HOYGKZX#q!6X6VLgKIrn~foXID`b)81Td-Y_d!z#~@2V*mxTwbQiIR#itOVt?3j#X(A>+ zmCdKjCmF)%GIPpu(n2gJt;7m_3$fLqM#Ng{5o;T_A2jFnkqUj(4x<&DMjdPo>lk-- zm{?~$nr5akaj}@il#PVdgEH9mUWMtIvCbdSnnyWuY~mtrFDi>SihyPe#^Ke z+Gxy`+D0^PjW(D1hIA0DK&b<>RpU%_PrkRA!`-V3x@!|SQ2U%tMgmx~_V%*_eM2BI zmk+jg1x%Vf&a0v-B}L6n`IVF`ZczPL*sQQ{BHE~h(Yt6I5zHcDVlgqZCc-4l0YPt16Ku=2TJPIN}7Y8}xcwI5?F`iot3AVrrsQ5vD{b9*m3g z!D(S8H5U&~@WMq=jt9kgQC5}Uv^Wu*ktS$6%gw3MjM6r{Kq^3y&gf+o2G~dF7c8kZ zhQGBTww;jY`57tRClXbflmswv-WvKz^zsqnqS~pLKc|LDUW3tfg^T>UDZhZuX;>Ca z&>BR;fU=oew|0`lG8qzP5rh+X5?mW^3@_ zo|~bxvp3E3>c-Kmmx!c_M6>duk`R)@=1-$ey#c?C5SXV4`M<$XV@1P;MiJt-z=!`= z6M0bhD4ngv*;XAZjAfEnb9gkN2xX3egd-S6o6$O-!(@pF0fSs*-^yOX`aLZ90^ot zjJhx@YGy?RTZ^)gii?^JTCy;!>R!~$;CRiVyF;_&@nDG)UXlY=4JXM?IF$H-X?Adx zoRS4mb3lX>RR@pe5mK@Wp<2L?VuhuCo$|P`DG<#`B|#`ss5KM?*mDXR8-V7kz_xNA#$$A5eku}EggZIAw z-ktiNiTBx+hV1Tl)6HGkhES#|^zcHa>eL_jOx5XC=FFO7%hLW^`){}9n7S-e_e!DJ z;d;0#?88rLaCkfOq}B?uW+fsY7l}Z#h(Q#MIs&g35eV1378iBc2W%t=b2_NWJ}T{}OTDiHxqxRvqmg_Em_n099@&MIXrpIRXukt9 zb#%#3fl+9$xodLnof-GedpPTE%-I_6O=oTG&piJ0uCA;nl(vV~8e4LW9ht_C&(3BW zJ99fa)6DiY->#gmHREgjt?)bfft>D+X4}}TZ#d^22H1OQa~^-j9`CSDwTQHktLf7{XdrFZyC`_Wg*F}UA# zZ}7HV<-H(1!r?CFG1>>><9#@MhDuzxu#mtJS+Y|~xU_erXHN_NOT1jW)psfKVFr+gYC{%YDy$S4N z^x@Dcsu!k4uP{d6?=mvLTZ7IqiYW*$UJV-bAe1^51zt{POhfRw`9BZem&#RBdZQR5zE3u#HPeaYB8r*&@cV*aCZU`5RZ2QtBdRp-E3W$n5d zxdt%^YrdVI9{%KT&exOi^?ac|y88Lm+>w#Yk&#v3Xtv_~Q{U(verfLy_hx*fi{W&| z`DZrA&9`q{x>I%Idvv$0I&0feP;=;$Lpfh}#@GEt{L#$kGr68~nVxg2zL9Lj=u_W_ zp=N}t87)_{J!@;CYMhm?zV;%=`}oz@4&*qG6&eGNecrx(%#&_!Upo`P#{*u?#w!c5 zAf>c@dV)+P2tPZu&^E--J9MWJ`gjsr z256okc7S7td!xf#`229cR?&Z15NA~>CFd)6y?Um3I$w#nSQRpAazw8T^?E@o3`O-P zTF$bvx?ECvZA;K0P)60@?8P<&oT52QqIR zSf`zb@4TeVdNm5;<2W5Xmll;5$d291dvo<|nfkVM+IeVwNt^Xrs3qRVxw9WUJ^MlW h!es8kbmqcz_Uy$M$c?uwGr78!OkK;rXh$!W{|&puFoFO8 diff --git a/lib/__pycache__/terminal_table.cpython-313.pyc b/lib/__pycache__/terminal_table.cpython-313.pyc deleted file mode 100644 index 5b4fa015d0ae1ff268300c28231a2fa5c01d66a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5051 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 YaxMfAKmY**5I_I{1Q0*~0R%dM4S6RqZvX%Q literal 0 HcmV?d00001 diff --git a/server/server.py b/server/server.py index 5ba0135..e3b666a 100755 --- a/server/server.py +++ b/server/server.py @@ -26,6 +26,34 @@ VERSION = 'jebp 1.0' CERT_FILE = 'server/sec/server.crt.pem' CLIENT_CERT_ISSUER_NAME = 'jCloudCA-Root-CA' +class Client: + def __init__(self, fingerprint: bytes, common_name: str, admin: bool = False): + self.fingerprint = fingerprint + self.common_name = common_name + self.admin = admin + +def get_client_info(client_fingerprint: bytes): + with dbm.open('server/data/conf/client_fingerprints') as db: + if client_fingerprint not in db.keys(): + raise KeyError('Client not registered') + common_name = db[client_fingerprint].decode() + db.close() + with open('server/data/conf/client_admin_rights') as carf: + admins = carf.read().split('\n') + carf.close() + return Client(fingerprint = client_fingerprint, common_name = common_name, admin = client_fingerprint.hex() in admins) + + +def process_command(command, client: Client): + print(client.fingerprint, client.common_name, client.admin) + response = b'' + if command[0] == 0x11: + if not client.admin: + response = b'\xb7' + return response + + + async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): addr = writer.get_extra_info('peername') print(f'Connected to {addr}') @@ -68,16 +96,27 @@ async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWrit cert = x509.load_der_x509_certificate(await readmsg(reader, aesgcm, client_nonce)) key_hash = hashlib.sha256(cert.public_key().public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)).digest() - with dbm.open('server/config/clients/fingerprints', 'c') as db: + with dbm.open('server/data/conf/client_fingerprints', 'c') as db: if key_hash not in db.keys(): + await sendmsg('ERR', writer, aesgcm, server_nonce) raise InvalidCertificateError('client not known') if not validate_cert(cert, db[key_hash].decode(), CLIENT_CERT_ISSUER_NAME): + await sendmsg('ERR', writer, aesgcm, server_nonce) raise InvalidCertificateError('client certificate not trusted') - print('Client authenticated') + await sendmsg('SCD', writer, aesgcm, server_nonce) + print('CLIENT AUTHENTICATED') + + while True: + try: + await sendmsg(process_command(await readmsg(reader, aesgcm, client_nonce), get_client_info(hashlib.sha256(cert.public_key().public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)).digest())), writer, aesgcm, server_nonce) + except Exception as e: + print(f'{str(type(e))[8:-2]}: {e}') + break writer.close() await writer.wait_closed() + print(f'Connection to {addr} closed') async def main(): diff --git a/server/utils/clients_management/chclient.py b/server/utils/clients_management/chclient.py new file mode 100755 index 0000000..a4358b8 --- /dev/null +++ b/server/utils/clients_management/chclient.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +import dbm +import sys +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization +import hashlib +import os + +if len(sys.argv) < 2: + print(f'{sys.argv[0]}: missing key hash') + sys.exit(1) + +with dbm.open('server/data/conf/client_fingerprints', 'c') as db: + if bytes.fromhex(sys.argv[1]) not in db.keys(): + print(f'{sys.argv[0]}: hash not registered') + sys.exit(1) + db[bytes.fromhex(sys.argv[1])] = input(f'New common name [{db[bytes.fromhex(sys.argv[1])].decode()}]: ') or db[bytes.fromhex(sys.argv[1])] + db.close() + +if os.path.exists('server/data/conf/locks/client_admin_rights.lock'): + with open('server/data/conf/locks/client_admin_rights.lock', 'r') as lockf: + print(f'{sys.argv[0]}: admin rights file is locked by process {lockf.read()}') + lockf.close() + sys.exit(1) + +try: + with open('server/data/conf/locks/client_admin_rights.lock', 'w') as lockf: + lockf.write(str(os.getpid())) + lockf.close() + + with open('server/data/conf/client_admin_rights', 'r') as carf: + admins = {kh for kh in carf.read().split('\n') if kh} + carf.close() + + admin = input(f'Is admin (y/n) [{'y' if sys.argv[1] in admins else 'n'}]: ') or ('y' if sys.argv[1] in admins else 'n') + if admin == 'y': + admin = True + else: + admin = False + if admin: + if sys.argv[1] not in admins: + print(f'Added {sys.argv[1]} to the admins') + admins.add(sys.argv[1]) + else: + try: + admins.remove(sys.argv[1]) + except KeyError: + pass + with open('server/data/conf/client_admin_rights', 'w') as carf: + carf.write('\n'.join(admins)) + carf.close() + +finally: + os.remove('server/data/conf/locks/client_admin_rights.lock') \ No newline at end of file diff --git a/server/utils/clients_management/lsclients.py b/server/utils/clients_management/lsclients.py new file mode 100755 index 0000000..cc256b7 --- /dev/null +++ b/server/utils/clients_management/lsclients.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +import sys +import os +sys.path.append(os.getcwd()) +import dbm +from lib.terminal_table import ascii_table + +with open('server/data/conf/client_admin_rights', 'r') as carf: + admins = {kh for kh in carf.read().split('\n') if kh} + carf.close() + +with dbm.open('server/data/conf/client_fingerprints', 'c') as db: + print(ascii_table([{ + 'Key Hash': k.hex(), + 'Common name': v.decode(), + 'Is admin': 'yes' if k.hex() in admins else 'no' + } for k, v in db.items()], )) + db.close() \ No newline at end of file diff --git a/server/clients_management/mkclient.py b/server/utils/clients_management/mkclient.py similarity index 88% rename from server/clients_management/mkclient.py rename to server/utils/clients_management/mkclient.py index cd61807..5a62db6 100755 --- a/server/clients_management/mkclient.py +++ b/server/utils/clients_management/mkclient.py @@ -17,6 +17,6 @@ except: print(f'{sys.argv[0]}: invalid certificate') sys.exit(1) -with dbm.open('server/config/clients/fingerprints', 'c') as db: +with dbm.open('server/data/conf/client_fingerprints', 'c') as db: db[hashlib.sha256(cert.public_key().public_bytes(encoding=serialization.Encoding.DER, format=serialization.PublicFormat.SubjectPublicKeyInfo)).digest()] = sys.argv[1] db.close() \ No newline at end of file diff --git a/server/utils/clients_management/rmclient.py b/server/utils/clients_management/rmclient.py new file mode 100755 index 0000000..956a051 --- /dev/null +++ b/server/utils/clients_management/rmclient.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import sys +import os +sys.path.append(os.getcwd()) +import dbm +from lib.terminal_table import ascii_table + +if len(sys.argv) < 2: + print(f'{sys.argv[0]}: missing key hash') + sys.exit(1) + +with dbm.open('server/data/conf/client_fingerprints', 'c') as db: + try: + del db[bytes.fromhex(sys.argv[1])] + except: + print(f'{sys.argv[0]}: hash not registered') + sys.exit(1) + db.close() + +if os.path.exists('server/data/conf/locks/client_admin_rights.lock'): + with open('server/data/conf/locks/client_admin_rights.lock', 'r') as lockf: + print(f'{sys.argv[0]}: admin rights file is locked by process {lockf.read()}') + lockf.close() + sys.exit(1) + +try: + with open('server/data/conf/locks/client_admin_rights.lock', 'w') as lockf: + lockf.write(str(os.getpid())) + lockf.close() + + with open('server/data/conf/client_admin_rights', 'r') as carf: + admins = {kh for kh in carf.read().split('\n') if kh} + carf.close() + + if sys.argv[1] in admins: + admins.remove(sys.argv[1]) + with open('server/data/conf/client_admin_rights', 'w') as carf: + carf.write('\n'.join(admins)) + carf.close() + +finally: + os.remove('server/data/conf/locks/client_admin_rights.lock') \ No newline at end of file diff --git a/server/clients_management/lsclients.py b/server/utils/topics_management/lstopics.py similarity index 52% rename from server/clients_management/lsclients.py rename to server/utils/topics_management/lstopics.py index 0809af8..997b400 100755 --- a/server/clients_management/lsclients.py +++ b/server/utils/topics_management/lstopics.py @@ -5,10 +5,11 @@ import os sys.path.append(os.getcwd()) import dbm from lib.terminal_table import ascii_table +import pickle -with dbm.open('server/config/clients/fingerprints', 'c') as db: +with dbm.open('server/data/conf/topics', 'c') as db: print(ascii_table([{ - 'Key Hash': k.hex(), - 'Common name': v.decode() + 'Name': k.decode(), + 'Partitions': str(pickle.loads(v['partitions'])), } for k, v in db.items()], )) db.close() \ No newline at end of file