|
24 | 24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | 25 |
|
26 | 26 | import os |
| 27 | +from multiprocessing import Process, Pipe |
27 | 28 | from ansible_collections.nextcloud.admin.plugins.module_utils.exceptions import ( |
28 | 29 | OccExceptions, |
29 | 30 | OccAuthenticationException, |
@@ -60,38 +61,83 @@ def convert_string(command: str) -> list: |
60 | 61 | return [token if " " in token else token.strip("\"'") for token in command_lex] |
61 | 62 |
|
62 | 63 |
|
| 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 | + |
63 | 110 | def run_occ( |
64 | 111 | module, |
65 | 112 | command, |
66 | 113 | ): |
67 | 114 | cli_full_path = module.params.get("nextcloud_path") + "/occ" |
68 | 115 | 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 | | - |
86 | 116 | if isinstance(command, list): |
87 | | - full_command = [cli_full_path] + ["--no-ansi"] + command |
| 117 | + full_command = [cli_full_path, "--no-ansi"] + command |
88 | 118 | 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) |
90 | 120 |
|
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) |
94 | 125 | ) |
| 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 | + |
95 | 141 | if "is in maintenance mode" in result["stderr"]: |
96 | 142 | module.warn(" ".join(result["stderr"].splitlines()[0:1])) |
97 | 143 | maintenanceMode = True |
|
0 commit comments