blob: c375d177fc220284e897620625378c2307e6c574 [file] [log] [blame]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001#!/usr/bin/python -u
2# -*- coding: utf-8 -*-
3#
4# Copyright 2015 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08008import argparse
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08009import contextlib
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080010import fcntl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080011import hashlib
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080012import json
13import logging
14import os
15import Queue
Wei-Ning Huang829e0c82015-05-26 14:37:23 +080016import re
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080017import select
Wei-Ning Huanga301f572015-06-03 17:34:21 +080018import signal
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080019import socket
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080020import ssl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080021import struct
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080022import subprocess
23import sys
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080024import termios
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080025import threading
26import time
Joel Kitching22b89042015-08-06 18:23:29 +080027import traceback
Wei-Ning Huang39169902015-09-19 06:00:23 +080028import tty
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080029import urllib2
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080030import uuid
31
Wei-Ning Huang2132de32015-04-13 17:24:38 +080032import jsonrpclib
33from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
34
35
36_GHOST_RPC_PORT = 4499
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080037
38_OVERLORD_PORT = 4455
39_OVERLORD_LAN_DISCOVERY_PORT = 4456
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080040_OVERLORD_HTTP_PORT = 9000
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080041
42_BUFSIZE = 8192
43_RETRY_INTERVAL = 2
44_SEPARATOR = '\r\n'
45_PING_TIMEOUT = 3
46_PING_INTERVAL = 5
47_REQUEST_TIMEOUT_SECS = 60
48_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang2132de32015-04-13 17:24:38 +080049_DEFAULT_BIND_ADDRESS = '0.0.0.0'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080050
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080051_CONTROL_START = 128
52_CONTROL_END = 129
53
Wei-Ning Huanga301f572015-06-03 17:34:21 +080054_BLOCK_SIZE = 4096
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080055_CONNECT_TIMEOUT = 3
Wei-Ning Huanga301f572015-06-03 17:34:21 +080056
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +080057# Stream control
58_STDIN_CLOSED = '##STDIN_CLOSED##'
59
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080060# A string that will always be included in the response of
61# GET http://OVERLORD_SERVER:_OVERLORD_HTTP_PORT
62_OVERLORD_RESPONSE_KEYWORD = 'HTTP'
63
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080064SUCCESS = 'success'
65FAILED = 'failed'
66DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080067
Joel Kitching22b89042015-08-06 18:23:29 +080068
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080069class PingTimeoutError(Exception):
70 pass
71
72
73class RequestError(Exception):
74 pass
75
76
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080077class BufferedSocket(object):
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080078 """A buffered socket that supports unrecv.
79
80 Allow putting back data back to the socket for the next recv() call.
81 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080082 def __init__(self, sock):
83 self.sock = sock
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080084 self._buf = ''
85
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080086 def fileno(self):
87 return self.sock.fileno()
88
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080089 def Recv(self, bufsize, flags=0):
90 if self._buf:
91 if len(self._buf) >= bufsize:
92 ret = self._buf[:bufsize]
93 self._buf = self._buf[bufsize:]
94 return ret
95 else:
96 ret = self._buf
97 self._buf = ''
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080098 return ret + self.sock.recv(bufsize - len(ret), flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080099 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800100 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800101
102 def UnRecv(self, buf):
103 self._buf = buf + self._buf
104
105 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800106 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800107
108 def RecvBuf(self):
109 """Only recive from buffer."""
110 ret = self._buf
111 self._buf = ''
112 return ret
113
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800114 def Close(self):
115 self.sock.close()
116
117
118class TLSSettings(object):
119 def __init__(self, tls_cert_file, enable_tls_without_verify):
120 """Constructor.
121
122 Args:
123 tls_cert_file: TLS certificate in PEM format.
124 enable_tls_without_verify: enable TLS but don't verify certificate.
125 """
126 self._tls_cert_file = tls_cert_file
127 self._tls_context = None
128
129 if self._tls_cert_file is not None or enable_tls_without_verify:
130 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
131 self._tls_context.verify_mode = ssl.CERT_NONE
132 if self._tls_cert_file:
133 self._tls_context.verify_mode = ssl.CERT_REQUIRED
134 self._tls_context.check_hostname = True
135 try:
136 self._tls_context.load_verify_locations(self._tls_cert_file)
137 except IOError as e:
138 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
139 sys.exit(1)
140
141 def Enabled(self):
142 return self._tls_context is not None
143
144 def Context(self):
145 return self._tls_context
146
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800147
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800148class Ghost(object):
149 """Ghost implements the client protocol of Overlord.
150
151 Ghost provide terminal/shell/logcat functionality and manages the client
152 side connectivity.
153 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800154 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800155
156 MODE_NAME = {
157 NONE: 'NONE',
158 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800159 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800160 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800161 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800162 FILE: 'File',
163 FORWARD: 'Forward'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800164 }
165
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800166 RANDOM_MID = '##random_mid##'
167
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800168 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
169 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800170 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800171 """Constructor.
172
173 Args:
174 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800175 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800176 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800177 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
178 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800179 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800180 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800181 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800182 terminal_sid: the terminal session ID associate with this client. This is
183 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800184 tty_device: the terminal device to open, if tty_device is None, as pseudo
185 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800186 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800187 file_op: a tuple (action, filepath, perm). action is either 'download' or
188 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800189 port: port number to forward.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800190 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800191 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
192 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800193 if mode == Ghost.SHELL:
194 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800195 if mode == Ghost.FILE:
196 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800197
198 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800199 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800200 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800201 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800202 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800203 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800204 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800205 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800206 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800207 self._ttyname_to_sid = {}
208 self._terminal_sid_to_pid = {}
209 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800210 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800211 self._register_status = DISCONNECTED
212 self._reset = threading.Event()
213
214 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800215 self._requests = {}
216 self._queue = Queue.Queue()
217
218 # Protocol specific
219 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800220 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800221 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800222 self._file_op = file_op
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800223 self._download_queue = Queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800224 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800225
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800226 def SetIgnoreChild(self, status):
227 # Only ignore child for Agent since only it could spawn child Ghost.
228 if self._mode == Ghost.AGENT:
229 signal.signal(signal.SIGCHLD,
230 signal.SIG_IGN if status else signal.SIG_DFL)
231
232 def GetFileSha1(self, filename):
233 with open(filename, 'r') as f:
234 return hashlib.sha1(f.read()).hexdigest()
235
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800236 def OverlordHTTPSEnabled(self):
237 """Determine if SSL is enabled on the Overlord HTTP server."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800238 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
239 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800240 sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huang58833882015-09-16 16:52:37 +0800241 sock.connect((self._connected_addr[0], _OVERLORD_HTTP_PORT))
242 sock.send('GET\r\n')
243
244 data = sock.recv(16)
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800245 return _OVERLORD_RESPONSE_KEYWORD not in data
Wei-Ning Huang58833882015-09-16 16:52:37 +0800246 except Exception:
247 return False # For whatever reason above failed, assume HTTP
248
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800249 def Upgrade(self):
250 logging.info('Upgrade: initiating upgrade sequence...')
251
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800252 server_tls_enabled = self.OverlordHTTPSEnabled()
253 if self._tls_settings.Enabled() and not server_tls_enabled:
254 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
255 'without TLS enabled! Possible mis-configuration or '
256 'DNS/IP spoofing detected, abort')
257 return
258
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800259 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800260 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800261 's' if server_tls_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800262 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800263
264 # Download sha1sum for ghost.py for verification
265 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800266 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800267 urllib2.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
268 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800269 if f.getcode() != 200:
270 raise RuntimeError('HTTP status %d' % f.getcode())
271 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800272 except (ssl.SSLError, ssl.CertificateError) as e:
273 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
274 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800275 except Exception:
276 logging.error('Upgrade: failed to download sha1sum file, abort')
277 return
278
279 if self.GetFileSha1(scriptpath) == sha1sum:
280 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
281 return
282
283 # Download upgrade version of ghost.py
284 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800285 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800286 urllib2.urlopen(url, timeout=_CONNECT_TIMEOUT,
287 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800288 if f.getcode() != 200:
289 raise RuntimeError('HTTP status %d' % f.getcode())
290 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800291 except (ssl.SSLError, ssl.CertificateError) as e:
292 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
293 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800294 except Exception:
295 logging.error('Upgrade: failed to download upgrade, abort')
296 return
297
298 # Compare SHA1 sum
299 if hashlib.sha1(data).hexdigest() != sha1sum:
300 logging.error('Upgrade: sha1sum mismatch, abort')
301 return
302
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800303 try:
304 with open(scriptpath, 'w') as f:
305 f.write(data)
306 except Exception:
307 logging.error('Upgrade: failed to write upgrade onto disk, abort')
308 return
309
310 logging.info('Upgrade: restarting ghost...')
311 self.CloseSockets()
312 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800313 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800314
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800315 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800316 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800317 if self._prop_file:
318 with open(self._prop_file, 'r') as f:
319 self._properties = json.loads(f.read())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800320 except Exception as e:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800321 logging.error('LoadProperties: ' + str(e))
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800322
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800323 def CloseSockets(self):
324 # Close sockets opened by parent process, since we don't use it anymore.
325 for fd in os.listdir('/proc/self/fd/'):
326 try:
327 real_fd = os.readlink('/proc/self/fd/%s' % fd)
328 if real_fd.startswith('socket'):
329 os.close(int(fd))
330 except Exception:
331 pass
332
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800333 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800334 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800335 """Spawn a child ghost with specific mode.
336
337 Returns:
338 The spawned child process pid.
339 """
Joel Kitching22b89042015-08-06 18:23:29 +0800340 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800341 self.SetIgnoreChild(False)
342
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800343 pid = os.fork()
344 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800345 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800346 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
347 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800348 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800349 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800350 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800351 sys.exit(0)
352 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800353 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800354 return pid
355
356 def Timestamp(self):
357 return int(time.time())
358
359 def GetGateWayIP(self):
360 with open('/proc/net/route', 'r') as f:
361 lines = f.readlines()
362
363 ips = []
364 for line in lines:
365 parts = line.split('\t')
366 if parts[2] == '00000000':
367 continue
368
369 try:
370 h = parts[2].decode('hex')
371 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
372 except TypeError:
373 pass
374
375 return ips
376
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800377 def GetShopfloorIP(self):
378 try:
379 import factory_common # pylint: disable=W0612
380 from cros.factory.test import shopfloor
381
382 url = shopfloor.get_server_url()
383 match = re.match(r'^https?://(.*):.*$', url)
384 if match:
385 return [match.group(1)]
386 except Exception:
387 pass
388 return []
389
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800390 def GetMachineID(self):
391 """Generates machine-dependent ID string for a machine.
392 There are many ways to generate a machine ID:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800393 1. factory device_id
394 2. factory device-data
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800395 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
396 4. MAC address
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800397 We follow the listed order to generate machine ID, and fallback to the next
398 alternative if the previous doesn't work.
399 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800400 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800401 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800402 elif self._mid:
403 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800404
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800405 # Try factory device id
406 try:
407 import factory_common # pylint: disable=W0612
408 from cros.factory.test import event_log
409 with open(event_log.DEVICE_ID_PATH) as f:
410 return f.read().strip()
411 except Exception:
412 pass
413
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800414 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800415 try:
416 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
417 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800418 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800419 stdout, unused_stderr = p.communicate()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800420 if stdout == '':
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800421 raise RuntimeError('empty mlb number')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800422 return stdout.strip()
423 except Exception:
424 pass
425
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800426 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800427 try:
428 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
429 return f.read().strip()
430 except Exception:
431 pass
432
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800433 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800434 try:
435 macs = []
436 ifaces = sorted(os.listdir('/sys/class/net'))
437 for iface in ifaces:
438 if iface == 'lo':
439 continue
440
441 with open('/sys/class/net/%s/address' % iface, 'r') as f:
442 macs.append(f.read().strip())
443
444 return ';'.join(macs)
445 except Exception:
446 pass
447
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800448 raise RuntimeError('can\'t generate machine ID')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800449
450 def Reset(self):
451 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800452 if self._sock is not None:
453 self._sock.Close()
454 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800455 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800456 self._last_ping = 0
457 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800458 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800459 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800460
461 def SendMessage(self, msg):
462 """Serialize the message and send it through the socket."""
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800463 self._sock.Send(json.dumps(msg) + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800464
465 def SendRequest(self, name, args, handler=None,
466 timeout=_REQUEST_TIMEOUT_SECS):
467 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800468 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800469
470 rid = str(uuid.uuid4())
471 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800472 if timeout >= 0:
473 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800474 self.SendMessage(msg)
475
476 def SendResponse(self, omsg, status, params=None):
477 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
478 self.SendMessage(msg)
479
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800480 def HandleTTYControl(self, fd, control_str):
481 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800482 command = msg['command']
483 params = msg['params']
484 if command == 'resize':
485 # some error happened on websocket
486 if len(params) != 2:
487 return
488 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
489 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
490 else:
491 logging.warn('Invalid request command "%s"', command)
492
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800493 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800494 """Spawn a TTY server and forward I/O to the TCP socket."""
495 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800496
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800497 try:
498 if self._tty_device is None:
499 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800500
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800501 if pid == 0:
502 ttyname = os.readlink('/proc/%d/fd/0' % os.getpid())
503 try:
504 server = GhostRPCServer()
505 server.RegisterTTY(self._session_id, ttyname)
506 server.RegisterSession(self._session_id, os.getpid())
507 except Exception:
508 # If ghost is launched without RPC server, the call will fail but we
509 # can ignore it.
510 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800511
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800512 # The directory that contains the current running ghost script
513 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800514
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800515 env = os.environ.copy()
516 env['USER'] = os.getenv('USER', 'root')
517 env['HOME'] = os.getenv('HOME', '/root')
518 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
519 os.chdir(env['HOME'])
520 os.execve(_SHELL, [_SHELL], env)
521 else:
522 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800523 tty.setraw(fd)
524 attr = termios.tcgetattr(fd)
525 attr[0] &= ~(termios.IXON | termios.IXOFF)
526 attr[2] |= termios.CLOCAL
527 attr[2] &= ~termios.CRTSCTS
528 attr[4] = termios.B115200
529 attr[5] = termios.B115200
530 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800531
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800532 nonlocals = {'control_state': None, 'control_str': ''}
533
534 def _ProcessBuffer(buf):
535 write_buffer = ''
536 while buf:
537 if nonlocals['control_state']:
538 if chr(_CONTROL_END) in buf:
539 index = buf.index(chr(_CONTROL_END))
540 nonlocals['control_str'] += buf[:index]
541 self.HandleTTYControl(fd, nonlocals['control_str'])
542 nonlocals['control_state'] = None
543 nonlocals['control_str'] = ''
544 buf = buf[index+1:]
545 else:
546 nonlocals['control_str'] += buf
547 buf = ''
548 else:
549 if chr(_CONTROL_START) in buf:
550 nonlocals['control_state'] = _CONTROL_START
551 index = buf.index(chr(_CONTROL_START))
552 write_buffer += buf[:index]
553 buf = buf[index+1:]
554 else:
555 write_buffer += buf
556 buf = ''
557
558 if write_buffer:
559 os.write(fd, write_buffer)
560
561 _ProcessBuffer(self._sock.RecvBuf())
562
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800563 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800564 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800565
566 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800567 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800568
569 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800570 buf = self._sock.Recv(_BUFSIZE)
571 if len(buf) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800572 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800573 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800574 except Exception as e:
575 logging.error('SpawnTTYServer: %s', e)
576 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800577 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800578
579 logging.info('SpawnTTYServer: terminated')
580 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800581
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800582 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800583 """Spawn a shell server and forward input/output from/to the TCP socket."""
584 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800585
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800586 # Add ghost executable to PATH
587 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
588 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800589 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800590
591 # Execute shell command from HOME directory
592 os.chdir(os.getenv('HOME', '/tmp'))
593
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800594 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800595 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800596 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800597
598 def make_non_block(fd):
599 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
600 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
601
602 make_non_block(p.stdout)
603 make_non_block(p.stderr)
604
605 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800606 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800607
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800608 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800609 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800610 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800611 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800612 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800613
614 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800615 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800616
617 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800618 ret = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800619 if len(ret) == 0:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800620 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800621
622 try:
623 idx = ret.index(_STDIN_CLOSED * 2)
624 p.stdin.write(ret[:idx])
625 p.stdin.close()
626 except ValueError:
627 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800628 p.poll()
629 if p.returncode != None:
630 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800631 except Exception as e:
632 logging.error('SpawnShellServer: %s', e)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800633 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800634 # Check if the process is terminated. If not, Send SIGTERM to process,
635 # then wait for 1 second. Send another SIGKILL to make sure the process is
636 # terminated.
637 p.poll()
638 if p.returncode is None:
639 try:
640 p.terminate()
641 time.sleep(1)
642 p.kill()
643 except Exception:
644 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800645
646 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800647 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800648
649 logging.info('SpawnShellServer: terminated')
650 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800651
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800652 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800653 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800654 try:
655 size = os.stat(self._file_op[1]).st_size
656 except OSError as e:
657 logging.error('InitiateFileOperation: download: %s', e)
658 sys.exit(1)
659
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800660 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800661 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800662 'filename': os.path.basename(self._file_op[1]),
663 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800664 elif self._file_op[0] == 'upload':
665 self.SendRequest('clear_to_upload', {}, timeout=-1)
666 self.StartUploadServer()
667 else:
668 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800669
670 def StartDownloadServer(self):
671 logging.info('StartDownloadServer: started')
672
673 try:
674 with open(self._file_op[1], 'rb') as f:
675 while True:
676 data = f.read(_BLOCK_SIZE)
677 if len(data) == 0:
678 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800679 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800680 except Exception as e:
681 logging.error('StartDownloadServer: %s', e)
682 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800683 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800684
685 logging.info('StartDownloadServer: terminated')
686 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800687
Wei-Ning Huange2981862015-08-03 15:03:08 +0800688 def StartUploadServer(self):
689 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800690 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800691 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800692 dirname = os.path.dirname(filepath)
693 if not os.path.exists(dirname):
694 try:
695 os.makedirs(dirname)
696 except Exception:
697 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800698
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800699 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800700 if self._file_op[2]:
701 os.fchmod(f.fileno(), self._file_op[2])
702
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800703 f.write(self._sock.RecvBuf())
704
Wei-Ning Huange2981862015-08-03 15:03:08 +0800705 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800706 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800707 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800708 buf = self._sock.Recv(_BLOCK_SIZE)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800709 if len(buf) == 0:
710 break
711 f.write(buf)
712 except socket.error as e:
713 logging.error('StartUploadServer: socket error: %s', e)
714 except Exception as e:
715 logging.error('StartUploadServer: %s', e)
716 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800717 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800718
719 logging.info('StartUploadServer: terminated')
720 sys.exit(0)
721
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800722 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800723 """Spawn a port forwarding server and forward I/O to the TCP socket."""
724 logging.info('SpawnPortForwardServer: started')
725
726 src_sock = None
727 try:
728 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800729 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800730 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800731
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800732 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800733
734 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800735 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800736
737 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800738 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800739 if len(data) == 0:
740 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800741 src_sock.send(data)
742
743 if src_sock in rd:
744 data = src_sock.recv(_BUFSIZE)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800745 if len(data) == 0:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800746 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800747 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800748 except Exception as e:
749 logging.error('SpawnPortForwardServer: %s', e)
750 finally:
751 if src_sock:
752 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800753 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800754
755 logging.info('SpawnPortForwardServer: terminated')
756 sys.exit(0)
757
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800758 def Ping(self):
759 def timeout_handler(x):
760 if x is None:
761 raise PingTimeoutError
762
763 self._last_ping = self.Timestamp()
764 self.SendRequest('ping', {}, timeout_handler, 5)
765
Wei-Ning Huangae923642015-09-24 14:08:09 +0800766 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800767 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800768 filepath = params['filename']
769 if not os.path.isabs(filepath):
770 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
771
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800772 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800773 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800774 pass
775 except Exception as e:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800776 return self.SendResponse(msg, str(e))
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800777
778 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800779 file_op=('download', filepath))
780 self.SendResponse(msg, SUCCESS)
781
782 def HandleFileUploadRequest(self, msg):
783 params = msg['params']
784
785 # Resolve upload filepath
786 filename = params['filename']
787 dest_path = filename
788
789 # If dest is specified, use it first
790 dest_path = params.get('dest', '')
791 if dest_path:
792 if not os.path.isabs(dest_path):
793 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
794
795 if os.path.isdir(dest_path):
796 dest_path = os.path.join(dest_path, filename)
797 else:
798 target_dir = os.getenv('HOME', '/tmp')
799
800 # Terminal session ID found, upload to it's current working directory
801 if params.has_key('terminal_sid'):
802 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
803 if pid:
804 target_dir = os.readlink('/proc/%d/cwd' % pid)
805
806 dest_path = os.path.join(target_dir, filename)
807
808 try:
809 os.makedirs(os.path.dirname(dest_path))
810 except Exception:
811 pass
812
813 try:
814 with open(dest_path, 'w') as _:
815 pass
816 except Exception as e:
817 return self.SendResponse(msg, str(e))
818
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800819 # If not check_only, spawn FILE mode ghost agent to handle upload
820 if not params.get('check_only', False):
821 self.SpawnGhost(self.FILE, params['sid'],
822 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800823 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800824
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800825 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800826 command = msg['name']
827 params = msg['params']
828
829 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800830 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800831 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800832 self.SpawnGhost(self.TERMINAL, params['sid'],
833 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800834 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800835 elif command == 'shell':
836 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800837 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800838 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800839 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800840 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800841 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800842 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800843 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800844 elif command == 'forward':
845 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
846 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800847
848 def HandleResponse(self, response):
849 rid = str(response['rid'])
850 if rid in self._requests:
851 handler = self._requests[rid][2]
852 del self._requests[rid]
853 if callable(handler):
854 handler(response)
855 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800856 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800857
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800858 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800859 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800860 try:
861 index = buf.index(_SEPARATOR)
862 except ValueError:
863 self._sock.UnRecv(buf)
864 return
865
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800866 msgs_json = [buf[:index]]
867 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800868 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800869 msgs_json = buf.split(_SEPARATOR)
870 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800871
872 for msg_json in msgs_json:
873 try:
874 msg = json.loads(msg_json)
875 except ValueError:
876 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800877 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800878 continue
879
880 if 'name' in msg:
881 self.HandleRequest(msg)
882 elif 'response' in msg:
883 self.HandleResponse(msg)
884 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800885 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800886
887 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800888 """Scans for pending requests which have timed out.
889
890 If any timed-out requests are discovered, their handler is called with the
891 special response value of None.
892 """
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800893 for rid in self._requests.keys()[:]:
894 request_time, timeout, handler = self._requests[rid]
895 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800896 if callable(handler):
897 handler(None)
898 else:
899 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800900 del self._requests[rid]
901
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800902 def InitiateDownload(self):
903 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800904 sid = self._ttyname_to_sid[ttyname]
905 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800906 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800907
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800908 def Listen(self):
909 try:
910 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800911 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
912 _PING_INTERVAL / 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800913
914 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800915 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800916
917 # Socket is closed
918 if len(data) == 0:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800919 break
920
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800921 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800922
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800923 if (self._mode == self.AGENT and
924 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800925 self.Ping()
926 self.ScanForTimeoutRequests()
927
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800928 if not self._download_queue.empty():
929 self.InitiateDownload()
930
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800931 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800932 break
933 except socket.error:
934 raise RuntimeError('Connection dropped')
935 except PingTimeoutError:
936 raise RuntimeError('Connection timeout')
937 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800938 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800939
940 self._queue.put('resume')
941
942 if self._mode != Ghost.AGENT:
943 sys.exit(1)
944
945 def Register(self):
946 non_local = {}
947 for addr in self._overlord_addrs:
948 non_local['addr'] = addr
949 def registered(response):
950 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800951 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800952 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +0800953
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800954 self._register_status = response['response']
955 if response['response'] != SUCCESS:
956 self._reset.set()
957 raise RuntimeError('Reigster: ' + response['response'])
958 else:
959 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
960 self._connected_addr = non_local['addr']
961 self.Upgrade() # Check for upgrade
962 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +0800963
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800964 try:
965 logging.info('Trying %s:%d ...', *addr)
966 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800967
968 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
969 sock.settimeout(_CONNECT_TIMEOUT)
970
971 try:
972 if self._tls_settings.Enabled():
973 tls_context = self._tls_settings.Context()
974 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
975
976 sock.connect(addr)
977 except (ssl.SSLError, ssl.CertificateError) as e:
978 logging.error('%s: %s', e.__class__.__name__, e)
979 continue
980 except IOError as e:
981 if e.errno == 2: # No such file or directory
982 logging.error('%s: %s', e.__class__.__name__, e)
983 continue
984 raise
985
986 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800987
988 logging.info('Connection established, registering...')
989 handler = {
990 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800991 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800992 Ghost.SHELL: self.SpawnShellServer,
993 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800994 Ghost.FORWARD: self.SpawnPortForwardServer,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800995 }[self._mode]
996
997 # Machine ID may change if MAC address is used (USB-ethernet dongle
998 # plugged/unplugged)
999 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001000 self.SendRequest('register',
1001 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001002 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001003 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001004 except socket.error:
1005 pass
1006 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001007 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001008 self.Listen()
1009
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001010 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001011
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001012 def Reconnect(self):
1013 logging.info('Received reconnect request from RPC server, reconnecting...')
1014 self._reset.set()
1015
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001016 def GetStatus(self):
1017 return self._register_status
1018
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001019 def AddToDownloadQueue(self, ttyname, filename):
1020 self._download_queue.put((ttyname, filename))
1021
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001022 def RegisterTTY(self, session_id, ttyname):
1023 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001024
1025 def RegisterSession(self, session_id, process_id):
1026 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001027
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001028 def StartLanDiscovery(self):
1029 """Start to listen to LAN discovery packet at
1030 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001031
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001032 def thread_func():
1033 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1034 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1035 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001036 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001037 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1038 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001039 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001040 return
1041
1042 logging.info('LAN Discovery: started')
1043 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001044 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001045
1046 if s in rd:
1047 data, source_addr = s.recvfrom(_BUFSIZE)
1048 parts = data.split()
1049 if parts[0] == 'OVERLORD':
1050 ip, port = parts[1].split(':')
1051 if not ip:
1052 ip = source_addr[0]
1053 self._queue.put((ip, int(port)), True)
1054
1055 try:
1056 obj = self._queue.get(False)
1057 except Queue.Empty:
1058 pass
1059 else:
1060 if type(obj) is not str:
1061 self._queue.put(obj)
1062 elif obj == 'pause':
1063 logging.info('LAN Discovery: paused')
1064 while obj != 'resume':
1065 obj = self._queue.get(True)
1066 logging.info('LAN Discovery: resumed')
1067
1068 t = threading.Thread(target=thread_func)
1069 t.daemon = True
1070 t.start()
1071
1072 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001073 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001074 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1075 logRequests=False)
1076 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001077 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001078 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001079 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001080 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001081 t = threading.Thread(target=rpc_server.serve_forever)
1082 t.daemon = True
1083 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001084
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001085 def ScanServer(self):
1086 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
1087 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1088 if addr not in self._overlord_addrs:
1089 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001090
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001091 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001092 logging.info('%s started', self.MODE_NAME[self._mode])
1093 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001094 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001095
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001096 # We don't care about child process's return code, not wait is needed. This
1097 # is used to prevent zombie process from lingering in the system.
1098 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001099
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001100 if lan_disc:
1101 self.StartLanDiscovery()
1102
1103 if rpc_server:
1104 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001105
1106 try:
1107 while True:
1108 try:
1109 addr = self._queue.get(False)
1110 except Queue.Empty:
1111 pass
1112 else:
1113 if type(addr) == tuple and addr not in self._overlord_addrs:
1114 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1115 self._overlord_addrs.append(addr)
1116
1117 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001118 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001119 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001120 # Don't show stack trace for RuntimeError, which we use in this file for
1121 # plausible and expected errors (such as can't connect to server).
1122 except RuntimeError as e:
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001123 logging.info('%s, retrying in %ds', e.message, _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001124 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001125 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001126 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001127 traceback.print_tb(exc_traceback)
1128 logging.info('%s: %s, retrying in %ds',
1129 e.__class__.__name__, e.message, _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001130 time.sleep(_RETRY_INTERVAL)
1131
1132 self.Reset()
1133 except KeyboardInterrupt:
1134 logging.error('Received keyboard interrupt, quit')
1135 sys.exit(0)
1136
1137
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001138def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001139 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001140 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1141
1142
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001143def ForkToBackground():
1144 """Fork process to run in background."""
1145 pid = os.fork()
1146 if pid != 0:
1147 logging.info('Ghost(%d) running in background.', pid)
1148 sys.exit(0)
1149
1150
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001151def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001152 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001153 filepath = os.path.abspath(filename)
1154 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001155 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001156 sys.exit(1)
1157
1158 # Check if we actually have permission to read the file
1159 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001160 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001161 sys.exit(1)
1162
1163 server = GhostRPCServer()
1164 server.AddToDownloadQueue(os.ttyname(0), filepath)
1165 sys.exit(0)
1166
1167
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001168def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001169 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001170 logger = logging.getLogger()
1171 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001172 handler = logging.StreamHandler()
1173 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1174 handler.setFormatter(formatter)
1175 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001176
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001177 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001178 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1179 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001180 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1181 default=None, help='use MID as machine ID')
1182 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1183 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001184 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1185 default=True, help='disable LAN discovery')
1186 parser.add_argument('--no-rpc-server', dest='rpc_server',
1187 action='store_false', default=True,
1188 help='disable RPC server')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001189 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1190 dest='tls_cert_file', type=str, default=None,
1191 help='file containing the server TLS certificate in PEM '
1192 'format')
1193 parser.add_argument('--enable-tls-without-verify',
1194 dest='enable_tls_without_verify', action='store_true',
1195 default=False,
1196 help='Enable TLS but don\'t verify certificate')
Joel Kitching22b89042015-08-06 18:23:29 +08001197 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001198 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001199 help='file containing the JSON representation of client '
1200 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001201 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1202 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001203 parser.add_argument('--reset', dest='reset', default=False,
1204 action='store_true',
1205 help='reset ghost and reload all configs')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001206 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1207 nargs='*', help='overlord server address')
1208 args = parser.parse_args()
1209
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001210 if args.fork:
1211 ForkToBackground()
1212
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001213 if args.reset:
1214 GhostRPCServer().Reconnect()
1215 sys.exit()
1216
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001217 if args.download:
1218 DownloadFile(args.download)
1219
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001220 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001221 addrs += [(x, _OVERLORD_PORT) for x in args.overlord_ip]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001222
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001223 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1224
1225 tls_settings = TLSSettings(args.tls_cert_file, args.enable_tls_without_verify)
1226 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
1227 prop_file=prop_file)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001228 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001229
1230
1231if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001232 try:
1233 main()
1234 except Exception as e:
1235 logging.error(e)