rpcd-mod-rad3-enc: add radicale3 password hasher
authorDaniel F. Dickinson <redacted>
Sat, 10 Jan 2026 00:07:34 +0000 (19:07 -0500)
committerPaul Donald <redacted>
Thu, 15 Jan 2026 17:20:51 +0000 (18:20 +0100)
There are a number of changes to radicale, passlib and bcrypt,
which means a new rpcd function is needed.

Signed-off-by: Daniel F. Dickinson <redacted>
libs/rpcd-mod-rad3-enc/Makefile [new file with mode: 0644]
libs/rpcd-mod-rad3-enc/files/rad3-enc [new file with mode: 0755]

diff --git a/libs/rpcd-mod-rad3-enc/Makefile b/libs/rpcd-mod-rad3-enc/Makefile
new file mode 100644 (file)
index 0000000..85a94d4
--- /dev/null
@@ -0,0 +1,44 @@
+# cspell:words TOPDIR INSTROOT IPKG Radicale passlib postinst rpcd
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=rpcd-mod-rad3-enc
+PKG_VERSION:=20260109
+
+PKG_LICENSE:=Apache-2.0
+
+PKG_BUILD_PARALLEL:=1
+
+include $(INCLUDE_DIR)/package.mk
+
+define Build/Prepare
+       true
+endef
+
+define Build/Compile
+       true
+endef
+
+define Package/rpcd-mod-rad3-enc
+  SECTION:=libs
+  CATEGORY:=Libraries
+  TITLE:=Radicale v3 Hashing RPC module
+  DEPENDS:=+rpcd +python3 +python3-passlib +radicale3
+  PROVIDES:=rpcd-mod-rad2-enc
+endef
+
+define Package/rpcd-mod-rad32-enc/description
+  Python3 password hashing module for use with Radicale v3 LuCI app
+endef
+
+define Package/rpcd-mod-rad3-enc/install
+       $(INSTALL_DIR) $(1)/usr/libexec/rpcd
+       $(INSTALL_BIN) ./files/rad3-enc $(1)/usr/libexec/rpcd
+endef
+
+define Package/rpcd-mod-rad3-enc/postinst
+#!/bin/sh
+[ -n "$$IPKG_INSTROOT" ] || /etc/init.d/rpcd reload
+endef
+
+$(eval $(call BuildPackage,rpcd-mod-rad3-enc))
diff --git a/libs/rpcd-mod-rad3-enc/files/rad3-enc b/libs/rpcd-mod-rad3-enc/files/rad3-enc
new file mode 100755 (executable)
index 0000000..39f9e84
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/python3
+
+# cspell:words encpass enctype hpasswd jsonin passlib plainpass radicale
+
+import sys
+import json
+from passlib.hash import apr_md5_crypt, sha256_crypt, sha512_crypt # pyright: ignore[reportMissingModuleSource]
+from radicale import utils # pyright: ignore[reportMissingImports]
+
+def main():
+
+    if len(sys.argv) < 2:
+        return -1
+
+    if sys.argv[1] == 'list':
+        print('{ "encrypt": { "type": "str", "plainpass": "str" } }\n')
+        return 0
+
+    if sys.argv[1] == 'call':
+        if len(sys.argv) < 3:
+            return -1
+
+    if sys.argv[2] != 'encrypt':
+        return -1
+
+    encpass = ""
+    error = ""
+
+    try:
+        jsonin = json.loads(sys.stdin.readline())
+        enctype = jsonin['type'].strip()
+        plainpass = jsonin['plainpass']
+
+        if enctype == 'plain':
+            encpass = plainpass
+        elif enctype == 'md5':
+            encpass = apr_md5_crypt.hash(plainpass).strip()
+        elif enctype == 'sha256':
+            encpass = sha256_crypt.using(rounds=5000).hash(plainpass).strip()
+        elif enctype == 'sha512':
+            encpass = sha512_crypt.using(rounds=5000).hash(plainpass).strip()
+        elif enctype == 'bcrypt':
+            try:
+                from passlib.hash import bcrypt  # pyright: ignore[reportMissingModuleSource]
+            except ImportError as e:
+                raise RuntimeError("hpasswd encryption method 'bcrypt' requires the bcrypt module, which is missing") from e
+            else:
+                encpass = bcrypt.hash(plainpass).strip()
+        elif enctype == 'argon2':
+            try:
+                import argon2 # pyright: ignore[reportMissingImports]
+            except ImportError as e:
+                raise RuntimeError("hpasswd encryption method 'argon2' requires the argon2 module, which is missing") from e
+            else:
+                encpass = argon2.using(type="ID").hash(plainpass).strip()
+
+    except Exception as e:
+        encpass = ""
+        error = str(e)
+
+    if ((encpass == "") and (error == "")):
+        error = "unable to encrypt password"
+
+    if error:
+        print(json.dumps({ "encrypted_password": encpass, "error": error}))
+    else:
+        print(json.dumps({ "encrypted_password": encpass}))
+
+    return 0
+
+main()
git clone https://git.99rst.org/PROJECT