12
12
from twisted.internet.protocol import ProcessProtocol
13
from twisted.internet.defer import Deferred, fail, maybeDeferred
13
from twisted.internet.defer import (
14
Deferred, fail, inlineCallbacks, returnValue, succeed)
14
15
from twisted.internet.error import ProcessDone
17
from landscape import VERSION
18
from landscape.constants import UBUNTU_PATH
16
19
from landscape.lib.scriptcontent import build_script
20
from landscape.lib.fetch import fetch_async, HTTPCodeError
21
from landscape.lib.persist import Persist
17
22
from landscape.manager.plugin import ManagerPlugin, SUCCEEDED, FAILED
20
25
ALL_USERS = object()
21
26
TIMEOUT_RESULT = 102
22
27
PROCESS_FAILED_RESULT = 103
23
# The name "UBUNTU" is used in the variable name due to the fact that the path
24
# is Ubuntu-specific, taken from /etc/login.defs.
25
UBUNTU_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
28
FETCH_ATTACHMENTS_FAILED_RESULT = 104
28
31
class UnknownUserError(Exception):
88
91
class ScriptRunnerMixin(object):
93
@param process_factory: The L{IReactorProcess} provider to run the
90
97
def __init__(self, process_factory=None):
92
@param process_factory: The L{IReactorProcess} provider to run the
95
98
if process_factory is None:
96
99
from twisted.internet import reactor as process_factory
97
100
self.process_factory = process_factory
113
116
script_file.close()
115
118
def _run_script(self, filename, uid, gid, path, env, time_limit):
120
if uid == os.getuid():
122
if gid == os.getgid():
116
125
pp = ProcessAccumulationProtocol(
117
126
self.registry.reactor, self.size_limit)
118
127
self.process_factory.spawnProcess(
158
167
u"Scripts cannot be run as user %s." % (user,),
169
server_supplied_env = message.get("env", None)
161
171
d = self.run_script(message["interpreter"], message["code"],
162
time_limit=message["time-limit"],
163
user=user, attachments=message["attachments"])
172
time_limit=message["time-limit"], user=user,
173
attachments=message["attachments"],
174
server_supplied_env=server_supplied_env)
164
175
d.addCallback(self._respond_success, opid)
165
176
d.addErrback(self._respond_failure, opid)
180
191
code = TIMEOUT_RESULT
181
192
elif failure.check(ProcessFailedError):
182
193
code = PROCESS_FAILED_RESULT
194
elif failure.check(HTTPCodeError):
195
code = FETCH_ATTACHMENTS_FAILED_RESULT
196
return self._respond(
197
FAILED, str(failure.value), opid,
198
FETCH_ATTACHMENTS_FAILED_RESULT)
183
200
if code is not None:
184
201
return self._respond(FAILED, failure.value.data, opid, code)
186
203
return self._respond(FAILED, str(failure), opid)
206
def _save_attachments(self, attachments, uid, gid, computer_id):
207
root_path = self.registry.config.url.rsplit("/", 1)[0] + "/attachment/"
208
attachment_dir = tempfile.mkdtemp()
209
headers = {"User-Agent": "landscape-client/%s" % VERSION,
210
"Content-Type": "application/octet-stream",
211
"X-Computer-ID": computer_id}
212
for filename, attachment_id in attachments.items():
213
if isinstance(attachment_id, str):
214
# Backward compatible behavior
218
data = yield fetch_async(
219
"%s%d" % (root_path, attachment_id),
220
cainfo=self.registry.config.ssl_public_key,
222
full_filename = os.path.join(attachment_dir, filename)
223
attachment = file(full_filename, "wb")
224
os.chmod(full_filename, 0600)
226
os.chown(full_filename, uid, gid)
227
attachment.write(data)
229
os.chmod(attachment_dir, 0700)
231
os.chown(attachment_dir, uid, gid)
232
returnValue(attachment_dir)
188
234
def run_script(self, shell, code, user=None, time_limit=None,
235
attachments=None, server_supplied_env=None):
191
237
Run a script based on a shell and the code.
210
256
if not os.path.exists(shell.split()[0]):
212
258
UnknownInterpreterError(shell))
213
260
uid, gid, path = get_user_info(user)
214
262
fd, filename = tempfile.mkstemp()
215
263
script_file = os.fdopen(fd, "w")
216
self.write_script_file(script_file, filename, shell, code, uid, gid)
264
self.write_script_file(
265
script_file, filename, shell, code, uid, gid)
267
env = {"PATH": UBUNTU_PATH, "USER": user or "", "HOME": path or ""}
268
if server_supplied_env:
269
env.update(server_supplied_env)
223
270
old_umask = os.umask(0022)
225
def run_with_attachments():
227
attachment_dir = tempfile.mkdtemp()
274
filename=os.path.join(self.registry.config.data_path,
276
persist = persist.root_at("registration")
277
computer_id = persist.get("secure-id")
278
d = self._save_attachments(attachments, uid, gid, computer_id)
282
def prepare_script(attachment_dir):
284
if attachment_dir is not None:
228
285
env["LANDSCAPE_ATTACHMENTS"] = attachment_dir
229
for attachment_filename, data in attachments.iteritems():
230
full_filename = os.path.join(
231
attachment_dir, attachment_filename)
232
attachment = file(full_filename, "wb")
233
os.chmod(full_filename, 0600)
235
os.chown(full_filename, uid, gid)
236
attachment.write(data)
238
os.chmod(attachment_dir, 0700)
240
os.chown(attachment_dir, uid, gid)
242
287
return self._run_script(
243
288
filename, uid, gid, path, env, time_limit)
245
result = maybeDeferred(run_with_attachments)
246
return result.addBoth(self._cleanup, filename, env, old_umask)
290
d.addCallback(prepare_script)
291
return d.addBoth(self._cleanup, filename, env, old_umask)
248
293
def _cleanup(self, result, filename, env, old_umask):