ChrisSacrumCor commited on
Commit
0add29f
·
verified ·
1 Parent(s): 076598a

Update tools/linux_tools.py

Browse files
Files changed (1) hide show
  1. tools/linux_tools.py +299 -4
tools/linux_tools.py CHANGED
@@ -1,9 +1,8 @@
1
  """
2
- Linux administration tools for Gradio MCP server.
3
- Uses @gr.tool decorator to automatically create MCP tools.
4
  """
5
 
6
- import gradio as gr
7
  import subprocess
8
  import os
9
  import pwd
@@ -17,12 +16,308 @@ logger = logging.getLogger(__name__)
17
 
18
  # Sensitive Tools (require Socratic dialogue)
19
 
20
- @gr.tool
21
  def add_user(username: str, groups: Optional[List[str]] = None,
22
  create_home: bool = True, shell: str = "/bin/bash") -> dict:
23
  """
24
  Add a new system user with proper validation.
25
  This is a SENSITIVE operation requiring careful consideration.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  Args:
28
  username: Username (lowercase, alphanumeric, starts with letter)
 
1
  """
2
+ Linux administration tools for Gradio interface.
3
+ Standard Python functions (no @gr.tool decorators).
4
  """
5
 
 
6
  import subprocess
7
  import os
8
  import pwd
 
16
 
17
  # Sensitive Tools (require Socratic dialogue)
18
 
 
19
  def add_user(username: str, groups: Optional[List[str]] = None,
20
  create_home: bool = True, shell: str = "/bin/bash") -> dict:
21
  """
22
  Add a new system user with proper validation.
23
  This is a SENSITIVE operation requiring careful consideration.
24
+ """
25
+ # Validate username
26
+ if not _validate_username(username):
27
+ return {
28
+ "success": False,
29
+ "error": "Invalid username format",
30
+ "details": "Username must be lowercase, alphanumeric, and start with letter",
31
+ "tool_type": "sensitive_operation"
32
+ }
33
+
34
+ # Check if user already exists
35
+ try:
36
+ pwd.getpwnam(username)
37
+ return {
38
+ "success": False,
39
+ "error": f"User '{username}' already exists",
40
+ "tool_type": "sensitive_operation"
41
+ }
42
+ except KeyError:
43
+ pass # User doesn't exist, which is what we want
44
+
45
+ # Build useradd command
46
+ cmd = ["useradd"]
47
+
48
+ if create_home:
49
+ cmd.append("-m")
50
+
51
+ if shell:
52
+ cmd.extend(["-s", shell])
53
+
54
+ if groups:
55
+ # Validate groups exist
56
+ valid_groups = []
57
+ for group in groups:
58
+ try:
59
+ grp.getgrnam(group)
60
+ valid_groups.append(group)
61
+ except KeyError:
62
+ logger.warning(f"Group '{group}' does not exist, skipping")
63
+
64
+ if valid_groups:
65
+ cmd.extend(["-G", ",".join(valid_groups)])
66
+
67
+ cmd.append(username)
68
+
69
+ try:
70
+ logger.info(f"Adding user: {' '.join(cmd)}")
71
+ result = subprocess.run(
72
+ cmd,
73
+ capture_output=True,
74
+ text=True,
75
+ timeout=config.security.command_timeout,
76
+ check=False
77
+ )
78
+
79
+ if result.returncode == 0:
80
+ return {
81
+ "success": True,
82
+ "message": f"User '{username}' created successfully",
83
+ "details": {
84
+ "username": username,
85
+ "groups": groups or [],
86
+ "home_created": create_home,
87
+ "shell": shell
88
+ },
89
+ "tool_type": "sensitive_operation"
90
+ }
91
+ else:
92
+ return {
93
+ "success": False,
94
+ "error": f"Failed to create user: {result.stderr}",
95
+ "exit_code": result.returncode,
96
+ "tool_type": "sensitive_operation"
97
+ }
98
+
99
+ except Exception as e:
100
+ return {
101
+ "success": False,
102
+ "error": f"Error creating user: {str(e)}",
103
+ "tool_type": "sensitive_operation"
104
+ }
105
+
106
+ def create_file(filepath: str, content: str, permissions: str = "644",
107
+ owner: Optional[str] = None) -> dict:
108
+ """
109
+ Create a new file with specified content and permissions.
110
+ This is a SENSITIVE operation that modifies the filesystem.
111
+ """
112
+ # Validate file path
113
+ if not _validate_filepath(filepath):
114
+ return {
115
+ "success": False,
116
+ "error": "Invalid file path - must be absolute and safe",
117
+ "tool_type": "sensitive_operation"
118
+ }
119
+
120
+ # Check if file already exists
121
+ if os.path.exists(filepath):
122
+ return {
123
+ "success": False,
124
+ "error": f"File '{filepath}' already exists",
125
+ "tool_type": "sensitive_operation"
126
+ }
127
+
128
+ try:
129
+ # Create directory if needed
130
+ directory = os.path.dirname(filepath)
131
+ if directory and not os.path.exists(directory):
132
+ os.makedirs(directory, mode=0o755)
133
+
134
+ # Write file
135
+ with open(filepath, 'w') as f:
136
+ f.write(content)
137
+
138
+ # Set permissions
139
+ os.chmod(filepath, int(permissions, 8))
140
+
141
+ # Change owner if specified
142
+ if owner:
143
+ try:
144
+ user_info = pwd.getpwnam(owner)
145
+ os.chown(filepath, user_info.pw_uid, user_info.pw_gid)
146
+ except KeyError:
147
+ logger.warning(f"User '{owner}' not found, keeping current owner")
148
+
149
+ return {
150
+ "success": True,
151
+ "message": f"File '{filepath}' created successfully",
152
+ "details": {
153
+ "path": filepath,
154
+ "permissions": permissions,
155
+ "owner": owner,
156
+ "size": len(content)
157
+ },
158
+ "tool_type": "sensitive_operation"
159
+ }
160
+
161
+ except Exception as e:
162
+ return {
163
+ "success": False,
164
+ "error": f"Error creating file: {str(e)}",
165
+ "tool_type": "sensitive_operation"
166
+ }
167
+
168
+ def change_permission(path: str, permissions: str, recursive: bool = False,
169
+ owner: Optional[str] = None) -> dict:
170
+ """
171
+ Change file or directory permissions.
172
+ This is a SENSITIVE operation that affects system security.
173
+ """
174
+ if not os.path.exists(path):
175
+ return {
176
+ "success": False,
177
+ "error": f"Path '{path}' does not exist",
178
+ "tool_type": "sensitive_operation"
179
+ }
180
+
181
+ try:
182
+ # Change permissions
183
+ perm_octal = int(permissions, 8)
184
+
185
+ if recursive and os.path.isdir(path):
186
+ # Recursively change permissions
187
+ for root, dirs, files in os.walk(path):
188
+ os.chmod(root, perm_octal)
189
+ for file in files:
190
+ os.chmod(os.path.join(root, file), perm_octal)
191
+ else:
192
+ os.chmod(path, perm_octal)
193
+
194
+ # Change owner if specified
195
+ if owner:
196
+ try:
197
+ user_info = pwd.getpwnam(owner)
198
+ if recursive and os.path.isdir(path):
199
+ for root, dirs, files in os.walk(path):
200
+ os.chown(root, user_info.pw_uid, user_info.pw_gid)
201
+ for file in files:
202
+ os.chown(os.path.join(root, file), user_info.pw_uid, user_info.pw_gid)
203
+ else:
204
+ os.chown(path, user_info.pw_uid, user_info.pw_gid)
205
+ except KeyError:
206
+ return {
207
+ "success": False,
208
+ "error": f"User '{owner}' not found",
209
+ "tool_type": "sensitive_operation"
210
+ }
211
+
212
+ return {
213
+ "success": True,
214
+ "message": f"Permissions changed for '{path}'",
215
+ "details": {
216
+ "path": path,
217
+ "permissions": permissions,
218
+ "recursive": recursive,
219
+ "owner": owner
220
+ },
221
+ "tool_type": "sensitive_operation"
222
+ }
223
+
224
+ except Exception as e:
225
+ return {
226
+ "success": False,
227
+ "error": f"Error changing permissions: {str(e)}",
228
+ "tool_type": "sensitive_operation"
229
+ }
230
+
231
+ def run_safe_command(command: str, args: Optional[List[str]] = None,
232
+ working_directory: Optional[str] = None) -> dict:
233
+ """
234
+ Execute safe, whitelisted system commands for information gathering.
235
+ These are READ-ONLY or low-risk operations.
236
+ """
237
+ if command not in config.security.safe_commands:
238
+ return {
239
+ "success": False,
240
+ "error": f"Command '{command}' not in whitelist",
241
+ "whitelist": config.security.safe_commands,
242
+ "tool_type": "safe_command"
243
+ }
244
+
245
+ # Build command
246
+ cmd_parts = [command]
247
+ if args:
248
+ # Validate and sanitize arguments
249
+ sanitized_args = [shlex.quote(arg) for arg in args if arg]
250
+ cmd_parts.extend(sanitized_args)
251
+
252
+ # Set working directory
253
+ if not working_directory:
254
+ working_directory = "/home"
255
+
256
+ try:
257
+ logger.info(f"Executing safe command: {' '.join(cmd_parts)} in {working_directory}")
258
+
259
+ result = subprocess.run(
260
+ cmd_parts,
261
+ cwd=working_directory,
262
+ capture_output=True,
263
+ text=True,
264
+ timeout=config.security.command_timeout,
265
+ check=False # Don't raise exception on non-zero exit
266
+ )
267
+
268
+ return {
269
+ "success": result.returncode == 0,
270
+ "output": result.stdout,
271
+ "error": result.stderr,
272
+ "exit_code": result.returncode,
273
+ "command": ' '.join(cmd_parts),
274
+ "tool_type": "safe_command"
275
+ }
276
+
277
+ except subprocess.TimeoutExpired:
278
+ return {
279
+ "success": False,
280
+ "error": f"Command timed out after {config.security.command_timeout} seconds",
281
+ "exit_code": -1,
282
+ "tool_type": "safe_command"
283
+ }
284
+ except Exception as e:
285
+ return {
286
+ "success": False,
287
+ "error": f"Execution error: {str(e)}",
288
+ "exit_code": -1,
289
+ "tool_type": "safe_command"
290
+ }
291
+
292
+ # Helper functions
293
+
294
+ def _validate_username(username: str) -> bool:
295
+ """Validate username format"""
296
+ if not username or not isinstance(username, str):
297
+ return False
298
+ if len(username) > 32: # Typical Linux limit
299
+ return False
300
+ if not username[0].islower() or not username[0].isalpha():
301
+ return False
302
+ return all(c.islower() or c.isdigit() or c in '_-' for c in username)
303
+
304
+ def _validate_filepath(filepath: str) -> bool:
305
+ """Basic file path validation"""
306
+ if not filepath or not isinstance(filepath, str):
307
+ return False
308
+ if not os.path.isabs(filepath):
309
+ return False
310
+ # Prevent path traversal
311
+ if '..' in filepath:
312
+ return False
313
+ # Prevent access to sensitive system files
314
+ forbidden_paths = ['/etc/passwd', '/etc/shadow', '/etc/sudoers']
315
+ if filepath in forbidden_paths:
316
+ return False
317
+ return True
318
+ """
319
+ Add a new system user with proper validation.
320
+ This is a SENSITIVE operation requiring careful consideration.
321
 
322
  Args:
323
  username: Username (lowercase, alphanumeric, starts with letter)