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:
35 # all possible places to check the cdrom mount point.
36 # /mnt/cdrom is typically after the machine has come up,
37 # and /usr is when the boot cd is running
38 CDROM_MOUNT_PATH = ("/mnt/cdrom/", "/usr/")
40 MONITORSERVER_CERTS = {}
47 # in seconds, how maximum time allowed for connect
48 DEFAULT_CURL_CONNECT_TIMEOUT = 30
49 # in seconds, maximum time allowed for any transfer
50 DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
51 # location of curl executable, if pycurl isn't available
52 # and the DownloadFile method is called (backup, only
53 # really need for the boot cd environment where pycurl
57 # use TLSv1 and not SSLv3 anymore
59 CURL_SSL_VERSION = pycurl.SSLVERSION_TLSv1
61 # used to be '3' for SSLv3
62 # xxx really not sure what this means when pycurl is not loaded
65 def __init__(self, vars, verbose=0):
67 self.VERBOSE = verbose
70 # see if we have a boot cd mounted by checking for the version file
71 # if HAS_BOOTCD == 0 then either the machine doesn't have
72 # a boot cd, or something else is mounted
75 for path in self.CDROM_MOUNT_PATH:
76 self.Message("Checking existance of boot cd on {}".format(path))
78 os.system("/bin/mount {} > /dev/null 2>&1".format(path))
80 version_file = self.VARS['BOOTCD_VERSION_FILE'].format(path=path)
81 self.Message("Looking for version file {}".format(version_file))
83 if os.access(version_file, os.R_OK) == 0:
84 self.Message("No boot cd found.");
86 self.Message("Found boot cd.")
92 # check the version of the boot cd, and locate the certs
93 self.Message("Getting boot cd version.")
95 versionRegExp = re.compile(r"PlanetLab BootCD v(\S+)")
97 bootcd_version_f = file(version_file, "r")
98 line = string.strip(bootcd_version_f.readline())
99 bootcd_version_f.close()
101 match = versionRegExp.findall(line)
103 (self.BOOTCD_VERSION) = match[0]
105 # right now, all the versions of the bootcd are supported,
106 # so no need to check it
108 self.Message("Getting server from configuration")
110 bootservers = [ self.VARS['BOOT_SERVER'] ]
111 for bootserver in bootservers:
112 bootserver = string.strip(bootserver)
113 cacert_path = "{}/{}/{}".format(
114 self.VARS['SERVER_CERT_DIR'].format(path=path),
116 self.VARS['CACERT_NAME'])
117 if os.access(cacert_path, os.R_OK):
118 self.BOOTSERVER_CERTS[bootserver] = cacert_path
120 monitorservers = [ self.VARS['MONITOR_SERVER'] ]
121 for monitorserver in monitorservers:
122 monitorserver = string.strip(monitorserver)
123 cacert_path = "{}/{}/{}".format(
124 self.VARS['SERVER_CERT_DIR'].format(path=path),
126 self.VARS['CACERT_NAME'])
127 if os.access(cacert_path, os.R_OK):
128 self.MONITORSERVER_CERTS[monitorserver] = cacert_path
130 self.Message("Set of servers to contact: {}".format(self.BOOTSERVER_CERTS))
131 self.Message("Set of servers to upload to: {}".format(self.MONITORSERVER_CERTS))
133 self.Message("Using default boot server address.")
134 self.BOOTSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
135 self.MONITORSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']] = ""
138 def CheckProxy(self):
139 # see if we have any proxy info from the machine
141 self.Message("Checking existance of proxy config file...")
143 if os.access(self.VARS['PROXY_FILE'], os.R_OK) and \
144 os.path.isfile(self.VARS['PROXY_FILE']):
145 self.PROXY = string.strip(file(self.VARS['PROXY_FILE'], 'r').readline())
147 self.Message("Using proxy {}.".format(self.PROXY))
149 self.Message("Not using any proxy.")
153 def Message(self, Msg):
157 def Error(self, Msg):
158 sys.stderr.write(Msg + "\n")
160 def Warning(self, Msg):
163 def MakeRequest(self, PartialPath, GetVars,
164 PostVars, DoSSL, DoCertCheck,
165 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
166 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
169 fd, buffer_name = tempfile.mkstemp("MakeRequest-XXXXXX")
171 buffer = open(buffer_name, "w+b")
173 # the file "buffer_name" will be deleted by DownloadFile()
175 ok = self.DownloadFile(PartialPath, GetVars, PostVars,
176 DoSSL, DoCertCheck, buffer_name,
181 # check the ok code, return the string only if it was successfull
190 # just in case it is not deleted by DownloadFile()
191 os.unlink(buffer_name)
197 def DownloadFile(self, PartialPath, GetVars, PostVars,
198 DoSSL, DoCertCheck, DestFilePath,
199 ConnectTimeout = DEFAULT_CURL_CONNECT_TIMEOUT,
200 MaxTransferTime = DEFAULT_CURL_MAX_TRANSFER_TIME,
203 self.Message("Attempting to retrieve {}".format(PartialPath))
205 # we can't do ssl and check the cert if we don't have a bootcd
206 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
207 self.Error("No boot cd exists (needed to use -c and -s.\n")
210 if DoSSL and not PYCURL_LOADED:
211 self.Warning("Using SSL without pycurl will by default " \
212 "check at least standard certs.")
214 # ConnectTimeout has to be greater than 0
215 if ConnectTimeout <= 0:
216 self.Error("Connect timeout must be greater than zero.\n")
224 # setup the post and get vars for the request
227 postdata = urllib.urlencode(PostVars)
228 self.Message("Posting data:\n{}\n".format(postdata))
232 getstr = "?" + urllib.urlencode(GetVars)
233 self.Message("Get data:\n{}\n".format(getstr))
235 # now, attempt to make the request, starting at the first
238 cert_list = self.MONITORSERVER_CERTS
240 cert_list = self.BOOTSERVER_CERTS
242 for server in cert_list:
243 self.Message("Contacting server {}.".format(server))
245 certpath = cert_list[server]
248 # output what we are going to be doing
249 self.Message("Connect timeout is {} seconds".format(ConnectTimeout))
250 self.Message("Max transfer time is {} seconds".format(MaxTransferTime))
253 url = "https://{}/{}{}".format(server, PartialPath, getstr)
255 if DoCertCheck and PYCURL_LOADED:
256 self.Message("Using SSL version {} and verifying peer."
257 .format(self.CURL_SSL_VERSION))
259 self.Message("Using SSL version {}."
260 .format(self.CURL_SSL_VERSION))
262 url = "http://{}/{}{}".format(server, PartialPath, getstr)
264 self.Message("URL: {}".format(url))
266 # setup a new pycurl instance, or a curl command line string
267 # if we don't have pycurl
272 # don't want curl sending any signals
273 curl.setopt(pycurl.NOSIGNAL, 1)
275 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
276 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
278 # do not follow location when attempting to download a file
279 curl.setopt(pycurl.FOLLOWLOCATION, 0)
282 curl.setopt(pycurl.PROXY, self.PROXY)
285 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
288 curl.setopt(pycurl.CAINFO, certpath)
289 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
292 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
295 curl.setopt(pycurl.POSTFIELDS, postdata)
297 # setup multipart/form-data upload
299 curl.setopt(pycurl.HTTPPOST, FormData)
301 curl.setopt(pycurl.URL, url)
305 "--connect-timeout {} " \
307 "--header Pragma: " \
310 .format(self.CURL_CMD, ConnectTimeout,
311 MaxTransferTime, DestFilePath)
314 cmdline = cmdline + "--data '" + postdata + "' "
317 cmdline = cmdline + "".join(["--form '" + field + "' " for field in FormData])
320 cmdline = cmdline + "--silent "
323 cmdline = cmdline + "--proxy {} ".format(self.PROXY)
326 cmdline = cmdline + "--sslv{} ".format(self.CURL_SSL_VERSION)
328 cmdline = cmdline + "--cacert {} ".format(certpath)
330 cmdline = cmdline + url
332 self.Message("curl command: {}".format(cmdline))
337 # setup the output file
338 outfile = open(DestFilePath,"wb")
340 self.Message("Opened output file {}".format(DestFilePath))
342 curl.setopt(pycurl.WRITEDATA, outfile)
344 self.Message("Fetching...")
346 self.Message("Done.")
348 http_result = curl.getinfo(pycurl.HTTP_CODE)
352 self.Message("Results saved in {}".format(DestFilePath))
354 # check the code, return 1 if successfull
355 if http_result == self.HTTP_SUCCESS:
356 self.Message("Successfull!")
359 self.Message("Failure, resultant http code: {}"
360 .format(http_result))
362 except pycurl.error as err:
364 self.Error("connect to {} failed; curl error {}: '{}'\n"
365 .format(server, errno, errstr))
367 if not outfile.closed:
369 os.unlink(DestFilePath)
375 self.Message("Fetching...")
376 rc = os.system(cmdline)
377 self.Message("Done.")
381 os.unlink(DestFilePath)
384 self.Message("Failure, resultant curl code: {}".format(rc))
385 self.Message("Removed {}".format(DestFilePath))
387 self.Message("Successfull!")
390 self.Error("Unable to successfully contact any boot servers.\n")
399 Usage: BootServerRequest.py [options] <partialpath>
401 -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing.
402 -h/--help This help text
403 -o/--output <file> Write result to file
404 -s/--ssl Make the request over HTTPS
405 -v Makes the operation more talkative
410 if __name__ == "__main__":
413 # check out our command line options
415 opt_list, arg_list = getopt.getopt(sys.argv[1:],
417 [ "output=", "verbose", \
418 "help","ssl","checkcert"])
425 for opt, arg in opt_list:
426 if opt in ("-h","--help"):
430 if opt in ("-c","--checkcert"):
433 if opt in ("-s","--ssl"):
436 if opt in ("-o","--output"):
442 if len(arg_list) != 1:
445 partialpath = arg_list[0]
446 if string.lower(partialpath[:4]) == "http":
453 # got the command line args straightened out
454 requestor = BootServerRequest(verbose)
457 requestor.DownloadFile(partialpath, None, None, ssl,
458 checkcert, output_file)
460 result = requestor.MakeRequest(partialpath, None, None, ssl, checkcert)