3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
9 from __future__ import print_function
25 # if there is no cStringIO, fall back to the original
27 from cStringIO import StringIO
29 from StringIO import StringIO
33 class BootServerRequest:
37 # all possible places to check the cdrom mount point.
38 # /mnt/cdrom is typically after the machine has come up,
39 # and /usr is when the boot cd is running
40 CDROM_MOUNT_PATH = ("/mnt/cdrom/", "/usr/")
42 MONITORSERVER_CERTS = {}
49 # in seconds, how maximum time allowed for connect
50 DEFAULT_CURL_CONNECT_TIMEOUT = 30
51 # in seconds, maximum time allowed for any transfer
52 DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
53 # location of curl executable, if pycurl isn't available
54 # and the DownloadFile method is called (backup, only
55 # really need for the boot cd environment where pycurl
60 def __init__(self, vars, verbose=0):
62 self.VERBOSE = verbose
65 # see if we have a boot cd mounted by checking for the version file
66 # if HAS_BOOTCD == 0 then either the machine doesn't have
67 # a boot cd, or something else is mounted
70 for path in self.CDROM_MOUNT_PATH:
71 self.Message("Checking existance of boot cd on {}".format(path))
73 os.system("/bin/mount {} > /dev/null 2>&1".format(path))
75 version_file = self.VARS['BOOTCD_VERSION_FILE'].format(path=path)
76 self.Message("Looking for version file {}".format(version_file))
78 if os.access(version_file, os.R_OK) == 0:
79 self.Message("No boot cd found.");
81 self.Message("Found boot cd.")
87 # check the version of the boot cd, and locate the certs
88 self.Message("Getting boot cd version.")
90 versionRegExp = re.compile(r"PlanetLab BootCD v(\S+)")
92 bootcd_version_f = file(version_file, "r")
93 line = string.strip(bootcd_version_f.readline())
94 bootcd_version_f.close()
96 match = versionRegExp.findall(line)
98 (self.BOOTCD_VERSION) = match[0]
100 # right now, all the versions of the bootcd are supported,
101 # so no need to check it
103 self.Message("Getting server from configuration")
105 bootservers = [ self.VARS['BOOT_SERVER'] ]
106 for bootserver in bootservers:
107 bootserver = string.strip(bootserver)
108 cacert_path = "{}/{}/{}".format(
109 self.VARS['SERVER_CERT_DIR'].format(path=path),
111 self.VARS['CACERT_NAME'])
112 if os.access(cacert_path, os.R_OK):
113 self.BOOTSERVER_CERTS[bootserver] = cacert_path
115 monitorservers = [ self.VARS['MONITOR_SERVER'] ]
116 for monitorserver in monitorservers:
117 monitorserver = string.strip(monitorserver)
118 cacert_path = "{}/{}/{}".format(
119 self.VARS['SERVER_CERT_DIR'].format(path=path),
121 self.VARS['CACERT_NAME'])
122 if os.access(cacert_path, os.R_OK):
123 self.MONITORSERVER_CERTS[monitorserver] = cacert_path
125 self.Message("Set of servers to contact: {}".format(self.BOOTSERVER_CERTS))
126 self.Message("Set of servers to upload to: {}".format(self.MONITORSERVER_CERTS))
128 self.Message("Using default boot server address.")
129 self.BOOTSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
130 self.MONITORSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
133 def CheckProxy(self):
134 # see if we have any proxy info from the machine
136 self.Message("Checking existance of proxy config file...")
138 if os.access(self.VARS['PROXY_FILE'], os.R_OK) and \
139 os.path.isfile(self.VARS['PROXY_FILE']):
140 self.PROXY = string.strip(file(self.VARS['PROXY_FILE'], 'r').readline())
142 self.Message("Using proxy {}.".format(self.PROXY))
144 self.Message("Not using any proxy.")
148 def Message(self, Msg):
152 def Error(self, Msg):
153 sys.stderr.write(Msg + "\n")
155 def Warning(self, Msg):
158 def MakeRequest(self, PartialPath, GetVars,
159 PostVars, DoSSL, DoCertCheck,
160 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
161 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
164 fd, buffer_name = tempfile.mkstemp("MakeRequest-XXXXXX")
166 buffer = open(buffer_name, "w+b")
168 # the file "buffer_name" will be deleted by DownloadFile()
170 ok = self.DownloadFile(PartialPath, GetVars, PostVars,
171 DoSSL, DoCertCheck, buffer_name,
176 # check the ok code, return the string only if it was successfull
185 # just in case it is not deleted by DownloadFile()
186 os.unlink(buffer_name)
192 def DownloadFile(self, PartialPath, GetVars, PostVars,
193 DoSSL, DoCertCheck, DestFilePath,
194 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
195 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
198 self.Message("Attempting to retrieve {}".format(PartialPath))
200 # we can't do ssl and check the cert if we don't have a bootcd
201 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
202 self.Error("No boot cd exists (needed to use -c and -s.\n")
205 if DoSSL and not PYCURL_LOADED:
206 self.Warning("Using SSL without pycurl will by default " \
207 "check at least standard certs.")
209 # ConnectTimeout has to be greater than 0
210 if ConnectTimeout <= 0:
211 self.Error("Connect timeout must be greater than zero.\n")
219 # setup the post and get vars for the request
222 postdata = urllib.urlencode(PostVars)
223 self.Message("Posting data:\n{}\n".format(postdata))
227 getstr = "?" + urllib.urlencode(GetVars)
228 self.Message("Get data:\n{}\n".format(getstr))
230 # now, attempt to make the request, starting at the first
233 cert_list = self.MONITORSERVER_CERTS
235 cert_list = self.BOOTSERVER_CERTS
237 for server in cert_list:
238 self.Message("Contacting server {}.".format(server))
240 certpath = cert_list[server]
243 # output what we are going to be doing
244 self.Message("Connect timeout is {} seconds".format(ConnectTimeout))
245 self.Message("Max transfer time is {} seconds".format(MaxTransferTime))
248 url = "https://{}/{}{}".format(server, PartialPath, getstr)
250 if DoCertCheck and PYCURL_LOADED:
251 self.Message("Using SSL version {} and verifying peer."
252 .format(self.CURL_SSL_VERSION))
254 self.Message("Using SSL version {}."
255 .format(self.CURL_SSL_VERSION))
257 url = "http://{}/{}{}".format(server, PartialPath, getstr)
259 self.Message("URL: {}".format(url))
261 # setup a new pycurl instance, or a curl command line string
262 # if we don't have pycurl
267 # don't want curl sending any signals
268 curl.setopt(pycurl.NOSIGNAL, 1)
270 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
271 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
273 # do not follow location when attempting to download a file
274 curl.setopt(pycurl.FOLLOWLOCATION, 0)
277 curl.setopt(pycurl.PROXY, self.PROXY)
280 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
283 curl.setopt(pycurl.CAINFO, certpath)
284 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
287 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
290 curl.setopt(pycurl.POSTFIELDS, postdata)
292 # setup multipart/form-data upload
294 curl.setopt(pycurl.HTTPPOST, FormData)
296 curl.setopt(pycurl.URL, url)
300 "--connect-timeout {} " \
302 "--header Pragma: " \
305 .format(self.CURL_CMD, ConnectTimeout,
306 MaxTransferTime, DestFilePath)
309 cmdline = cmdline + "--data '" + postdata + "' "
312 cmdline = cmdline + "".join(["--form '" + field + "' " for field in FormData])
315 cmdline = cmdline + "--silent "
318 cmdline = cmdline + "--proxy {} ".format(self.PROXY)
321 cmdline = cmdline + "--sslv{} ".format(self.CURL_SSL_VERSION)
323 cmdline = cmdline + "--cacert {} ".format(certpath)
325 cmdline = cmdline + url
327 self.Message("curl command: {}".format(cmdline))
332 # setup the output file
333 outfile = open(DestFilePath,"wb")
335 self.Message("Opened output file {}".format(DestFilePath))
337 curl.setopt(pycurl.WRITEDATA, outfile)
339 self.Message("Fetching...")
341 self.Message("Done.")
343 http_result = curl.getinfo(pycurl.HTTP_CODE)
347 self.Message("Results saved in {}".format(DestFilePath))
349 # check the code, return 1 if successfull
350 if http_result == self.HTTP_SUCCESS:
351 self.Message("Successfull!")
354 self.Message("Failure, resultant http code: {}"
355 .format(http_result))
357 except pycurl.error as err:
359 self.Error("connect to {} failed; curl error {}: '{}'\n"
360 .format(server, errno, errstr))
362 if not outfile.closed:
364 os.unlink(DestFilePath)
370 self.Message("Fetching...")
371 rc = os.system(cmdline)
372 self.Message("Done.")
376 os.unlink(DestFilePath)
379 self.Message("Failure, resultant curl code: {}".format(rc))
380 self.Message("Removed {}".format(DestFilePath))
382 self.Message("Successfull!")
385 self.Error("Unable to successfully contact any boot servers.\n")
394 Usage: BootServerRequest.py [options] <partialpath>
396 -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing.
397 -h/--help This help text
398 -o/--output <file> Write result to file
399 -s/--ssl Make the request over HTTPS
400 -v Makes the operation more talkative
405 if __name__ == "__main__":
408 # check out our command line options
410 opt_list, arg_list = getopt.getopt(sys.argv[1:],
412 [ "output=", "verbose", \
413 "help","ssl","checkcert"])
420 for opt, arg in opt_list:
421 if opt in ("-h","--help"):
425 if opt in ("-c","--checkcert"):
428 if opt in ("-s","--ssl"):
431 if opt in ("-o","--output"):
437 if len(arg_list) != 1:
440 partialpath = arg_list[0]
441 if string.lower(partialpath[:4]) == "http":
448 # got the command line args straightened out
449 requestor = BootServerRequest(verbose)
452 requestor.DownloadFile(partialpath, None, None, ssl,
453 checkcert, output_file)
455 result = requestor.MakeRequest(partialpath, None, None, ssl, checkcert)