diff --git a/changelogs/fragments/86368-user-freebsd-nonexistent.yml b/changelogs/fragments/86368-user-freebsd-nonexistent.yml new file mode 100644 index 00000000000..9d4f75f7493 --- /dev/null +++ b/changelogs/fragments/86368-user-freebsd-nonexistent.yml @@ -0,0 +1,2 @@ +bugfixes: + - user - fix ``FreeBsdUser`` to not create ``/nonexistent`` directory when modifying user to add them to a group on FreeBSD (https://github.com/ansible/ansible/issues/86368) diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py index b81258153fd..2818a11646d 100644 --- a/lib/ansible/modules/user.py +++ b/lib/ansible/modules/user.py @@ -512,6 +512,15 @@ from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.sys_info import get_platform_subclass +# Placeholder home directories that should never be physically created on disk +# These are conventions for system accounts that should not have real home directories +PLACEHOLDER_HOME_DIRS = frozenset([ + '/nonexistent', # FreeBSD convention for system accounts (www, nobody, etc.) + '/dev/null', # Used on some Linux systems for system accounts + '/var/empty', # OpenBSD convention for privilege-separated processes +]) + + class StructSpwdType(ctypes.Structure): _fields_ = [ ('sp_namp', ctypes.c_char_p), @@ -1586,7 +1595,9 @@ class FreeBsdUser(User): cmd.append(self.comment) if self.home is not None: - if (info[5] != self.home and self.move_home) or (not os.path.exists(self.home) and self.create_home): + # Skip home creation for placeholder directories (e.g., /nonexistent, /dev/null) + # These are conventions for system accounts and should not be created on disk + if (info[5] != self.home and self.move_home) or (info[5] not in PLACEHOLDER_HOME_DIRS and not os.path.exists(self.home) and self.create_home): cmd.append('-m') if info[5] != self.home: cmd.append('-d') @@ -3405,7 +3416,7 @@ def main(): info = user.user_info() if user.home is None: user.home = info[5] - if not os.path.exists(user.home) and user.create_home: + if not os.path.exists(user.home) and user.home not in PLACEHOLDER_HOME_DIRS and user.create_home: if not module.check_mode: user.create_homedir(user.home) user.chown_homedir(info[2], info[3], user.home) diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml index 6a3c84eecd7..075e740a36f 100644 --- a/test/integration/targets/user/tasks/main.yml +++ b/test/integration/targets/user/tasks/main.yml @@ -33,4 +33,6 @@ when: - ansible_facts.system == 'Linux' - ansible_distribution != 'Alpine' +- include_tasks: test_freebsd_nonexistent.yml + when: ansible_facts.system == 'FreeBSD' - import_tasks: ssh_keygen.yml diff --git a/test/integration/targets/user/tasks/test_freebsd_nonexistent.yml b/test/integration/targets/user/tasks/test_freebsd_nonexistent.yml new file mode 100644 index 00000000000..7bdf2229f58 --- /dev/null +++ b/test/integration/targets/user/tasks/test_freebsd_nonexistent.yml @@ -0,0 +1,47 @@ +--- +# Test for issue #86368 - ensure /nonexistent is not created on FreeBSD +# when modifying a user to add them to a group + +- name: Create a test user with /nonexistent as home directory + user: + name: ansibulltestuser + home: /nonexistent + create_home: no + state: present + register: user_create + +- name: Create a test group + group: + name: ansibulltestgroup + state: present + +- name: Add user to group (should not create /nonexistent) + user: + name: ansibulltestuser + groups: ansibulltestgroup + append: true + register: user_modify + +- name: Check if /nonexistent was created (it should not exist) + stat: + path: /nonexistent + register: nonexistent_stat + +- name: Validate that /nonexistent was not created + assert: + that: + - user_create is changed + - user_modify is changed + - not nonexistent_stat.stat.exists + fail_msg: "/nonexistent directory should not be created when adding user to group" + +- name: Clean up - remove test user + user: + name: ansibulltestuser + state: absent + force: true + +- name: Clean up - remove test group + group: + name: ansibulltestgroup + state: absent