blob: df7ba1818885d99a64a6f2ef0e894a9548d06a28 [file] [log] [blame]
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +08001#!/usr/bin/env python
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08002# Copyright 2015 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08006import argparse
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08007import contextlib
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +08008import ctypes
9import ctypes.util
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
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +080015import platform
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080016import Queue
Wei-Ning Huang829e0c82015-05-26 14:37:23 +080017import re
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080018import select
Wei-Ning Huanga301f572015-06-03 17:34:21 +080019import signal
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080020import socket
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080021import ssl
Wei-Ning Huangb05cde32015-08-01 09:48:41 +080022import struct
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080023import subprocess
24import sys
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080025import termios
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080026import threading
27import time
Joel Kitching22b89042015-08-06 18:23:29 +080028import traceback
Wei-Ning Huang39169902015-09-19 06:00:23 +080029import tty
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080030import urllib2
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080031import uuid
32
Wei-Ning Huang2132de32015-04-13 17:24:38 +080033import jsonrpclib
34from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
35
36
Peter Shihc56c5b62016-12-22 12:30:57 +080037_GHOST_RPC_PORT = int(os.getenv('GHOST_RPC_PORT', 4499))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080038
Peter Shihc56c5b62016-12-22 12:30:57 +080039_OVERLORD_PORT = int(os.getenv('OVERLORD_PORT', 4455))
40_OVERLORD_LAN_DISCOVERY_PORT = int(os.getenv('OVERLORD_LD_PORT', 4456))
41_OVERLORD_HTTP_PORT = int(os.getenv('OVERLORD_HTTP_PORT', 9000))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080042
43_BUFSIZE = 8192
44_RETRY_INTERVAL = 2
45_SEPARATOR = '\r\n'
46_PING_TIMEOUT = 3
47_PING_INTERVAL = 5
48_REQUEST_TIMEOUT_SECS = 60
49_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang7dbf4a72016-03-02 20:16:20 +080050_DEFAULT_BIND_ADDRESS = 'localhost'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080051
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080052_CONTROL_START = 128
53_CONTROL_END = 129
54
Wei-Ning Huanga301f572015-06-03 17:34:21 +080055_BLOCK_SIZE = 4096
Wei-Ning Huange0def6a2015-11-05 15:41:24 +080056_CONNECT_TIMEOUT = 3
Wei-Ning Huanga301f572015-06-03 17:34:21 +080057
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +080058# Stream control
59_STDIN_CLOSED = '##STDIN_CLOSED##'
60
Wei-Ning Huang7ec55342015-09-17 08:46:06 +080061SUCCESS = 'success'
62FAILED = 'failed'
63DISCONNECTED = 'disconnected'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080064
Joel Kitching22b89042015-08-06 18:23:29 +080065
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080066class PingTimeoutError(Exception):
67 pass
68
69
70class RequestError(Exception):
71 pass
72
73
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080074class BufferedSocket(object):
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080075 """A buffered socket that supports unrecv.
76
77 Allow putting back data back to the socket for the next recv() call.
78 """
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080079 def __init__(self, sock):
80 self.sock = sock
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080081 self._buf = ''
82
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080083 def fileno(self):
84 return self.sock.fileno()
85
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080086 def Recv(self, bufsize, flags=0):
87 if self._buf:
88 if len(self._buf) >= bufsize:
89 ret = self._buf[:bufsize]
90 self._buf = self._buf[bufsize:]
91 return ret
92 else:
93 ret = self._buf
94 self._buf = ''
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080095 return ret + self.sock.recv(bufsize - len(ret), flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080096 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +080097 return self.sock.recv(bufsize, flags)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +080098
99 def UnRecv(self, buf):
100 self._buf = buf + self._buf
101
102 def Send(self, *args, **kwargs):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800103 return self.sock.send(*args, **kwargs)
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800104
105 def RecvBuf(self):
106 """Only recive from buffer."""
107 ret = self._buf
108 self._buf = ''
109 return ret
110
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800111 def Close(self):
112 self.sock.close()
113
114
115class TLSSettings(object):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800116 def __init__(self, tls_cert_file, verify):
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800117 """Constructor.
118
119 Args:
120 tls_cert_file: TLS certificate in PEM format.
121 enable_tls_without_verify: enable TLS but don't verify certificate.
122 """
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800123 self._enabled = False
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800124 self._tls_cert_file = tls_cert_file
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800125 self._verify = verify
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800126 self._tls_context = None
127
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800128 def _UpdateContext(self):
129 if not self._enabled:
130 self._tls_context = None
131 return
132
133 self._tls_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
134 self._tls_context.verify_mode = ssl.CERT_REQUIRED
135
136 if self._verify:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800137 if self._tls_cert_file:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800138 self._tls_context.check_hostname = True
139 try:
140 self._tls_context.load_verify_locations(self._tls_cert_file)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800141 logging.info('TLSSettings: using user-supplied ca-certificate')
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800142 except IOError as e:
143 logging.error('TLSSettings: %s: %s', self._tls_cert_file, e)
144 sys.exit(1)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800145 else:
146 self._tls_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
147 logging.info('TLSSettings: using built-in ca-certificates')
148 else:
149 self._tls_context.verify_mode = ssl.CERT_NONE
150 logging.info('TLSSettings: skipping TLS verification!!!')
151
152 def SetEnabled(self, enabled):
153 logging.info('TLSSettings: enabled: %s', enabled)
154
155 if self._enabled != enabled:
156 self._enabled = enabled
157 self._UpdateContext()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800158
159 def Enabled(self):
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800160 return self._enabled
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800161
162 def Context(self):
163 return self._tls_context
164
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800165
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800166class Ghost(object):
167 """Ghost implements the client protocol of Overlord.
168
169 Ghost provide terminal/shell/logcat functionality and manages the client
170 side connectivity.
171 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800172 NONE, AGENT, TERMINAL, SHELL, LOGCAT, FILE, FORWARD = range(7)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800173
174 MODE_NAME = {
175 NONE: 'NONE',
176 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800177 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800178 SHELL: 'Shell',
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800179 LOGCAT: 'Logcat',
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800180 FILE: 'File',
181 FORWARD: 'Forward'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800182 }
183
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800184 RANDOM_MID = '##random_mid##'
185
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800186 def __init__(self, overlord_addrs, tls_settings=None, mode=AGENT, mid=None,
187 sid=None, prop_file=None, terminal_sid=None, tty_device=None,
Peter Shih220a96d2016-12-22 17:02:16 +0800188 command=None, file_op=None, port=None, tls_mode=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800189 """Constructor.
190
191 Args:
192 overlord_addrs: a list of possible address of overlord.
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800193 tls_settings: a TLSSetting object.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800194 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800195 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
196 id is randomly generated.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800197 sid: session ID. If the connection is requested by overlord, sid should
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800198 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800199 prop_file: properties file filename.
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800200 terminal_sid: the terminal session ID associate with this client. This is
201 use for file download.
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800202 tty_device: the terminal device to open, if tty_device is None, as pseudo
203 terminal will be opened instead.
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800204 command: the command to execute when we are in SHELL mode.
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800205 file_op: a tuple (action, filepath, perm). action is either 'download' or
206 'upload'. perm is the permission to set for the file.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800207 port: port number to forward.
Peter Shih220a96d2016-12-22 17:02:16 +0800208 tls_mode: can be [True, False, None]. if not None, skip detection of
209 TLS and assume whether server use TLS or not.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800210 """
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800211 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL, Ghost.FILE,
212 Ghost.FORWARD]
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800213 if mode == Ghost.SHELL:
214 assert command is not None
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800215 if mode == Ghost.FILE:
216 assert file_op is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800217
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800218 self._platform = platform.system()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800219 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800220 self._connected_addr = None
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800221 self._tls_settings = tls_settings
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800222 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800223 self._sock = None
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800224 self._mode = mode
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800225 self._machine_id = self.GetMachineID()
Wei-Ning Huangfed95862015-08-07 03:17:11 +0800226 self._session_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800227 self._terminal_session_id = terminal_sid
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800228 self._ttyname_to_sid = {}
229 self._terminal_sid_to_pid = {}
230 self._prop_file = prop_file
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800231 self._properties = {}
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800232 self._register_status = DISCONNECTED
233 self._reset = threading.Event()
Peter Shih220a96d2016-12-22 17:02:16 +0800234 self._tls_mode = tls_mode
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800235
236 # RPC
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800237 self._requests = {}
238 self._queue = Queue.Queue()
239
240 # Protocol specific
241 self._last_ping = 0
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800242 self._tty_device = tty_device
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800243 self._shell_command = command
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800244 self._file_op = file_op
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800245 self._download_queue = Queue.Queue()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800246 self._port = port
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800247
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800248 def SetIgnoreChild(self, status):
249 # Only ignore child for Agent since only it could spawn child Ghost.
250 if self._mode == Ghost.AGENT:
251 signal.signal(signal.SIGCHLD,
252 signal.SIG_IGN if status else signal.SIG_DFL)
253
254 def GetFileSha1(self, filename):
255 with open(filename, 'r') as f:
256 return hashlib.sha1(f.read()).hexdigest()
257
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800258 def TLSEnabled(self, host, port):
259 """Determine if TLS is enabled on given server address."""
Wei-Ning Huang58833882015-09-16 16:52:37 +0800260 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
261 try:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800262 # Allow any certificate since we only want to check if server talks TLS.
263 context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
264 context.verify_mode = ssl.CERT_NONE
Wei-Ning Huang58833882015-09-16 16:52:37 +0800265
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800266 sock = context.wrap_socket(sock, server_hostname=host)
267 sock.settimeout(_CONNECT_TIMEOUT)
268 sock.connect((host, port))
269 return True
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800270 except ssl.SSLError:
271 return False
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800272 except socket.error: # Connect refused or timeout
273 raise
Wei-Ning Huang58833882015-09-16 16:52:37 +0800274 except Exception:
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800275 return False # For whatever reason above failed, assume False
Wei-Ning Huang58833882015-09-16 16:52:37 +0800276
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800277 def Upgrade(self):
278 logging.info('Upgrade: initiating upgrade sequence...')
279
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800280 try:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800281 https_enabled = self.TLSEnabled(self._connected_addr[0],
282 _OVERLORD_HTTP_PORT)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800283 except socket.error:
Wei-Ning Huangb6605d22016-06-22 17:33:37 +0800284 logging.error('Upgrade: failed to connect to Overlord HTTP server, '
285 'abort')
286 return
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800287
288 if self._tls_settings.Enabled() and not https_enabled:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800289 logging.error('Upgrade: TLS enforced but found Overlord HTTP server '
290 'without TLS enabled! Possible mis-configuration or '
291 'DNS/IP spoofing detected, abort')
292 return
293
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800294 scriptpath = os.path.abspath(sys.argv[0])
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800295 url = 'http%s://%s:%d/upgrade/ghost.py' % (
Wei-Ning Huang47c79b82016-05-24 01:24:46 +0800296 's' if https_enabled else '', self._connected_addr[0],
Wei-Ning Huang03f9f762015-09-16 21:51:35 +0800297 _OVERLORD_HTTP_PORT)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800298
299 # Download sha1sum for ghost.py for verification
300 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800301 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800302 urllib2.urlopen(url + '.sha1', timeout=_CONNECT_TIMEOUT,
303 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800304 if f.getcode() != 200:
305 raise RuntimeError('HTTP status %d' % f.getcode())
306 sha1sum = f.read().strip()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800307 except (ssl.SSLError, ssl.CertificateError) as e:
308 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
309 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800310 except Exception:
311 logging.error('Upgrade: failed to download sha1sum file, abort')
312 return
313
314 if self.GetFileSha1(scriptpath) == sha1sum:
315 logging.info('Upgrade: ghost is already up-to-date, skipping upgrade')
316 return
317
318 # Download upgrade version of ghost.py
319 try:
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800320 with contextlib.closing(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800321 urllib2.urlopen(url, timeout=_CONNECT_TIMEOUT,
322 context=self._tls_settings.Context())) as f:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800323 if f.getcode() != 200:
324 raise RuntimeError('HTTP status %d' % f.getcode())
325 data = f.read()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800326 except (ssl.SSLError, ssl.CertificateError) as e:
327 logging.error('Upgrade: %s: %s', e.__class__.__name__, e)
328 return
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800329 except Exception:
330 logging.error('Upgrade: failed to download upgrade, abort')
331 return
332
333 # Compare SHA1 sum
334 if hashlib.sha1(data).hexdigest() != sha1sum:
335 logging.error('Upgrade: sha1sum mismatch, abort')
336 return
337
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800338 try:
339 with open(scriptpath, 'w') as f:
340 f.write(data)
341 except Exception:
342 logging.error('Upgrade: failed to write upgrade onto disk, abort')
343 return
344
345 logging.info('Upgrade: restarting ghost...')
346 self.CloseSockets()
347 self.SetIgnoreChild(False)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800348 os.execve(scriptpath, [scriptpath] + sys.argv[1:], os.environ)
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800349
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800350 def LoadProperties(self):
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800351 try:
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800352 if self._prop_file:
353 with open(self._prop_file, 'r') as f:
354 self._properties = json.loads(f.read())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800355 except Exception as e:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800356 logging.error('LoadProperties: ' + str(e))
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800357
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800358 def CloseSockets(self):
359 # Close sockets opened by parent process, since we don't use it anymore.
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800360 if self._platform == 'Linux':
361 for fd in os.listdir('/proc/self/fd/'):
362 try:
363 real_fd = os.readlink('/proc/self/fd/%s' % fd)
364 if real_fd.startswith('socket'):
365 os.close(int(fd))
366 except Exception:
367 pass
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800368
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800369 def SpawnGhost(self, mode, sid=None, terminal_sid=None, tty_device=None,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800370 command=None, file_op=None, port=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800371 """Spawn a child ghost with specific mode.
372
373 Returns:
374 The spawned child process pid.
375 """
Joel Kitching22b89042015-08-06 18:23:29 +0800376 # Restore the default signal handler, so our child won't have problems.
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800377 self.SetIgnoreChild(False)
378
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800379 pid = os.fork()
380 if pid == 0:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800381 self.CloseSockets()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800382 g = Ghost([self._connected_addr], tls_settings=self._tls_settings,
383 mode=mode, mid=Ghost.RANDOM_MID, sid=sid,
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800384 terminal_sid=terminal_sid, tty_device=tty_device,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800385 command=command, file_op=file_op, port=port)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800386 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800387 sys.exit(0)
388 else:
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800389 self.SetIgnoreChild(True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800390 return pid
391
392 def Timestamp(self):
393 return int(time.time())
394
395 def GetGateWayIP(self):
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800396 if self._platform == 'Darwin':
397 output = subprocess.check_output(['route', '-n', 'get', 'default'])
398 ret = re.search('gateway: (.*)', output)
399 if ret:
400 return [ret.group(1)]
401 elif self._platform == 'Linux':
402 with open('/proc/net/route', 'r') as f:
403 lines = f.readlines()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800404
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800405 ips = []
406 for line in lines:
407 parts = line.split('\t')
408 if parts[2] == '00000000':
409 continue
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800410
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800411 try:
412 h = parts[2].decode('hex')
413 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
414 except TypeError:
415 pass
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800416
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800417 return ips
418 else:
419 logging.warning('GetGateWayIP: unsupported platform')
420 return []
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800421
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800422 def GetShopfloorIP(self):
423 try:
Peter Shihcb0e5512017-06-14 16:59:46 +0800424 import factory_common # pylint: disable=unused-variable
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800425 from cros.factory.test import shopfloor
426
427 url = shopfloor.get_server_url()
428 match = re.match(r'^https?://(.*):.*$', url)
429 if match:
430 return [match.group(1)]
431 except Exception:
432 pass
433 return []
434
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800435 def GetMachineID(self):
436 """Generates machine-dependent ID string for a machine.
437 There are many ways to generate a machine ID:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800438 Linux:
439 1. factory device_id
Peter Shih5f1f48c2017-06-26 14:12:00 +0800440 2. /sys/class/dmi/id/product_uuid (only available on intel machines)
441 3. MAC address
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800442 We follow the listed order to generate machine ID, and fallback to the
443 next alternative if the previous doesn't work.
444
445 Darwin:
446 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800447 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800448 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800449 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800450 elif self._mid:
451 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800452
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800453 # Darwin
454 if self._platform == 'Darwin':
455 output = subprocess.check_output(['ioreg', '-rd1', '-c',
456 'IOPlatformExpertDevice'])
457 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
458 if ret:
459 return ret.group(1)
460
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800461 # Try factory device id
462 try:
Peter Shihcb0e5512017-06-14 16:59:46 +0800463 import factory_common # pylint: disable=unused-variable
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800464 from cros.factory.test import event_log
465 with open(event_log.DEVICE_ID_PATH) as f:
466 return f.read().strip()
467 except Exception:
468 pass
469
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800470 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800471 try:
472 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
473 return f.read().strip()
474 except Exception:
475 pass
476
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800477 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800478 try:
479 macs = []
480 ifaces = sorted(os.listdir('/sys/class/net'))
481 for iface in ifaces:
482 if iface == 'lo':
483 continue
484
485 with open('/sys/class/net/%s/address' % iface, 'r') as f:
486 macs.append(f.read().strip())
487
488 return ';'.join(macs)
489 except Exception:
490 pass
491
Peter Shihcb0e5512017-06-14 16:59:46 +0800492 raise RuntimeError("can't generate machine ID")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800493
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800494 def GetProcessWorkingDirectory(self, pid):
495 if self._platform == 'Linux':
496 return os.readlink('/proc/%d/cwd' % pid)
497 elif self._platform == 'Darwin':
498 PROC_PIDVNODEPATHINFO = 9
499 proc_vnodepathinfo_size = 2352
500 vid_path_offset = 152
501
502 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
503 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
504 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
505 ctypes.byref(buf), proc_vnodepathinfo_size)
506 buf = buf.raw[vid_path_offset:]
507 n = buf.index('\0')
508 return buf[:n]
509 else:
510 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
511
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800512 def Reset(self):
513 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800514 if self._sock is not None:
515 self._sock.Close()
516 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800517 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800518 self._last_ping = 0
519 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800520 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800521 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800522
523 def SendMessage(self, msg):
524 """Serialize the message and send it through the socket."""
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800525 self._sock.Send(json.dumps(msg) + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800526
527 def SendRequest(self, name, args, handler=None,
528 timeout=_REQUEST_TIMEOUT_SECS):
529 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800530 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800531
532 rid = str(uuid.uuid4())
533 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800534 if timeout >= 0:
535 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800536 self.SendMessage(msg)
537
538 def SendResponse(self, omsg, status, params=None):
539 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
540 self.SendMessage(msg)
541
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800542 def HandleTTYControl(self, fd, control_str):
543 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800544 command = msg['command']
545 params = msg['params']
546 if command == 'resize':
547 # some error happened on websocket
548 if len(params) != 2:
549 return
550 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
551 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
552 else:
553 logging.warn('Invalid request command "%s"', command)
554
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800555 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800556 """Spawn a TTY server and forward I/O to the TCP socket."""
557 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800558
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800559 try:
560 if self._tty_device is None:
561 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800562
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800563 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800564 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800565 try:
566 server = GhostRPCServer()
567 server.RegisterTTY(self._session_id, ttyname)
568 server.RegisterSession(self._session_id, os.getpid())
569 except Exception:
570 # If ghost is launched without RPC server, the call will fail but we
571 # can ignore it.
572 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800573
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800574 # The directory that contains the current running ghost script
575 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800576
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800577 env = os.environ.copy()
578 env['USER'] = os.getenv('USER', 'root')
579 env['HOME'] = os.getenv('HOME', '/root')
580 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
581 os.chdir(env['HOME'])
582 os.execve(_SHELL, [_SHELL], env)
583 else:
584 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800585 tty.setraw(fd)
586 attr = termios.tcgetattr(fd)
587 attr[0] &= ~(termios.IXON | termios.IXOFF)
588 attr[2] |= termios.CLOCAL
589 attr[2] &= ~termios.CRTSCTS
590 attr[4] = termios.B115200
591 attr[5] = termios.B115200
592 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800593
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800594 nonlocals = {'control_state': None, 'control_str': ''}
595
596 def _ProcessBuffer(buf):
597 write_buffer = ''
598 while buf:
599 if nonlocals['control_state']:
600 if chr(_CONTROL_END) in buf:
601 index = buf.index(chr(_CONTROL_END))
602 nonlocals['control_str'] += buf[:index]
603 self.HandleTTYControl(fd, nonlocals['control_str'])
604 nonlocals['control_state'] = None
605 nonlocals['control_str'] = ''
606 buf = buf[index+1:]
607 else:
608 nonlocals['control_str'] += buf
609 buf = ''
610 else:
611 if chr(_CONTROL_START) in buf:
612 nonlocals['control_state'] = _CONTROL_START
613 index = buf.index(chr(_CONTROL_START))
614 write_buffer += buf[:index]
615 buf = buf[index+1:]
616 else:
617 write_buffer += buf
618 buf = ''
619
620 if write_buffer:
621 os.write(fd, write_buffer)
622
623 _ProcessBuffer(self._sock.RecvBuf())
624
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800625 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800626 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800627
628 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800629 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800630
631 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800632 buf = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800633 if not buf:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800634 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800635 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800636 except Exception as e:
637 logging.error('SpawnTTYServer: %s', e)
638 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800639 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800640
641 logging.info('SpawnTTYServer: terminated')
642 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800643
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800644 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800645 """Spawn a shell server and forward input/output from/to the TCP socket."""
646 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800647
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800648 # Add ghost executable to PATH
649 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
650 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800651 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800652
653 # Execute shell command from HOME directory
654 os.chdir(os.getenv('HOME', '/tmp'))
655
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800656 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800657 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800658 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800659
660 def make_non_block(fd):
661 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
662 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
663
664 make_non_block(p.stdout)
665 make_non_block(p.stderr)
666
667 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800668 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800669
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800670 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800671 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800672 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800673 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800674 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800675
676 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800677 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800678
679 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800680 ret = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800681 if not ret:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800682 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800683
684 try:
685 idx = ret.index(_STDIN_CLOSED * 2)
686 p.stdin.write(ret[:idx])
687 p.stdin.close()
688 except ValueError:
689 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800690 p.poll()
691 if p.returncode != None:
692 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800693 except Exception as e:
694 logging.error('SpawnShellServer: %s', e)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800695 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800696 # Check if the process is terminated. If not, Send SIGTERM to process,
697 # then wait for 1 second. Send another SIGKILL to make sure the process is
698 # terminated.
699 p.poll()
700 if p.returncode is None:
701 try:
702 p.terminate()
703 time.sleep(1)
704 p.kill()
705 except Exception:
706 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800707
708 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800709 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800710
711 logging.info('SpawnShellServer: terminated')
712 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800713
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800714 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800715 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800716 try:
717 size = os.stat(self._file_op[1]).st_size
718 except OSError as e:
719 logging.error('InitiateFileOperation: download: %s', e)
720 sys.exit(1)
721
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800722 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800723 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800724 'filename': os.path.basename(self._file_op[1]),
725 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800726 elif self._file_op[0] == 'upload':
727 self.SendRequest('clear_to_upload', {}, timeout=-1)
728 self.StartUploadServer()
729 else:
730 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800731
732 def StartDownloadServer(self):
733 logging.info('StartDownloadServer: started')
734
735 try:
736 with open(self._file_op[1], 'rb') as f:
737 while True:
738 data = f.read(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800739 if not data:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800740 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800741 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800742 except Exception as e:
743 logging.error('StartDownloadServer: %s', e)
744 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800745 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800746
747 logging.info('StartDownloadServer: terminated')
748 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800749
Wei-Ning Huange2981862015-08-03 15:03:08 +0800750 def StartUploadServer(self):
751 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800752 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800753 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800754 dirname = os.path.dirname(filepath)
755 if not os.path.exists(dirname):
756 try:
757 os.makedirs(dirname)
758 except Exception:
759 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800760
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800761 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800762 if self._file_op[2]:
763 os.fchmod(f.fileno(), self._file_op[2])
764
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800765 f.write(self._sock.RecvBuf())
766
Wei-Ning Huange2981862015-08-03 15:03:08 +0800767 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800768 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800769 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800770 buf = self._sock.Recv(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800771 if not buf:
Wei-Ning Huange2981862015-08-03 15:03:08 +0800772 break
773 f.write(buf)
774 except socket.error as e:
775 logging.error('StartUploadServer: socket error: %s', e)
776 except Exception as e:
777 logging.error('StartUploadServer: %s', e)
778 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800779 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800780
781 logging.info('StartUploadServer: terminated')
782 sys.exit(0)
783
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800784 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800785 """Spawn a port forwarding server and forward I/O to the TCP socket."""
786 logging.info('SpawnPortForwardServer: started')
787
788 src_sock = None
789 try:
790 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800791 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800792 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800793
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800794 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800795
796 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800797 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800798
799 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800800 data = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800801 if not data:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800802 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800803 src_sock.send(data)
804
805 if src_sock in rd:
806 data = src_sock.recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800807 if not data:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800808 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800809 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800810 except Exception as e:
811 logging.error('SpawnPortForwardServer: %s', e)
812 finally:
813 if src_sock:
814 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800815 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800816
817 logging.info('SpawnPortForwardServer: terminated')
818 sys.exit(0)
819
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800820 def Ping(self):
821 def timeout_handler(x):
822 if x is None:
823 raise PingTimeoutError
824
825 self._last_ping = self.Timestamp()
826 self.SendRequest('ping', {}, timeout_handler, 5)
827
Wei-Ning Huangae923642015-09-24 14:08:09 +0800828 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800829 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800830 filepath = params['filename']
831 if not os.path.isabs(filepath):
832 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
833
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800834 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800835 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800836 pass
837 except Exception as e:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800838 return self.SendResponse(msg, str(e))
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800839
840 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800841 file_op=('download', filepath))
842 self.SendResponse(msg, SUCCESS)
843
844 def HandleFileUploadRequest(self, msg):
845 params = msg['params']
846
847 # Resolve upload filepath
848 filename = params['filename']
849 dest_path = filename
850
851 # If dest is specified, use it first
852 dest_path = params.get('dest', '')
853 if dest_path:
854 if not os.path.isabs(dest_path):
855 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
856
857 if os.path.isdir(dest_path):
858 dest_path = os.path.join(dest_path, filename)
859 else:
860 target_dir = os.getenv('HOME', '/tmp')
861
862 # Terminal session ID found, upload to it's current working directory
863 if params.has_key('terminal_sid'):
864 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
865 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800866 try:
867 target_dir = self.GetProcessWorkingDirectory(pid)
868 except Exception as e:
869 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800870
871 dest_path = os.path.join(target_dir, filename)
872
873 try:
874 os.makedirs(os.path.dirname(dest_path))
875 except Exception:
876 pass
877
878 try:
879 with open(dest_path, 'w') as _:
880 pass
881 except Exception as e:
882 return self.SendResponse(msg, str(e))
883
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800884 # If not check_only, spawn FILE mode ghost agent to handle upload
885 if not params.get('check_only', False):
886 self.SpawnGhost(self.FILE, params['sid'],
887 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800888 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800889
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800890 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800891 command = msg['name']
892 params = msg['params']
893
894 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800895 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800896 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800897 self.SpawnGhost(self.TERMINAL, params['sid'],
898 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800899 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800900 elif command == 'shell':
901 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800902 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800903 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800904 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800905 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800906 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800907 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800908 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800909 elif command == 'forward':
910 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
911 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800912
913 def HandleResponse(self, response):
914 rid = str(response['rid'])
915 if rid in self._requests:
916 handler = self._requests[rid][2]
917 del self._requests[rid]
918 if callable(handler):
919 handler(response)
920 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800921 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800922
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800923 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800924 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800925 try:
926 index = buf.index(_SEPARATOR)
927 except ValueError:
928 self._sock.UnRecv(buf)
929 return
930
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800931 msgs_json = [buf[:index]]
932 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800933 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800934 msgs_json = buf.split(_SEPARATOR)
935 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800936
937 for msg_json in msgs_json:
938 try:
939 msg = json.loads(msg_json)
940 except ValueError:
941 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800942 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800943 continue
944
945 if 'name' in msg:
946 self.HandleRequest(msg)
947 elif 'response' in msg:
948 self.HandleResponse(msg)
949 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800950 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800951
952 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800953 """Scans for pending requests which have timed out.
954
955 If any timed-out requests are discovered, their handler is called with the
956 special response value of None.
957 """
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800958 for rid in self._requests.keys()[:]:
959 request_time, timeout, handler = self._requests[rid]
960 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800961 if callable(handler):
962 handler(None)
963 else:
964 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800965 del self._requests[rid]
966
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800967 def InitiateDownload(self):
968 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800969 sid = self._ttyname_to_sid[ttyname]
970 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800971 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800972
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800973 def Listen(self):
974 try:
975 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800976 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
977 _PING_INTERVAL / 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800978
979 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800980 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800981
982 # Socket is closed
Peter Shihaacbc2f2017-06-16 14:39:29 +0800983 if not data:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800984 break
985
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800986 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800987
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800988 if (self._mode == self.AGENT and
989 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800990 self.Ping()
991 self.ScanForTimeoutRequests()
992
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800993 if not self._download_queue.empty():
994 self.InitiateDownload()
995
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800996 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800997 break
998 except socket.error:
999 raise RuntimeError('Connection dropped')
1000 except PingTimeoutError:
1001 raise RuntimeError('Connection timeout')
1002 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001003 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001004
1005 self._queue.put('resume')
1006
1007 if self._mode != Ghost.AGENT:
1008 sys.exit(1)
1009
1010 def Register(self):
1011 non_local = {}
1012 for addr in self._overlord_addrs:
1013 non_local['addr'] = addr
1014 def registered(response):
1015 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001016 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001017 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001018
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001019 self._register_status = response['response']
1020 if response['response'] != SUCCESS:
1021 self._reset.set()
Peter Shih220a96d2016-12-22 17:02:16 +08001022 raise RuntimeError('Register: ' + response['response'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001023 else:
1024 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1025 self._connected_addr = non_local['addr']
1026 self.Upgrade() # Check for upgrade
1027 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001028
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001029 try:
1030 logging.info('Trying %s:%d ...', *addr)
1031 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001032
Peter Shih220a96d2016-12-22 17:02:16 +08001033 # Check if server has TLS enabled. Only check if self._tls_mode is
1034 # None.
Wei-Ning Huangb6605d22016-06-22 17:33:37 +08001035 # Only control channel needs to determine if TLS is enabled. Other mode
1036 # should use the TLSSettings passed in when it was spawned.
1037 if self._mode == Ghost.AGENT:
Peter Shih220a96d2016-12-22 17:02:16 +08001038 self._tls_settings.SetEnabled(
1039 self.TLSEnabled(*addr) if self._tls_mode is None
1040 else self._tls_mode)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001041
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001042 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1043 sock.settimeout(_CONNECT_TIMEOUT)
1044
1045 try:
1046 if self._tls_settings.Enabled():
1047 tls_context = self._tls_settings.Context()
1048 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1049
1050 sock.connect(addr)
1051 except (ssl.SSLError, ssl.CertificateError) as e:
1052 logging.error('%s: %s', e.__class__.__name__, e)
1053 continue
1054 except IOError as e:
1055 if e.errno == 2: # No such file or directory
1056 logging.error('%s: %s', e.__class__.__name__, e)
1057 continue
1058 raise
1059
1060 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001061
1062 logging.info('Connection established, registering...')
1063 handler = {
1064 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001065 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001066 Ghost.SHELL: self.SpawnShellServer,
1067 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001068 Ghost.FORWARD: self.SpawnPortForwardServer,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001069 }[self._mode]
1070
1071 # Machine ID may change if MAC address is used (USB-ethernet dongle
1072 # plugged/unplugged)
1073 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001074 self.SendRequest('register',
1075 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001076 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001077 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001078 except socket.error:
1079 pass
1080 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001081 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001082 self.Listen()
1083
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001084 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001085
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001086 def Reconnect(self):
1087 logging.info('Received reconnect request from RPC server, reconnecting...')
1088 self._reset.set()
1089
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001090 def GetStatus(self):
1091 return self._register_status
1092
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001093 def AddToDownloadQueue(self, ttyname, filename):
1094 self._download_queue.put((ttyname, filename))
1095
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001096 def RegisterTTY(self, session_id, ttyname):
1097 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001098
1099 def RegisterSession(self, session_id, process_id):
1100 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001101
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001102 def StartLanDiscovery(self):
1103 """Start to listen to LAN discovery packet at
1104 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001105
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001106 def thread_func():
1107 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1108 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1109 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001110 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001111 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1112 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001113 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001114 return
1115
1116 logging.info('LAN Discovery: started')
1117 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001118 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001119
1120 if s in rd:
1121 data, source_addr = s.recvfrom(_BUFSIZE)
1122 parts = data.split()
1123 if parts[0] == 'OVERLORD':
1124 ip, port = parts[1].split(':')
1125 if not ip:
1126 ip = source_addr[0]
1127 self._queue.put((ip, int(port)), True)
1128
1129 try:
1130 obj = self._queue.get(False)
1131 except Queue.Empty:
1132 pass
1133 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001134 if not isinstance(obj, str):
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001135 self._queue.put(obj)
1136 elif obj == 'pause':
1137 logging.info('LAN Discovery: paused')
1138 while obj != 'resume':
1139 obj = self._queue.get(True)
1140 logging.info('LAN Discovery: resumed')
1141
1142 t = threading.Thread(target=thread_func)
1143 t.daemon = True
1144 t.start()
1145
1146 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001147 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001148 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1149 logRequests=False)
1150 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001151 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001152 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001153 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001154 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001155 t = threading.Thread(target=rpc_server.serve_forever)
1156 t.daemon = True
1157 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001158
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001159 def ScanServer(self):
1160 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
1161 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1162 if addr not in self._overlord_addrs:
1163 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001164
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001165 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001166 logging.info('%s started', self.MODE_NAME[self._mode])
1167 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001168 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001169
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001170 # We don't care about child process's return code, not wait is needed. This
1171 # is used to prevent zombie process from lingering in the system.
1172 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001173
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001174 if lan_disc:
1175 self.StartLanDiscovery()
1176
1177 if rpc_server:
1178 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001179
1180 try:
1181 while True:
1182 try:
1183 addr = self._queue.get(False)
1184 except Queue.Empty:
1185 pass
1186 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001187 if isinstance(addr, tuple) and addr not in self._overlord_addrs:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001188 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1189 self._overlord_addrs.append(addr)
1190
1191 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001192 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001193 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001194 # Don't show stack trace for RuntimeError, which we use in this file for
1195 # plausible and expected errors (such as can't connect to server).
1196 except RuntimeError as e:
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001197 logging.info('%s, retrying in %ds', e.message, _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001198 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001199 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001200 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001201 traceback.print_tb(exc_traceback)
1202 logging.info('%s: %s, retrying in %ds',
1203 e.__class__.__name__, e.message, _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001204 time.sleep(_RETRY_INTERVAL)
1205
1206 self.Reset()
1207 except KeyboardInterrupt:
1208 logging.error('Received keyboard interrupt, quit')
1209 sys.exit(0)
1210
1211
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001212def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001213 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001214 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1215
1216
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001217def ForkToBackground():
1218 """Fork process to run in background."""
1219 pid = os.fork()
1220 if pid != 0:
1221 logging.info('Ghost(%d) running in background.', pid)
1222 sys.exit(0)
1223
1224
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001225def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001226 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001227 filepath = os.path.abspath(filename)
1228 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001229 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001230 sys.exit(1)
1231
1232 # Check if we actually have permission to read the file
1233 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001234 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001235 sys.exit(1)
1236
1237 server = GhostRPCServer()
1238 server.AddToDownloadQueue(os.ttyname(0), filepath)
1239 sys.exit(0)
1240
1241
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001242def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001243 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001244 logger = logging.getLogger()
1245 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001246 handler = logging.StreamHandler()
1247 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1248 handler.setFormatter(formatter)
1249 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001250
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001251 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001252 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1253 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001254 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1255 default=None, help='use MID as machine ID')
1256 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1257 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001258 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1259 default=True, help='disable LAN discovery')
1260 parser.add_argument('--no-rpc-server', dest='rpc_server',
1261 action='store_false', default=True,
1262 help='disable RPC server')
Peter Shih220a96d2016-12-22 17:02:16 +08001263 parser.add_argument('--tls', dest='tls_mode', default='detect',
1264 choices=('y', 'n', 'detect'),
1265 help="specify 'y' or 'n' to force enable/disable TLS")
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001266 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1267 dest='tls_cert_file', type=str, default=None,
1268 help='file containing the server TLS certificate in PEM '
1269 'format')
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001270 parser.add_argument('--tls-no-verify', dest='tls_no_verify',
1271 action='store_true', default=False,
1272 help='do not verify certificate if TLS is enabled')
Joel Kitching22b89042015-08-06 18:23:29 +08001273 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001274 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001275 help='file containing the JSON representation of client '
1276 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001277 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1278 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001279 parser.add_argument('--reset', dest='reset', default=False,
1280 action='store_true',
1281 help='reset ghost and reload all configs')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001282 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1283 nargs='*', help='overlord server address')
1284 args = parser.parse_args()
1285
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001286 if args.fork:
1287 ForkToBackground()
1288
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001289 if args.reset:
1290 GhostRPCServer().Reconnect()
1291 sys.exit()
1292
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001293 if args.download:
1294 DownloadFile(args.download)
1295
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001296 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001297 addrs = [(x, _OVERLORD_PORT) for x in args.overlord_ip] + addrs
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001298
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001299 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1300
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001301 tls_settings = TLSSettings(args.tls_cert_file, not args.tls_no_verify)
Peter Shih220a96d2016-12-22 17:02:16 +08001302 tls_mode = args.tls_mode
1303 tls_mode = {'y': True, 'n': False, 'detect': None}[tls_mode]
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001304 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
Peter Shih220a96d2016-12-22 17:02:16 +08001305 prop_file=prop_file, tls_mode=tls_mode)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001306 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001307
1308
1309if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001310 try:
1311 main()
1312 except Exception as e:
1313 logging.error(e)