3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
9 from __future__ import print_function
19 # if there is no cStringIO, fall back to the original
21 from cStringIO import StringIO
23 from StringIO import StringIO
27 class BootServerRequest:
29 # all possible places to check the cdrom mount point.
30 # /mnt/cdrom is typically after the machine has come up,
31 # and /usr is when the boot cd is running
32 CDROM_MOUNT_PATH = ("/mnt/cdrom/", "/usr/")
34 MONITORSERVER_CERTS = {}
41 # in seconds, how maximum time allowed for connect
42 DEFAULT_CURL_CONNECT_TIMEOUT = 30
43 # in seconds, maximum time allowed for any transfer
44 DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
45 # location of curl executable, if pycurl isn't available
46 # and the DownloadFile method is called (backup, only
47 # really need for the boot cd environment where pycurl
51 # use TLSv1 and not SSLv3 anymore
52 CURL_SSL_VERSION = pycurl.SSLVERSION_TLSv1
54 def __init__(self, vars, verbose=0):
56 self.VERBOSE = verbose
59 # see if we have a boot cd mounted by checking for the version file
60 # if HAS_BOOTCD == 0 then either the machine doesn't have
61 # a boot cd, or something else is mounted
64 for path in self.CDROM_MOUNT_PATH:
65 self.Message("Checking existance of boot cd on {}".format(path))
67 os.system("/bin/mount {} > /dev/null 2>&1".format(path))
69 version_file = self.VARS['BOOTCD_VERSION_FILE'].format(path=path)
70 self.Message("Looking for version file {}".format(version_file))
72 if os.access(version_file, os.R_OK) == 0:
73 self.Message("No boot cd found.");
75 self.Message("Found boot cd.")
81 # check the version of the boot cd, and locate the certs
82 self.Message("Getting boot cd version.")
84 versionRegExp = re.compile(r"PlanetLab BootCD v(\S+)")
86 bootcd_version_f = file(version_file, "r")
87 line = string.strip(bootcd_version_f.readline())
88 bootcd_version_f.close()
90 match = versionRegExp.findall(line)
92 (self.BOOTCD_VERSION) = match[0]
94 # right now, all the versions of the bootcd are supported,
95 # so no need to check it
97 self.Message("Getting server from configuration")
99 bootservers = [ self.VARS['BOOT_SERVER'] ]
100 for bootserver in bootservers:
101 bootserver = string.strip(bootserver)
102 cacert_path = "{}/{}/{}".format(
103 self.VARS['SERVER_CERT_DIR'].format(path=path),
105 self.VARS['CACERT_NAME'])
106 if os.access(cacert_path, os.R_OK):
107 self.BOOTSERVER_CERTS[bootserver] = cacert_path
109 monitorservers = [ self.VARS['MONITOR_SERVER'] ]
110 for monitorserver in monitorservers:
111 monitorserver = string.strip(monitorserver)
112 cacert_path = "{}/{}/{}".format(
113 self.VARS['SERVER_CERT_DIR'].format(path=path),
115 self.VARS['CACERT_NAME'])
116 if os.access(cacert_path, os.R_OK):
117 self.MONITORSERVER_CERTS[monitorserver] = cacert_path
119 self.Message("Set of servers to contact: {}".format(self.BOOTSERVER_CERTS))
120 self.Message("Set of servers to upload to: {}".format(self.MONITORSERVER_CERTS))
122 self.Message("Using default boot server address.")
123 self.BOOTSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
124 self.MONITORSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
127 def CheckProxy(self):
128 # see if we have any proxy info from the machine
130 self.Message("Checking existance of proxy config file...")
132 if os.access(self.VARS['PROXY_FILE'], os.R_OK) and \
133 os.path.isfile(self.VARS['PROXY_FILE']):
134 self.PROXY = string.strip(file(self.VARS['PROXY_FILE'], 'r').readline())
136 self.Message("Using proxy {}.".format(self.PROXY))
138 self.Message("Not using any proxy.")
142 def Message(self, Msg):
146 def Error(self, Msg):
147 sys.stderr.write(Msg + "\n")
149 def Warning(self, Msg):
152 def MakeRequest(self, PartialPath, GetVars,
153 PostVars, DoSSL, DoCertCheck,
154 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
155 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
158 fd, buffer_name = tempfile.mkstemp("MakeRequest-XXXXXX")
160 buffer = open(buffer_name, "w+b")
162 # the file "buffer_name" will be deleted by DownloadFile()
164 ok = self.DownloadFile(PartialPath, GetVars, PostVars,
165 DoSSL, DoCertCheck, buffer_name,
170 # check the ok code, return the string only if it was successfull
179 # just in case it is not deleted by DownloadFile()
180 os.unlink(buffer_name)
186 def DownloadFile(self, PartialPath, GetVars, PostVars,
187 DoSSL, DoCertCheck, DestFilePath,
188 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
189 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
192 self.Message("Attempting to retrieve {}".format(PartialPath))
194 # we can't do ssl and check the cert if we don't have a bootcd
195 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
196 self.Error("No boot cd exists (needed to use -c and -s.\n")
199 # ConnectTimeout has to be greater than 0
200 if ConnectTimeout <= 0:
201 self.Error("Connect timeout must be greater than zero.\n")
209 # setup the post and get vars for the request
212 postdata = urllib.urlencode(PostVars)
213 self.Message("Posting data:\n{}\n".format(postdata))
217 getstr = "?" + urllib.urlencode(GetVars)
218 self.Message("Get data:\n{}\n".format(getstr))
220 # now, attempt to make the request, starting at the first
223 cert_list = self.MONITORSERVER_CERTS
225 cert_list = self.BOOTSERVER_CERTS
227 for server in cert_list:
228 self.Message("Contacting server {}.".format(server))
230 certpath = cert_list[server]
233 # output what we are going to be doing
234 self.Message("Connect timeout is {} seconds".format(ConnectTimeout))
235 self.Message("Max transfer time is {} seconds".format(MaxTransferTime))
238 url = "https://{}/{}{}".format(server, PartialPath, getstr)
241 self.Message("Using SSL version {} and verifying peer."
242 .format(self.CURL_SSL_VERSION))
244 self.Message("Using SSL version {}."
245 .format(self.CURL_SSL_VERSION))
247 url = "http://{}/{}{}".format(server, PartialPath, getstr)
249 self.Message("URL: {}".format(url))
251 # setup a new pycurl instance
254 # don't want curl sending any signals
255 curl.setopt(pycurl.NOSIGNAL, 1)
257 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
258 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
260 # do not follow location when attempting to download a file
261 curl.setopt(pycurl.FOLLOWLOCATION, 0)
264 curl.setopt(pycurl.PROXY, self.PROXY)
267 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
270 curl.setopt(pycurl.CAINFO, certpath)
271 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
273 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
276 curl.setopt(pycurl.POSTFIELDS, postdata)
278 # setup multipart/form-data upload
280 curl.setopt(pycurl.HTTPPOST, FormData)
282 curl.setopt(pycurl.URL, url)
285 # setup the output file
286 with open(DestFilePath,"wb") as outfile:
288 self.Message("Opened output file {}".format(DestFilePath))
290 curl.setopt(pycurl.WRITEDATA, outfile)
292 self.Message("Fetching...")
294 self.Message("Done.")
296 http_result = curl.getinfo(pycurl.HTTP_CODE)
299 self.Message("Results saved in {}".format(DestFilePath))
301 # check the code, return 1 if successfull
302 if http_result == self.HTTP_SUCCESS:
303 self.Message("Successfull!")
306 self.Message("Failure, resultant http code: {}"
307 .format(http_result))
309 except pycurl.error as err:
311 self.Error("connect to {} failed; curl error {}: '{}'\n"
312 .format(server, errno, errstr))
314 self.Error("Unable to successfully contact any boot servers.\n")
322 Usage: BootServerRequest.py [options] <partialpath>
324 -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing.
325 -h/--help This help text
326 -o/--output <file> Write result to file
327 -s/--ssl Make the request over HTTPS
328 -v Makes the operation more talkative
333 if __name__ == "__main__":
336 # check out our command line options
338 opt_list, arg_list = getopt.getopt(sys.argv[1:],
340 [ "output=", "verbose", \
341 "help","ssl","checkcert"])
348 for opt, arg in opt_list:
349 if opt in ("-h","--help"):
353 if opt in ("-c","--checkcert"):
356 if opt in ("-s","--ssl"):
359 if opt in ("-o","--output"):
365 if len(arg_list) != 1:
368 partialpath = arg_list[0]
369 if string.lower(partialpath[:4]) == "http":
376 # got the command line args straightened out
377 requestor = BootServerRequest(verbose)
380 requestor.DownloadFile(partialpath, None, None, ssl,
381 checkcert, output_file)
383 result = requestor.MakeRequest(partialpath, None, None, ssl, checkcert)