blob: dedce7d7f7e7c363bf53475db6b398bd34ce2780 [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
440 2. factory device-data
441 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
442 4. MAC address
443 We follow the listed order to generate machine ID, and fallback to the
444 next alternative if the previous doesn't work.
445
446 Darwin:
447 All Darwin system should have the IOPlatformSerialNumber attribute.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800448 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800449 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800450 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800451 elif self._mid:
452 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800453
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800454 # Darwin
455 if self._platform == 'Darwin':
456 output = subprocess.check_output(['ioreg', '-rd1', '-c',
457 'IOPlatformExpertDevice'])
458 ret = re.search('"IOPlatformSerialNumber" = "(.*)"', output)
459 if ret:
460 return ret.group(1)
461
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800462 # Try factory device id
463 try:
Peter Shihcb0e5512017-06-14 16:59:46 +0800464 import factory_common # pylint: disable=unused-variable
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800465 from cros.factory.test import event_log
466 with open(event_log.DEVICE_ID_PATH) as f:
467 return f.read().strip()
468 except Exception:
469 pass
470
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800471 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800472 try:
Peter Shihcb0e5512017-06-14 16:59:46 +0800473 import factory_common # pylint: disable=unused-variable
474 from cros.factory.test import state
475 mlb_serial_number = state.GetSerialNumber(state.KEY_MLB_SERIAL_NUMBER)
476 if mlb_serial_number:
477 return mlb_serial_number
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800478 except Exception:
479 pass
480
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800481 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800482 try:
483 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
484 return f.read().strip()
485 except Exception:
486 pass
487
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800488 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800489 try:
490 macs = []
491 ifaces = sorted(os.listdir('/sys/class/net'))
492 for iface in ifaces:
493 if iface == 'lo':
494 continue
495
496 with open('/sys/class/net/%s/address' % iface, 'r') as f:
497 macs.append(f.read().strip())
498
499 return ';'.join(macs)
500 except Exception:
501 pass
502
Peter Shihcb0e5512017-06-14 16:59:46 +0800503 raise RuntimeError("can't generate machine ID")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800504
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800505 def GetProcessWorkingDirectory(self, pid):
506 if self._platform == 'Linux':
507 return os.readlink('/proc/%d/cwd' % pid)
508 elif self._platform == 'Darwin':
509 PROC_PIDVNODEPATHINFO = 9
510 proc_vnodepathinfo_size = 2352
511 vid_path_offset = 152
512
513 proc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libproc'))
514 buf = ctypes.create_string_buffer('\0' * proc_vnodepathinfo_size)
515 proc.proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0,
516 ctypes.byref(buf), proc_vnodepathinfo_size)
517 buf = buf.raw[vid_path_offset:]
518 n = buf.index('\0')
519 return buf[:n]
520 else:
521 raise RuntimeError('GetProcessWorkingDirectory: unsupported platform')
522
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800523 def Reset(self):
524 """Reset state and clear request handlers."""
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800525 if self._sock is not None:
526 self._sock.Close()
527 self._sock = None
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800528 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800529 self._last_ping = 0
530 self._requests = {}
Wei-Ning Huang23ed0162015-09-18 14:42:03 +0800531 self.LoadProperties()
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800532 self._register_status = DISCONNECTED
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800533
534 def SendMessage(self, msg):
535 """Serialize the message and send it through the socket."""
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800536 self._sock.Send(json.dumps(msg) + _SEPARATOR)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800537
538 def SendRequest(self, name, args, handler=None,
539 timeout=_REQUEST_TIMEOUT_SECS):
540 if handler and not callable(handler):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800541 raise RequestError('Invalid request handler for msg "%s"' % name)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800542
543 rid = str(uuid.uuid4())
544 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
Wei-Ning Huange2981862015-08-03 15:03:08 +0800545 if timeout >= 0:
546 self._requests[rid] = [self.Timestamp(), timeout, handler]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800547 self.SendMessage(msg)
548
549 def SendResponse(self, omsg, status, params=None):
550 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
551 self.SendMessage(msg)
552
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800553 def HandleTTYControl(self, fd, control_str):
554 msg = json.loads(control_str)
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800555 command = msg['command']
556 params = msg['params']
557 if command == 'resize':
558 # some error happened on websocket
559 if len(params) != 2:
560 return
561 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
562 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
563 else:
564 logging.warn('Invalid request command "%s"', command)
565
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800566 def SpawnTTYServer(self, unused_var):
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800567 """Spawn a TTY server and forward I/O to the TCP socket."""
568 logging.info('SpawnTTYServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800569
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800570 try:
571 if self._tty_device is None:
572 pid, fd = os.forkpty()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800573
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800574 if pid == 0:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800575 ttyname = os.ttyname(sys.stdout.fileno())
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800576 try:
577 server = GhostRPCServer()
578 server.RegisterTTY(self._session_id, ttyname)
579 server.RegisterSession(self._session_id, os.getpid())
580 except Exception:
581 # If ghost is launched without RPC server, the call will fail but we
582 # can ignore it.
583 pass
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800584
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800585 # The directory that contains the current running ghost script
586 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800587
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800588 env = os.environ.copy()
589 env['USER'] = os.getenv('USER', 'root')
590 env['HOME'] = os.getenv('HOME', '/root')
591 env['PATH'] = os.getenv('PATH') + ':%s' % script_dir
592 os.chdir(env['HOME'])
593 os.execve(_SHELL, [_SHELL], env)
594 else:
595 fd = os.open(self._tty_device, os.O_RDWR)
Wei-Ning Huang39169902015-09-19 06:00:23 +0800596 tty.setraw(fd)
597 attr = termios.tcgetattr(fd)
598 attr[0] &= ~(termios.IXON | termios.IXOFF)
599 attr[2] |= termios.CLOCAL
600 attr[2] &= ~termios.CRTSCTS
601 attr[4] = termios.B115200
602 attr[5] = termios.B115200
603 termios.tcsetattr(fd, termios.TCSANOW, attr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800604
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800605 nonlocals = {'control_state': None, 'control_str': ''}
606
607 def _ProcessBuffer(buf):
608 write_buffer = ''
609 while buf:
610 if nonlocals['control_state']:
611 if chr(_CONTROL_END) in buf:
612 index = buf.index(chr(_CONTROL_END))
613 nonlocals['control_str'] += buf[:index]
614 self.HandleTTYControl(fd, nonlocals['control_str'])
615 nonlocals['control_state'] = None
616 nonlocals['control_str'] = ''
617 buf = buf[index+1:]
618 else:
619 nonlocals['control_str'] += buf
620 buf = ''
621 else:
622 if chr(_CONTROL_START) in buf:
623 nonlocals['control_state'] = _CONTROL_START
624 index = buf.index(chr(_CONTROL_START))
625 write_buffer += buf[:index]
626 buf = buf[index+1:]
627 else:
628 write_buffer += buf
629 buf = ''
630
631 if write_buffer:
632 os.write(fd, write_buffer)
633
634 _ProcessBuffer(self._sock.RecvBuf())
635
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800636 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800637 rd, unused_wd, unused_xd = select.select([self._sock, fd], [], [])
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800638
639 if fd in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800640 self._sock.Send(os.read(fd, _BUFSIZE))
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800641
642 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800643 buf = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800644 if not buf:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800645 raise RuntimeError('connection terminated')
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800646 _ProcessBuffer(buf)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800647 except Exception as e:
648 logging.error('SpawnTTYServer: %s', e)
649 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800650 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800651
652 logging.info('SpawnTTYServer: terminated')
653 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800654
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800655 def SpawnShellServer(self, unused_var):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800656 """Spawn a shell server and forward input/output from/to the TCP socket."""
657 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800658
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800659 # Add ghost executable to PATH
660 script_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
661 env = os.environ.copy()
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800662 env['PATH'] = '%s:%s' % (script_dir, os.getenv('PATH'))
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800663
664 # Execute shell command from HOME directory
665 os.chdir(os.getenv('HOME', '/tmp'))
666
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800667 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800668 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800669 shell=True, env=env)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800670
671 def make_non_block(fd):
672 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
673 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
674
675 make_non_block(p.stdout)
676 make_non_block(p.stderr)
677
678 try:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800679 p.stdin.write(self._sock.RecvBuf())
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800680
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800681 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800682 rd, unused_wd, unused_xd = select.select(
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800683 [p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800684 if p.stdout in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800685 self._sock.Send(p.stdout.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800686
687 if p.stderr in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800688 self._sock.Send(p.stderr.read(_BUFSIZE))
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800689
690 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800691 ret = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800692 if not ret:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800693 raise RuntimeError('connection terminated')
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800694
695 try:
696 idx = ret.index(_STDIN_CLOSED * 2)
697 p.stdin.write(ret[:idx])
698 p.stdin.close()
699 except ValueError:
700 p.stdin.write(ret)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800701 p.poll()
702 if p.returncode != None:
703 break
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800704 except Exception as e:
705 logging.error('SpawnShellServer: %s', e)
Wei-Ning Huangf14c84e2015-08-03 15:03:08 +0800706 finally:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800707 # Check if the process is terminated. If not, Send SIGTERM to process,
708 # then wait for 1 second. Send another SIGKILL to make sure the process is
709 # terminated.
710 p.poll()
711 if p.returncode is None:
712 try:
713 p.terminate()
714 time.sleep(1)
715 p.kill()
716 except Exception:
717 pass
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800718
719 p.wait()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800720 self._sock.Close()
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800721
722 logging.info('SpawnShellServer: terminated')
723 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800724
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800725 def InitiateFileOperation(self, unused_var):
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800726 if self._file_op[0] == 'download':
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800727 try:
728 size = os.stat(self._file_op[1]).st_size
729 except OSError as e:
730 logging.error('InitiateFileOperation: download: %s', e)
731 sys.exit(1)
732
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800733 self.SendRequest('request_to_download',
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800734 {'terminal_sid': self._terminal_session_id,
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800735 'filename': os.path.basename(self._file_op[1]),
736 'size': size})
Wei-Ning Huange2981862015-08-03 15:03:08 +0800737 elif self._file_op[0] == 'upload':
738 self.SendRequest('clear_to_upload', {}, timeout=-1)
739 self.StartUploadServer()
740 else:
741 logging.error('InitiateFileOperation: unknown file operation, ignored')
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800742
743 def StartDownloadServer(self):
744 logging.info('StartDownloadServer: started')
745
746 try:
747 with open(self._file_op[1], 'rb') as f:
748 while True:
749 data = f.read(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800750 if not data:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800751 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800752 self._sock.Send(data)
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800753 except Exception as e:
754 logging.error('StartDownloadServer: %s', e)
755 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800756 self._sock.Close()
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800757
758 logging.info('StartDownloadServer: terminated')
759 sys.exit(0)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800760
Wei-Ning Huange2981862015-08-03 15:03:08 +0800761 def StartUploadServer(self):
762 logging.info('StartUploadServer: started')
Wei-Ning Huange2981862015-08-03 15:03:08 +0800763 try:
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800764 filepath = self._file_op[1]
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800765 dirname = os.path.dirname(filepath)
766 if not os.path.exists(dirname):
767 try:
768 os.makedirs(dirname)
769 except Exception:
770 pass
Wei-Ning Huange2981862015-08-03 15:03:08 +0800771
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800772 with open(filepath, 'wb') as f:
Wei-Ning Huang8ee3bcd2015-10-01 17:10:01 +0800773 if self._file_op[2]:
774 os.fchmod(f.fileno(), self._file_op[2])
775
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800776 f.write(self._sock.RecvBuf())
777
Wei-Ning Huange2981862015-08-03 15:03:08 +0800778 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800779 rd, unused_wd, unused_xd = select.select([self._sock], [], [])
Wei-Ning Huange2981862015-08-03 15:03:08 +0800780 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800781 buf = self._sock.Recv(_BLOCK_SIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800782 if not buf:
Wei-Ning Huange2981862015-08-03 15:03:08 +0800783 break
784 f.write(buf)
785 except socket.error as e:
786 logging.error('StartUploadServer: socket error: %s', e)
787 except Exception as e:
788 logging.error('StartUploadServer: %s', e)
789 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800790 self._sock.Close()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800791
792 logging.info('StartUploadServer: terminated')
793 sys.exit(0)
794
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800795 def SpawnPortForwardServer(self, unused_var):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800796 """Spawn a port forwarding server and forward I/O to the TCP socket."""
797 logging.info('SpawnPortForwardServer: started')
798
799 src_sock = None
800 try:
801 src_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Wei-Ning Huange0def6a2015-11-05 15:41:24 +0800802 src_sock.settimeout(_CONNECT_TIMEOUT)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800803 src_sock.connect(('localhost', self._port))
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800804
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800805 src_sock.send(self._sock.RecvBuf())
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800806
807 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800808 rd, unused_wd, unused_xd = select.select([self._sock, src_sock], [], [])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800809
810 if self._sock in rd:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800811 data = self._sock.Recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800812 if not data:
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +0800813 raise RuntimeError('connection terminated')
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800814 src_sock.send(data)
815
816 if src_sock in rd:
817 data = src_sock.recv(_BUFSIZE)
Peter Shihaacbc2f2017-06-16 14:39:29 +0800818 if not data:
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800819 break
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800820 self._sock.Send(data)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800821 except Exception as e:
822 logging.error('SpawnPortForwardServer: %s', e)
823 finally:
824 if src_sock:
825 src_sock.close()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800826 self._sock.Close()
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800827
828 logging.info('SpawnPortForwardServer: terminated')
829 sys.exit(0)
830
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800831 def Ping(self):
832 def timeout_handler(x):
833 if x is None:
834 raise PingTimeoutError
835
836 self._last_ping = self.Timestamp()
837 self.SendRequest('ping', {}, timeout_handler, 5)
838
Wei-Ning Huangae923642015-09-24 14:08:09 +0800839 def HandleFileDownloadRequest(self, msg):
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800840 params = msg['params']
Wei-Ning Huangae923642015-09-24 14:08:09 +0800841 filepath = params['filename']
842 if not os.path.isabs(filepath):
843 filepath = os.path.join(os.getenv('HOME', '/tmp'), filepath)
844
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800845 try:
Wei-Ning Huang11c35022015-10-21 16:52:32 +0800846 with open(filepath, 'r') as _:
Wei-Ning Huang46a3fc92015-10-06 02:35:27 +0800847 pass
848 except Exception as e:
Wei-Ning Huangae923642015-09-24 14:08:09 +0800849 return self.SendResponse(msg, str(e))
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800850
851 self.SpawnGhost(self.FILE, params['sid'],
Wei-Ning Huangae923642015-09-24 14:08:09 +0800852 file_op=('download', filepath))
853 self.SendResponse(msg, SUCCESS)
854
855 def HandleFileUploadRequest(self, msg):
856 params = msg['params']
857
858 # Resolve upload filepath
859 filename = params['filename']
860 dest_path = filename
861
862 # If dest is specified, use it first
863 dest_path = params.get('dest', '')
864 if dest_path:
865 if not os.path.isabs(dest_path):
866 dest_path = os.path.join(os.getenv('HOME', '/tmp'), dest_path)
867
868 if os.path.isdir(dest_path):
869 dest_path = os.path.join(dest_path, filename)
870 else:
871 target_dir = os.getenv('HOME', '/tmp')
872
873 # Terminal session ID found, upload to it's current working directory
874 if params.has_key('terminal_sid'):
875 pid = self._terminal_sid_to_pid.get(params['terminal_sid'], None)
876 if pid:
Wei-Ning Huanga0e55b82016-02-10 14:32:07 +0800877 try:
878 target_dir = self.GetProcessWorkingDirectory(pid)
879 except Exception as e:
880 logging.error(e)
Wei-Ning Huangae923642015-09-24 14:08:09 +0800881
882 dest_path = os.path.join(target_dir, filename)
883
884 try:
885 os.makedirs(os.path.dirname(dest_path))
886 except Exception:
887 pass
888
889 try:
890 with open(dest_path, 'w') as _:
891 pass
892 except Exception as e:
893 return self.SendResponse(msg, str(e))
894
Wei-Ning Huangd6f69762015-10-01 21:02:07 +0800895 # If not check_only, spawn FILE mode ghost agent to handle upload
896 if not params.get('check_only', False):
897 self.SpawnGhost(self.FILE, params['sid'],
898 file_op=('upload', dest_path, params.get('perm', None)))
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800899 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang552cd702015-08-12 16:11:13 +0800900
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800901 def HandleRequest(self, msg):
Wei-Ning Huange2981862015-08-03 15:03:08 +0800902 command = msg['name']
903 params = msg['params']
904
905 if command == 'upgrade':
Wei-Ning Huangb05cde32015-08-01 09:48:41 +0800906 self.Upgrade()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800907 elif command == 'terminal':
Wei-Ning Huangb8461202015-09-01 20:07:41 +0800908 self.SpawnGhost(self.TERMINAL, params['sid'],
909 tty_device=params['tty_device'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800910 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800911 elif command == 'shell':
912 self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +0800913 self.SendResponse(msg, SUCCESS)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800914 elif command == 'file_download':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800915 self.HandleFileDownloadRequest(msg)
Wei-Ning Huange2981862015-08-03 15:03:08 +0800916 elif command == 'clear_to_download':
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800917 self.StartDownloadServer()
Wei-Ning Huange2981862015-08-03 15:03:08 +0800918 elif command == 'file_upload':
Wei-Ning Huangae923642015-09-24 14:08:09 +0800919 self.HandleFileUploadRequest(msg)
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800920 elif command == 'forward':
921 self.SpawnGhost(self.FORWARD, params['sid'], port=params['port'])
922 self.SendResponse(msg, SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800923
924 def HandleResponse(self, response):
925 rid = str(response['rid'])
926 if rid in self._requests:
927 handler = self._requests[rid][2]
928 del self._requests[rid]
929 if callable(handler):
930 handler(response)
931 else:
Joel Kitching22b89042015-08-06 18:23:29 +0800932 logging.warning('Received unsolicited response, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800933
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800934 def ParseMessage(self, buf, single=True):
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800935 if single:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +0800936 try:
937 index = buf.index(_SEPARATOR)
938 except ValueError:
939 self._sock.UnRecv(buf)
940 return
941
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800942 msgs_json = [buf[:index]]
943 self._sock.UnRecv(buf[index + 2:])
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800944 else:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800945 msgs_json = buf.split(_SEPARATOR)
946 self._sock.UnRecv(msgs_json.pop())
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800947
948 for msg_json in msgs_json:
949 try:
950 msg = json.loads(msg_json)
951 except ValueError:
952 # Ignore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800953 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800954 continue
955
956 if 'name' in msg:
957 self.HandleRequest(msg)
958 elif 'response' in msg:
959 self.HandleResponse(msg)
960 else: # Ingnore mal-formed message.
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +0800961 logging.error('mal-formed JSON request, ignored')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800962
963 def ScanForTimeoutRequests(self):
Joel Kitching22b89042015-08-06 18:23:29 +0800964 """Scans for pending requests which have timed out.
965
966 If any timed-out requests are discovered, their handler is called with the
967 special response value of None.
968 """
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800969 for rid in self._requests.keys()[:]:
970 request_time, timeout, handler = self._requests[rid]
971 if self.Timestamp() - request_time > timeout:
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800972 if callable(handler):
973 handler(None)
974 else:
975 logging.error('Request %s timeout', rid)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800976 del self._requests[rid]
977
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800978 def InitiateDownload(self):
979 ttyname, filename = self._download_queue.get()
Wei-Ning Huangd521f282015-08-07 05:28:04 +0800980 sid = self._ttyname_to_sid[ttyname]
981 self.SpawnGhost(self.FILE, terminal_sid=sid,
Wei-Ning Huangae923642015-09-24 14:08:09 +0800982 file_op=('download', filename))
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800983
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800984 def Listen(self):
985 try:
986 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +0800987 rds, unused_wd, unused_xd = select.select([self._sock], [], [],
988 _PING_INTERVAL / 2)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800989
990 if self._sock in rds:
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800991 data = self._sock.Recv(_BUFSIZE)
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800992
993 # Socket is closed
Peter Shihaacbc2f2017-06-16 14:39:29 +0800994 if not data:
Wei-Ning Huang09c19612015-11-24 16:29:09 +0800995 break
996
Wei-Ning Huanga28cd232016-01-27 15:04:41 +0800997 self.ParseMessage(data, self._register_status != SUCCESS)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800998
Wei-Ning Huanga301f572015-06-03 17:34:21 +0800999 if (self._mode == self.AGENT and
1000 self.Timestamp() - self._last_ping > _PING_INTERVAL):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001001 self.Ping()
1002 self.ScanForTimeoutRequests()
1003
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001004 if not self._download_queue.empty():
1005 self.InitiateDownload()
1006
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001007 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001008 break
1009 except socket.error:
1010 raise RuntimeError('Connection dropped')
1011 except PingTimeoutError:
1012 raise RuntimeError('Connection timeout')
1013 finally:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001014 self.Reset()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001015
1016 self._queue.put('resume')
1017
1018 if self._mode != Ghost.AGENT:
1019 sys.exit(1)
1020
1021 def Register(self):
1022 non_local = {}
1023 for addr in self._overlord_addrs:
1024 non_local['addr'] = addr
1025 def registered(response):
1026 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001027 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001028 raise RuntimeError('Register request timeout')
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001029
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001030 self._register_status = response['response']
1031 if response['response'] != SUCCESS:
1032 self._reset.set()
Peter Shih220a96d2016-12-22 17:02:16 +08001033 raise RuntimeError('Register: ' + response['response'])
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001034 else:
1035 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
1036 self._connected_addr = non_local['addr']
1037 self.Upgrade() # Check for upgrade
1038 self._queue.put('pause', True)
Wei-Ning Huang63c16092015-09-18 16:20:27 +08001039
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001040 try:
1041 logging.info('Trying %s:%d ...', *addr)
1042 self.Reset()
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001043
Peter Shih220a96d2016-12-22 17:02:16 +08001044 # Check if server has TLS enabled. Only check if self._tls_mode is
1045 # None.
Wei-Ning Huangb6605d22016-06-22 17:33:37 +08001046 # Only control channel needs to determine if TLS is enabled. Other mode
1047 # should use the TLSSettings passed in when it was spawned.
1048 if self._mode == Ghost.AGENT:
Peter Shih220a96d2016-12-22 17:02:16 +08001049 self._tls_settings.SetEnabled(
1050 self.TLSEnabled(*addr) if self._tls_mode is None
1051 else self._tls_mode)
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001052
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001053 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1054 sock.settimeout(_CONNECT_TIMEOUT)
1055
1056 try:
1057 if self._tls_settings.Enabled():
1058 tls_context = self._tls_settings.Context()
1059 sock = tls_context.wrap_socket(sock, server_hostname=addr[0])
1060
1061 sock.connect(addr)
1062 except (ssl.SSLError, ssl.CertificateError) as e:
1063 logging.error('%s: %s', e.__class__.__name__, e)
1064 continue
1065 except IOError as e:
1066 if e.errno == 2: # No such file or directory
1067 logging.error('%s: %s', e.__class__.__name__, e)
1068 continue
1069 raise
1070
1071 self._sock = BufferedSocket(sock)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001072
1073 logging.info('Connection established, registering...')
1074 handler = {
1075 Ghost.AGENT: registered,
Wei-Ning Huangb8461202015-09-01 20:07:41 +08001076 Ghost.TERMINAL: self.SpawnTTYServer,
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001077 Ghost.SHELL: self.SpawnShellServer,
1078 Ghost.FILE: self.InitiateFileOperation,
Wei-Ning Huangdadbeb62015-09-20 00:38:27 +08001079 Ghost.FORWARD: self.SpawnPortForwardServer,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001080 }[self._mode]
1081
1082 # Machine ID may change if MAC address is used (USB-ethernet dongle
1083 # plugged/unplugged)
1084 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001085 self.SendRequest('register',
1086 {'mode': self._mode, 'mid': self._machine_id,
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001087 'sid': self._session_id,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001088 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001089 except socket.error:
1090 pass
1091 else:
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001092 sock.settimeout(None)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001093 self.Listen()
1094
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001095 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001096
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001097 def Reconnect(self):
1098 logging.info('Received reconnect request from RPC server, reconnecting...')
1099 self._reset.set()
1100
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001101 def GetStatus(self):
1102 return self._register_status
1103
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001104 def AddToDownloadQueue(self, ttyname, filename):
1105 self._download_queue.put((ttyname, filename))
1106
Wei-Ning Huangd521f282015-08-07 05:28:04 +08001107 def RegisterTTY(self, session_id, ttyname):
1108 self._ttyname_to_sid[ttyname] = session_id
Wei-Ning Huange2981862015-08-03 15:03:08 +08001109
1110 def RegisterSession(self, session_id, process_id):
1111 self._terminal_sid_to_pid[session_id] = process_id
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001112
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001113 def StartLanDiscovery(self):
1114 """Start to listen to LAN discovery packet at
1115 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001116
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001117 def thread_func():
1118 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
1119 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1120 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001121 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001122 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
1123 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +08001124 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001125 return
1126
1127 logging.info('LAN Discovery: started')
1128 while True:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001129 rd, unused_wd, unused_xd = select.select([s], [], [], 1)
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001130
1131 if s in rd:
1132 data, source_addr = s.recvfrom(_BUFSIZE)
1133 parts = data.split()
1134 if parts[0] == 'OVERLORD':
1135 ip, port = parts[1].split(':')
1136 if not ip:
1137 ip = source_addr[0]
1138 self._queue.put((ip, int(port)), True)
1139
1140 try:
1141 obj = self._queue.get(False)
1142 except Queue.Empty:
1143 pass
1144 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001145 if not isinstance(obj, str):
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001146 self._queue.put(obj)
1147 elif obj == 'pause':
1148 logging.info('LAN Discovery: paused')
1149 while obj != 'resume':
1150 obj = self._queue.get(True)
1151 logging.info('LAN Discovery: resumed')
1152
1153 t = threading.Thread(target=thread_func)
1154 t.daemon = True
1155 t.start()
1156
1157 def StartRPCServer(self):
Joel Kitching22b89042015-08-06 18:23:29 +08001158 logging.info('RPC Server: started')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001159 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
1160 logRequests=False)
1161 rpc_server.register_function(self.Reconnect, 'Reconnect')
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001162 rpc_server.register_function(self.GetStatus, 'GetStatus')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001163 rpc_server.register_function(self.RegisterTTY, 'RegisterTTY')
Wei-Ning Huange2981862015-08-03 15:03:08 +08001164 rpc_server.register_function(self.RegisterSession, 'RegisterSession')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001165 rpc_server.register_function(self.AddToDownloadQueue, 'AddToDownloadQueue')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001166 t = threading.Thread(target=rpc_server.serve_forever)
1167 t.daemon = True
1168 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001169
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001170 def ScanServer(self):
1171 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
1172 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
1173 if addr not in self._overlord_addrs:
1174 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001175
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001176 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001177 logging.info('%s started', self.MODE_NAME[self._mode])
1178 logging.info('MID: %s', self._machine_id)
Wei-Ning Huangfed95862015-08-07 03:17:11 +08001179 logging.info('SID: %s', self._session_id)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001180
Wei-Ning Huangb05cde32015-08-01 09:48:41 +08001181 # We don't care about child process's return code, not wait is needed. This
1182 # is used to prevent zombie process from lingering in the system.
1183 self.SetIgnoreChild(True)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001184
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001185 if lan_disc:
1186 self.StartLanDiscovery()
1187
1188 if rpc_server:
1189 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001190
1191 try:
1192 while True:
1193 try:
1194 addr = self._queue.get(False)
1195 except Queue.Empty:
1196 pass
1197 else:
Peter Shihaacbc2f2017-06-16 14:39:29 +08001198 if isinstance(addr, tuple) and addr not in self._overlord_addrs:
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001199 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
1200 self._overlord_addrs.append(addr)
1201
1202 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +08001203 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001204 self.Register()
Joel Kitching22b89042015-08-06 18:23:29 +08001205 # Don't show stack trace for RuntimeError, which we use in this file for
1206 # plausible and expected errors (such as can't connect to server).
1207 except RuntimeError as e:
Wei-Ning Huang7ec55342015-09-17 08:46:06 +08001208 logging.info('%s, retrying in %ds', e.message, _RETRY_INTERVAL)
Joel Kitching22b89042015-08-06 18:23:29 +08001209 time.sleep(_RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001210 except Exception as e:
Wei-Ning Huang9083b7c2016-01-26 16:44:11 +08001211 unused_x, unused_y, exc_traceback = sys.exc_info()
Joel Kitching22b89042015-08-06 18:23:29 +08001212 traceback.print_tb(exc_traceback)
1213 logging.info('%s: %s, retrying in %ds',
1214 e.__class__.__name__, e.message, _RETRY_INTERVAL)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001215 time.sleep(_RETRY_INTERVAL)
1216
1217 self.Reset()
1218 except KeyboardInterrupt:
1219 logging.error('Received keyboard interrupt, quit')
1220 sys.exit(0)
1221
1222
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001223def GhostRPCServer():
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001224 """Returns handler to Ghost's JSON RPC server."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001225 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
1226
1227
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001228def ForkToBackground():
1229 """Fork process to run in background."""
1230 pid = os.fork()
1231 if pid != 0:
1232 logging.info('Ghost(%d) running in background.', pid)
1233 sys.exit(0)
1234
1235
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001236def DownloadFile(filename):
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001237 """Initiate a client-initiated file download."""
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001238 filepath = os.path.abspath(filename)
1239 if not os.path.exists(filepath):
Joel Kitching22b89042015-08-06 18:23:29 +08001240 logging.error('file `%s\' does not exist', filename)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001241 sys.exit(1)
1242
1243 # Check if we actually have permission to read the file
1244 if not os.access(filepath, os.R_OK):
Joel Kitching22b89042015-08-06 18:23:29 +08001245 logging.error('can not open %s for reading', filepath)
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001246 sys.exit(1)
1247
1248 server = GhostRPCServer()
1249 server.AddToDownloadQueue(os.ttyname(0), filepath)
1250 sys.exit(0)
1251
1252
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001253def main():
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001254 # Setup logging format
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001255 logger = logging.getLogger()
1256 logger.setLevel(logging.INFO)
Wei-Ning Huang5f3fa8f2015-10-24 15:08:48 +08001257 handler = logging.StreamHandler()
1258 formatter = logging.Formatter('%(asctime)s %(message)s', '%Y/%m/%d %H:%M:%S')
1259 handler.setFormatter(formatter)
1260 logger.addHandler(handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001261
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001262 parser = argparse.ArgumentParser()
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001263 parser.add_argument('--fork', dest='fork', action='store_true', default=False,
1264 help='fork procecess to run in background')
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +08001265 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
1266 default=None, help='use MID as machine ID')
1267 parser.add_argument('--rand-mid', dest='mid', action='store_const',
1268 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +08001269 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
1270 default=True, help='disable LAN discovery')
1271 parser.add_argument('--no-rpc-server', dest='rpc_server',
1272 action='store_false', default=True,
1273 help='disable RPC server')
Peter Shih220a96d2016-12-22 17:02:16 +08001274 parser.add_argument('--tls', dest='tls_mode', default='detect',
1275 choices=('y', 'n', 'detect'),
1276 help="specify 'y' or 'n' to force enable/disable TLS")
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001277 parser.add_argument('--tls-cert-file', metavar='TLS_CERT_FILE',
1278 dest='tls_cert_file', type=str, default=None,
1279 help='file containing the server TLS certificate in PEM '
1280 'format')
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001281 parser.add_argument('--tls-no-verify', dest='tls_no_verify',
1282 action='store_true', default=False,
1283 help='do not verify certificate if TLS is enabled')
Joel Kitching22b89042015-08-06 18:23:29 +08001284 parser.add_argument('--prop-file', metavar='PROP_FILE', dest='prop_file',
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001285 type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001286 help='file containing the JSON representation of client '
1287 'properties')
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001288 parser.add_argument('--download', metavar='FILE', dest='download', type=str,
1289 default=None, help='file to download')
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001290 parser.add_argument('--reset', dest='reset', default=False,
1291 action='store_true',
1292 help='reset ghost and reload all configs')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08001293 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
1294 nargs='*', help='overlord server address')
1295 args = parser.parse_args()
1296
Wei-Ning Huang8037c182015-09-19 04:41:50 +08001297 if args.fork:
1298 ForkToBackground()
1299
Wei-Ning Huang23ed0162015-09-18 14:42:03 +08001300 if args.reset:
1301 GhostRPCServer().Reconnect()
1302 sys.exit()
1303
Wei-Ning Huanga301f572015-06-03 17:34:21 +08001304 if args.download:
1305 DownloadFile(args.download)
1306
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001307 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001308 addrs = [(x, _OVERLORD_PORT) for x in args.overlord_ip] + addrs
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001309
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001310 prop_file = os.path.abspath(args.prop_file) if args.prop_file else None
1311
Wei-Ning Huang47c79b82016-05-24 01:24:46 +08001312 tls_settings = TLSSettings(args.tls_cert_file, not args.tls_no_verify)
Peter Shih220a96d2016-12-22 17:02:16 +08001313 tls_mode = args.tls_mode
1314 tls_mode = {'y': True, 'n': False, 'detect': None}[tls_mode]
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001315 g = Ghost(addrs, tls_settings, Ghost.AGENT, args.mid,
Peter Shih220a96d2016-12-22 17:02:16 +08001316 prop_file=prop_file, tls_mode=tls_mode)
Wei-Ning Huang11c35022015-10-21 16:52:32 +08001317 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001318
1319
1320if __name__ == '__main__':
Wei-Ning Huangf5311a02016-02-04 15:23:46 +08001321 try:
1322 main()
1323 except Exception as e:
1324 logging.error(e)