Skip to content

Commit 22380fa

Browse files
authored
Merge pull request #424 from nextcloud/fix/ansible-core-2.19
fix regression on ansible-core 2.19+ by improving how the module switches to the owner of occ to run commands
2 parents 5035ca7 + 2af13fc commit 22380fa

File tree

2 files changed

+212
-131
lines changed

2 files changed

+212
-131
lines changed

plugins/module_utils/nc_tools.py

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2525

2626
import os
27+
from multiprocessing import Process, Pipe
2728
from ansible_collections.nextcloud.admin.plugins.module_utils.exceptions import (
2829
OccExceptions,
2930
OccAuthenticationException,
@@ -60,38 +61,83 @@ def convert_string(command: str) -> list:
6061
return [token if " " in token else token.strip("\"'") for token in command_lex]
6162

6263

64+
def execute_occ_command(conn, module, php_exec, command):
65+
"""
66+
Execute a given occ command using the PHP interpreter and handle user permissions.
67+
68+
This function attempts to runs occ through the PHP interpreter with
69+
the appropriate user permissions. It checks if the current user has the same
70+
UID as the owner of the occ file, switches to that user if necessary,
71+
and runs the command. The output of the command execution along with any
72+
errors are sent back via the provided connection object.
73+
74+
Parameters:
75+
- conn (multiprocessing.connection.Connection): The connection object used for communication.
76+
- module (AnsibleModule): An object providing methods for running commands.
77+
- php_exec (str): The path to the PHP executable.
78+
- command (list): A list where the first element is 'occ' with its full path.
79+
80+
Raises:
81+
- OccFileNotFoundException: If the command file does not exist.
82+
- OccAuthenticationException: If there are insufficient permissions to switch the user.
83+
84+
Returns:
85+
None: This function does not return anything. It sends the results or exceptions through the conn object.
86+
"""
87+
try:
88+
cli_stats = os.stat(command[0])
89+
if os.getuid() != cli_stats.st_uid:
90+
os.setgid(cli_stats.st_gid)
91+
os.setuid(cli_stats.st_uid)
92+
93+
rc, stdout, stderr = module.run_command([php_exec] + command)
94+
conn.send({"rc": rc, "stdout": stdout, "stderr": stderr})
95+
except FileNotFoundError:
96+
conn.send({"exception": "OccFileNotFoundException"})
97+
except PermissionError:
98+
conn.send(
99+
{
100+
"exception": "OccAuthenticationException",
101+
"msg": f"Insufficient permissions to switch to user id {cli_stats.st_uid}.",
102+
}
103+
)
104+
except Exception as e:
105+
conn.send({"exception": str(e)})
106+
finally:
107+
conn.close()
108+
109+
63110
def run_occ(
64111
module,
65112
command,
66113
):
67114
cli_full_path = module.params.get("nextcloud_path") + "/occ"
68115
php_exec = module.params.get("php_runtime")
69-
try:
70-
cli_stats = os.stat(cli_full_path)
71-
except FileNotFoundError:
72-
raise OccFileNotFoundException()
73-
74-
if os.getuid() != cli_stats.st_uid:
75-
module.debug(f"DEBUG: Switching user to id {cli_stats.st_uid}.")
76-
try:
77-
os.setgid(cli_stats.st_gid)
78-
os.setuid(cli_stats.st_uid)
79-
except PermissionError:
80-
raise OccAuthenticationException(
81-
msg="Insufficient permissions to switch to user id {}.".format(
82-
cli_stats.st_uid
83-
)
84-
)
85-
86116
if isinstance(command, list):
87-
full_command = [cli_full_path] + ["--no-ansi"] + command
117+
full_command = [cli_full_path, "--no-ansi"] + command
88118
elif isinstance(command, str):
89-
full_command = [cli_full_path] + ["--no-ansi"] + convert_string(command)
119+
full_command = [cli_full_path, "--no-ansi"] + convert_string(command)
90120

91-
module.debug(f"DEBUG: Running command '{[php_exec] + full_command}'.")
92-
result = dict(
93-
zip(("rc", "stdout", "stderr"), module.run_command([php_exec] + full_command))
121+
# execute the occ command in a child process to keep current privileges
122+
module_conn, occ_conn = Pipe()
123+
p = Process(
124+
target=execute_occ_command, args=(occ_conn, module, php_exec, full_command)
94125
)
126+
p.start()
127+
result = module_conn.recv()
128+
p.join()
129+
130+
# check if the child process has sent an exception.
131+
if "exception" in result:
132+
exception_type = result["exception"]
133+
# raise the proper exception.
134+
if exception_type == "OccFileNotFoundException":
135+
raise OccFileNotFoundException(full_command)
136+
elif exception_type == "OccAuthenticationException":
137+
raise OccAuthenticationException(full_command, **result)
138+
else:
139+
raise OccExceptions(f"An unknown error occurred: {exception_type}")
140+
95141
if "is in maintenance mode" in result["stderr"]:
96142
module.warn(" ".join(result["stderr"].splitlines()[0:1]))
97143
maintenanceMode = True

0 commit comments

Comments
 (0)