From 7372dc207962c6e16a5f9e73c6d082b39861b9cd Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 24 Nov 2022 14:42:43 +0100 Subject: [PATCH 1/6] be able to change the loginShell of a user --- src/user.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/user.py b/src/user.py index 84923106c..74a11e99d 100644 --- a/src/user.py +++ b/src/user.py @@ -134,6 +134,7 @@ def user_create( lastname=None, mailbox_quota="0", admin=False, + loginShell="/bin/bash", from_import=False, ): @@ -253,7 +254,7 @@ def user_create( "gidNumber": [uid], "uidNumber": [uid], "homeDirectory": ["/home/" + username], - "loginShell": ["/bin/bash"], + "loginShell": [loginShell], } try: @@ -363,6 +364,7 @@ def user_update( mailbox_quota=None, from_import=False, fullname=None, + loginShell=None, ): if firstname or lastname: @@ -524,6 +526,10 @@ def user_update( new_attr_dict["mailuserquota"] = [mailbox_quota] env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota + if loginShell is not None: + new_attr_dict["loginShell"] = [loginShell] + env_dict["YNH_USER_LOGINSHELL"] = loginShell + if not from_import: operation_logger.start() @@ -532,6 +538,10 @@ def user_update( except Exception as e: raise YunohostError("user_update_failed", user=username, error=e) + # Invalidate passwd and group to update the loginShell + subprocess.call(["nscd", "-i", "passwd"]) + subprocess.call(["nscd", "-i", "group"]) + # Trigger post_user_update hooks hook_callback("post_user_update", env=env_dict) From af1c1d8c02507a36d8e882fd79e08c0506704582 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 24 Nov 2022 15:13:59 +0100 Subject: [PATCH 2/6] check if the shell exists --- locales/en.json | 1 + src/user.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/locales/en.json b/locales/en.json index d18f8791e..4dc4037f8 100644 --- a/locales/en.json +++ b/locales/en.json @@ -459,6 +459,7 @@ "invalid_number_max": "Must be lesser than {max}", "invalid_number_min": "Must be greater than {min}", "invalid_regex": "Invalid regex:'{regex}'", + "invalid_shell": "Invalid shell: {shell}", "ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it", "ldap_attribute_already_exists": "LDAP attribute '{attribute}' already exists with value '{value}'", diff --git a/src/user.py b/src/user.py index 74a11e99d..6583b32e8 100644 --- a/src/user.py +++ b/src/user.py @@ -122,6 +122,29 @@ def user_list(fields=None): return {"users": users} +def list_shells(): + import ctypes + import ctypes.util + import os + import sys + + """List the shells from /etc/shells.""" + libc = ctypes.CDLL(ctypes.util.find_library("c")) + getusershell = libc.getusershell + getusershell.restype = ctypes.c_char_p + libc.setusershell() + while True: + shell = getusershell() + if not shell: + break + yield shell.decode() + libc.endusershell() + + +def shellexists(shell): + """Check if the provided shell exists and is executable.""" + return os.path.isfile(shell) and os.access(shell, os.X_OK) + @is_unit_operation([("username", "user")]) def user_create( @@ -134,8 +157,8 @@ def user_create( lastname=None, mailbox_quota="0", admin=False, - loginShell="/bin/bash", from_import=False, + loginShell=None, ): if firstname or lastname: @@ -235,6 +258,12 @@ def user_create( uid = str(random.randint(1001, 65000)) uid_guid_found = uid not in all_uid and uid not in all_gid + if not loginShell: + loginShell = "/bin/bash" + else: + if not shellexists(loginShell) or loginShell not in list_shells(): + raise YunohostValidationError("invalid_shell", shell=loginShell) + attr_dict = { "objectClass": [ "mailAccount", @@ -527,6 +556,8 @@ def user_update( env_dict["YNH_USER_MAILQUOTA"] = mailbox_quota if loginShell is not None: + if not shellexists(loginShell) or loginShell not in list_shells(): + raise YunohostValidationError("invalid_shell", shell=loginShell) new_attr_dict["loginShell"] = [loginShell] env_dict["YNH_USER_LOGINSHELL"] = loginShell @@ -563,7 +594,7 @@ def user_info(username): ldap = _get_ldap_interface() - user_attrs = ["cn", "mail", "uid", "maildrop", "mailuserquota"] + user_attrs = ["cn", "mail", "uid", "maildrop", "mailuserquota", "loginShell"] if len(username.split("@")) == 2: filter = "mail=" + username @@ -581,6 +612,7 @@ def user_info(username): "username": user["uid"][0], "fullname": user["cn"][0], "mail": user["mail"][0], + "loginShell": user["loginShell"][0], "mail-aliases": [], "mail-forward": [], } From dda5095157b4d6e5d947df4a65d54c9eb17c0dfa Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 24 Nov 2022 15:14:06 +0100 Subject: [PATCH 3/6] add actionsmap parameters --- share/actionsmap.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/share/actionsmap.yml b/share/actionsmap.yml index 98ae59a7b..72c515e6f 100644 --- a/share/actionsmap.yml +++ b/share/actionsmap.yml @@ -124,6 +124,11 @@ user: pattern: &pattern_mailbox_quota - !!str ^(\d+[bkMGT])|0$ - "pattern_mailbox_quota" + -s: + full: --loginShell + help: The login shell used + default: "/bin/bash" + ### user_delete() delete: @@ -203,6 +208,10 @@ user: metavar: "{SIZE|0}" extra: pattern: *pattern_mailbox_quota + -s: + full: --loginShell + help: The login shell used + default: "/bin/bash" ### user_info() info: From 21c72ad1c5378da21513f45c15addad2dd9e7596 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 24 Nov 2022 17:30:05 +0100 Subject: [PATCH 4/6] fix linter --- src/user.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/user.py b/src/user.py index 6583b32e8..61060a9ef 100644 --- a/src/user.py +++ b/src/user.py @@ -125,8 +125,6 @@ def user_list(fields=None): def list_shells(): import ctypes import ctypes.util - import os - import sys """List the shells from /etc/shells.""" libc = ctypes.CDLL(ctypes.util.find_library("c")) From 36b0f5899329a39e6e75204e91e08d26aa22be99 Mon Sep 17 00:00:00 2001 From: Kay0u Date: Thu, 19 Jan 2023 11:15:02 +0100 Subject: [PATCH 5/6] rewrite list_shells --- src/user.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/user.py b/src/user.py index 61060a9ef..b3a2a22e6 100644 --- a/src/user.py +++ b/src/user.py @@ -122,21 +122,17 @@ def user_list(fields=None): return {"users": users} -def list_shells(): - import ctypes - import ctypes.util - """List the shells from /etc/shells.""" - libc = ctypes.CDLL(ctypes.util.find_library("c")) - getusershell = libc.getusershell - getusershell.restype = ctypes.c_char_p - libc.setusershell() - while True: - shell = getusershell() - if not shell: - break - yield shell.decode() - libc.endusershell() +def list_shells(): + with open("/etc/shells", "r") as f: + content = f.readlines() + + shells = [] + for line in content: + if line.startswith("/"): + shells.append(line.replace("\n","")) + return shells + def shellexists(shell): From 13be9af65f6e76ab00be1c6096093e8ce61d2aa7 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 19 Jan 2023 16:24:50 +0100 Subject: [PATCH 6/6] Simplify code --- src/user.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/user.py b/src/user.py index b3a2a22e6..05dce8f24 100644 --- a/src/user.py +++ b/src/user.py @@ -127,12 +127,7 @@ def list_shells(): with open("/etc/shells", "r") as f: content = f.readlines() - shells = [] - for line in content: - if line.startswith("/"): - shells.append(line.replace("\n","")) - return shells - + return [line.strip() for line in content if line.startswith("/")] def shellexists(shell):