1    	#!@PYTHON@ -tt
2    	
3    	import sys
4    	import atexit
5    	import socket
6    	import struct
7    	import logging
8    	sys.path.append("@FENCEAGENTSLIBDIR@")
9    	from fencing import *
10   	from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT
11   	
12   	INT4 = 4
13   	
14   	def open_socket(options):
15   		try:
16   			if "--inet6-only" in options:
17   				protocol = socket.AF_INET6
18   			elif "--inet4-only" in options:
19   				protocol = socket.AF_INET
20   			else:
21   				protocol = 0
22   			(_, _, _, _, addr) = socket.getaddrinfo( \
23   					options["--ip"], options["--ipport"], protocol,
24   					0, socket.IPPROTO_TCP, socket.AI_PASSIVE
25   					)[0]
26   		except socket.gaierror:
27   			fail(EC_LOGIN_DENIED)
28   	
29   		if "--ssl-secure" in options or "--ssl-insecure" in options:
30   			import ssl
31   			sock = socket.socket()
(1) Event Sigma main event: Certificate validation has been disabled for the `ssl.SSLContext` instance. This allows for the acceptance of a rogue certificate and the potential for a manipulator-in-the-middle (MITM) attack.
(2) Event remediation: Modify the `ssl.SSLContext` instance to enable certificate validation: * Explicitly set `ssl.SSLContext.verify_mode` to `ssl.CERT_REQUIRED` or set `ssl.SSLContext.check_hostname` to `True` as enabling `check_hostname` will automatically change the default from `CERT_NONE` to `CERT_REQUIRED`. * Explicitly set `protocol` to `ssl.PROTOCOL_TLS_SERVER` or `ssl.PROTOCOL_TLS_CLIENT`.
32   			sslcx = ssl.create_default_context()
33   			if "--ssl-insecure" in options:
34   				sslcx.check_hostname = False
35   				sslcx.verify_mode = ssl.CERT_NONE
36   			conn = sslcx.wrap_socket(sock, server_hostname=options["--ip"])
37   		else:
38   			conn = socket.socket()
39   		conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
40   		conn.settimeout(float(options["--shell-timeout"]) or None)
41   		try:
42   			conn.connect(addr)
43   		except socket.error as e:
44   			logging.debug(e)
45   			fail(EC_LOGIN_DENIED)
46   	
47   		return conn
48   	
49   	def smapi_pack_string(string):
50   		return struct.pack("!i%ds" % (len(string)), len(string), string.encode("UTF-8"))
51   	
52   	def prepare_smapi_command(options, smapi_function, additional_args):
53   		packet_size = 3*INT4 + len(smapi_function) + len(options["--username"]) + len(options["--password"])
54   		for arg in additional_args:
55   			packet_size += INT4 + len(arg)
56   	
57   		command = struct.pack("!i", packet_size)
58   		command += smapi_pack_string(smapi_function)
59   		command += smapi_pack_string(options["--username"])
60   		command += smapi_pack_string(options["--password"])
61   		for arg in additional_args:
62   			command += smapi_pack_string(arg)
63   	
64   		return command
65   	
66   	def get_power_status(conn, options):
67   		del conn
68   	
69   		if options.get("--original-action", None) == "monitor":
70   			(return_code, reason_code, images_active) = \
71   				get_list_of_images(options, "Check_Authentication", None)
72   	
73   			logging.debug("Check_Authenticate (%d,%d)", return_code, reason_code)
74   			if return_code == 0:
75   				return {}
76   			else:
77   				fail(EC_LOGIN_DENIED)
78   	
79   		if options["--action"] == "list":
80   			# '*' = list all active images
81   			options["--plug"] = "*"
82   	
83   		(return_code, reason_code, images_active) = \
84   				get_list_of_images(options, "Image_Status_Query", options["--plug"])
85   		logging.debug("Image_Status_Query results are (%d,%d)", return_code, reason_code)
86   	
87   		if not options["--action"] == "list":
88   			if (return_code == 0) and (reason_code == 0):
89   				return "on"
90   			elif (return_code == 0) and (reason_code == 12):
91   				# We are running always with --missing-as-off because we can not check if image
92   				# is defined or not (look at rhbz#1188750)
93   				return "off"
94   			else:
95   				return "unknown"
96   		else:
97   			(return_code, reason_code, images_defined) = \
98   				get_list_of_images(options, "Image_Name_Query_DM", options["--username"])
99   			logging.debug("Image_Name_Query_DM results are (%d,%d)", return_code, reason_code)
100  	
101  			return dict([(i, ("", "on" if i in images_active else "off")) for i in images_defined])
102  	
103  	def set_power_status(conn, options):
104  		conn = open_socket(options)
105  	
106  		packet = None
107  		if options["--action"] == "on":
108  			packet = prepare_smapi_command(options, "Image_Activate", [options["--plug"]])
109  		elif options["--action"] == "off":
110  			packet = prepare_smapi_command(options, "Image_Deactivate", [options["--plug"], "IMMED"])
111  		conn.send(packet)
112  	
113  		request_id = struct.unpack("!i", conn.recv(INT4))[0]
114  		(output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
115  		logging.debug("Image_(De)Activate results are (%d,%d)", return_code, reason_code)
116  	
117  		conn.close()
118  		return
119  	
120  	def get_list_of_images(options, command, data_as_plug):
121  		conn = open_socket(options)
122  	
123  		if data_as_plug is None:
124  			packet = prepare_smapi_command(options, command, [])
125  		else:
126  			packet = prepare_smapi_command(options, command, [data_as_plug])
127  	
128  		conn.send(packet)
129  	
130  		try:
131  			request_id = struct.unpack("!i", conn.recv(INT4))[0]
132  			(output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4))
133  		except struct.error:
134  			logging.debug(sys.exc_info())
135  			fail_usage("Failed: Unable to connect to {} port: {} SSL: {} \n".format(options["--ip"], options["--ipport"], bool("--ssl" in options)))
136  	
137  		images = set()
138  	
139  		if output_len > 3*INT4:
140  			recvflag = socket.MSG_WAITALL if "--ssl-secure" not in options and "--ssl-insecure" not in options else 0
141  			array_len = struct.unpack("!i", conn.recv(INT4))[0]
142  			data = ""
143  	
144  			while True:
145  				read_data = conn.recv(1024, recvflag).decode("UTF-8")
146  				data += read_data
147  				if array_len == len(data):
148  					break
149  				elif not read_data:
150  					logging.error("Failed: Not enough data read from socket")
151  					fail(EC_TIMED_OUT)
152  	
153  			parsed_len = 0
154  			while parsed_len < array_len:
155  				string_len = struct.unpack("!i", data[parsed_len:parsed_len+INT4].encode("UTF-8"))[0]
156  				parsed_len += INT4
157  				image_name = struct.unpack("!%ds" % (string_len), data[parsed_len:parsed_len+string_len].encode("UTF-8"))[0].decode("UTF-8")
158  				parsed_len += string_len
159  				images.add(image_name)
160  	
161  		conn.close()
162  		return (return_code, reason_code, images)
163  	
164  	def define_new_opts():
165  		all_opt["disable_ssl"] = {
166  			"getopt" : "",
167  			"longopt" : "disable-ssl",
168  			"help" : "--disable-ssl                  Don't use SSL connection",
169  			"required" : "0",
170  			"shortdesc" : "Don't use SSL",
171  			"order": 2
172  		}
173  	
174  	def main():
175  		device_opt = ["ipaddr", "login", "passwd", "port", "method", "missing_as_off",
176  			      "inet4_only", "inet6_only", "ssl", "disable_ssl"]
177  	
178  		atexit.register(atexit_handler)
179  		define_new_opts()
180  	
181  		all_opt["ssl"]["help"] = "-z, --ssl                      Use SSL connection with verifying certificate (Default)"
182  	
183  		all_opt["ipport"]["default"] = "44444"
184  		all_opt["shell_timeout"]["default"] = "5"
185  		all_opt["missing_as_off"]["default"] = "1"
186  		all_opt["ssl"]["default"] = "1"
187  		options = check_input(device_opt, process_input(device_opt), other_conditions=True)
188  	
189  		if "--disable-ssl" in options or options["--ssl"] == "0":
190  			for k in ["--ssl", "--ssl-secure", "--ssl-insecure"]:
191  				if k in options:
192  					del options[k]
193  	
194  		if len(options.get("--plug", "")) > 8:
195  			fail_usage("Failed: Name of image can not be longer than 8 characters")
196  	
197  		if options["--action"] == "validate-all":
198  			sys.exit(0)
199  	
200  		docs = {}
201  		docs["shortdesc"] = "Fence agent for use with z/VM Virtual Machines"
202  		docs["longdesc"] = """fence_zvmip is a Power Fencing agent for z/VM \
203  	SMAPI service via TCP/IP.
204  	
205  	The z/VM SMAPI service must be configured so that the virtual machine running
206  	the agent can connect to the service, access the system's directory manager,
207  	and shortly thereafter run image_deactivate and image_activate. This involves
208  	updating the VSMWORK1 NAMELIST and VSMWORK1 AUTHLIST VMSYS:VSMWORK1 files.
209  	
210  	The NAMELIST entry assigns all the required functions to one nick and should
211  	look similar to this:
212  	
213  	:nick.ZVM_FENCE\n.br\n\
214  	:list.\n.br\n\
215  	IMAGE_ACTIVATE\n.br\n\
216  	IMAGE_DEACTIVATE\n.br\n\
217  	IMAGE_STATUS_QUERY\n.br\n\
218  	CHECK_AUTHENTICATION\n.br\n\
219  	IMAGE_NAME_QUERY_DM
220  	
221  	
222  	The AUTHLIST entry authorizes the user to perform all the functions associated
223  	with the nick, and should look similar to this:
224  	
225  	Column 1                   Column 66                Column 131
226  	
227  	|                          |                        |\n.br\n\
228  	V                          V                        V
229  	
230  	XXXXXXXX                   ALL                      ZVM_FENCE
231  	
232  	where XXXXXXXX is the name of the user in the authuser field of the request.
233  	
234  	Refer to the official z/VM documentation for complete instructions and
235  	reference materials.
236  	"""
237  		docs["vendorurl"] = "http://www.ibm.com"
238  		show_docs(options, docs)
239  	
240  		run_delay(options)
241  		result = fence_action(None, options, set_power_status, get_power_status, get_power_status)
242  		sys.exit(result)
243  	
244  	if __name__ == "__main__":
245  		main()
246