3 # Copyright (c) 2003 Intel Corporation
6 # Copyright (c) 2004-2006 The Trustees of Princeton University
23 # if there is no cStringIO, fall back to the original
25 from cStringIO import StringIO
27 from StringIO import StringIO
31 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 # this is the server to contact if we don't have a bootcd
41 DEFAULT_BOOT_SERVER = "boot.planet-lab.org"
43 BOOTCD_VERSION_FILE = "bootme/ID"
44 BOOTCD_SERVER_FILE = "bootme/BOOTSERVER"
45 BOOTCD_SERVER_CERT_DIR = "bootme/cacert"
46 CACERT_NAME = "cacert.pem"
48 # location of file containing http/https proxy info, if needed
49 PROXY_FILE = '/etc/planetlab/http_proxy'
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 # in seconds, how maximum time allowed for connect
58 DEFAULT_CURL_CONNECT_TIMEOUT = 30
60 # in seconds, maximum time allowed for any transfer
61 DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
76 def __init__(self, verbose=0):
80 # see if we have a boot cd mounted by checking for the version file
81 # if HAS_BOOTCD == 0 then either the machine doesn't have
82 # a boot cd, or something else is mounted
85 for path in self.CDROM_MOUNT_PATH:
86 self.Message( "Checking existance of boot cd on %s" % path )
88 os.system("/bin/mount %s > /dev/null 2>&1" % path )
90 version_file= path + self.BOOTCD_VERSION_FILE
91 self.Message( "Looking for version file %s" % version_file )
93 if os.access(version_file, os.R_OK) == 0:
94 self.Message( "No boot cd found." );
96 self.Message( "Found boot cd." )
103 # check the version of the boot cd, and locate the certs
104 self.Message( "Getting boot cd version." )
106 versionRegExp= re.compile(r"PlanetLab BootCD v(\S+)")
108 bootcd_version_f= file(version_file,"r")
109 line= string.strip(bootcd_version_f.readline())
110 bootcd_version_f.close()
112 match= versionRegExp.findall(line)
114 (self.BOOTCD_VERSION)= match[0]
116 # right now, all the versions of the bootcd are supported,
117 # so no need to check it
119 # create a list of the servers we should
120 # attempt to contact, and the certs for each
121 server_list= path + self.BOOTCD_SERVER_FILE
122 self.Message( "Getting list of servers off of cd from %s." %
125 bootservers_f= file(server_list,"r")
126 bootservers= bootservers_f.readlines()
127 bootservers_f.close()
129 for bootserver in bootservers:
130 bootserver = string.strip(bootserver)
131 cacert_path= "%s/%s/%s/%s" % \
132 (path,self.BOOTCD_SERVER_CERT_DIR,
133 bootserver,self.CACERT_NAME)
134 if os.access(cacert_path, os.R_OK):
135 self.BOOTSERVER_CERTS[bootserver]= cacert_path
137 self.Message( "Set of servers to contact: %s" %
138 str(self.BOOTSERVER_CERTS) )
140 self.Message( "Using default boot server address." )
141 self.BOOTSERVER_CERTS[self.DEFAULT_BOOT_SERVER]= ""
144 def CheckProxy( self ):
145 # see if we have any proxy info from the machine
147 self.Message( "Checking existance of proxy config file..." )
149 if os.access(self.PROXY_FILE, os.R_OK) and \
150 os.path.isfile(self.PROXY_FILE):
151 self.PROXY= string.strip(file(self.PROXY_FILE,'r').readline())
153 self.Message( "Using proxy %s." % self.PROXY )
155 self.Message( "Not using any proxy." )
159 def Message( self, Msg ):
165 def Error( self, Msg ):
166 sys.stderr.write( Msg + "\n" )
170 def Warning( self, Msg ):
175 def MakeRequest( self, PartialPath, GetVars,
176 PostVars, DoSSL, DoCertCheck,
177 ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
178 MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
181 (fd, buffer_name) = tempfile.mkstemp("MakeRequest-XXXXXX")
183 buffer = open(buffer_name, "w+b")
185 # the file "buffer_name" will be deleted by DownloadFile()
187 ok = self.DownloadFile(PartialPath, GetVars, PostVars,
188 DoSSL, DoCertCheck, buffer_name,
193 # check the ok code, return the string only if it was successfull
202 # just in case it is not deleted by DownloadFile()
203 os.unlink(buffer_name)
209 def DownloadFile(self, PartialPath, GetVars, PostVars,
210 DoSSL, DoCertCheck, DestFilePath,
211 ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
212 MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
215 self.Message( "Attempting to retrieve %s" % PartialPath )
217 # we can't do ssl and check the cert if we don't have a bootcd
218 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
219 self.Error( "No boot cd exists (needed to use -c and -s.\n" )
222 if DoSSL and not PYCURL_LOADED:
223 self.Warning( "Using SSL without pycurl will by default " \
224 "check at least standard certs." )
226 # ConnectTimeout has to be greater than 0
227 if ConnectTimeout <= 0:
228 self.Error( "Connect timeout must be greater than zero.\n" )
236 # setup the post and get vars for the request
239 postdata = urllib.urlencode(PostVars)
240 self.Message( "Posting data:\n%s\n" % postdata )
244 getstr= "?" + urllib.urlencode(GetVars)
245 self.Message( "Get data:\n%s\n" % getstr )
247 # now, attempt to make the request, starting at the first
250 for server in self.BOOTSERVER_CERTS:
251 self.Message( "Contacting server %s." % server )
253 certpath = self.BOOTSERVER_CERTS[server]
256 # output what we are going to be doing
257 self.Message( "Connect timeout is %s seconds" % \
260 self.Message( "Max transfer time is %s seconds" % \
264 url = "https://%s/%s%s" % (server,PartialPath,getstr)
266 if DoCertCheck and PYCURL_LOADED:
267 self.Message( "Using SSL version %d and verifying peer." %
268 self.CURL_SSL_VERSION )
270 self.Message( "Using SSL version %d." %
271 self.CURL_SSL_VERSION )
273 url = "http://%s/%s%s" % (server,PartialPath,getstr)
275 self.Message( "URL: %s" % url )
277 # setup a new pycurl instance, or a curl command line string
278 # if we don't have pycurl
283 # don't want curl sending any signals
284 curl.setopt(pycurl.NOSIGNAL, 1)
286 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
287 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
289 # do not follow location when attempting to download a file
290 curl.setopt(pycurl.FOLLOWLOCATION, 0)
293 curl.setopt(pycurl.PROXY, self.PROXY )
296 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
299 curl.setopt(pycurl.CAINFO, certpath)
300 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
303 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
306 curl.setopt(pycurl.POSTFIELDS, postdata)
308 # setup multipart/form-data upload
310 curl.setopt(pycurl.HTTPPOST, FormData)
312 curl.setopt(pycurl.URL, url)
316 "--connect-timeout %d " \
318 "--header Pragma: " \
321 (self.CURL_CMD, ConnectTimeout,
322 MaxTransferTime, DestFilePath)
325 cmdline = cmdline + "--data '" + postdata + "' "
328 cmdline = cmdline + "".join(["--form '" + field + "' " for field in FormData])
331 cmdline = cmdline + "--silent "
334 cmdline = cmdline + "--proxy %s " % self.PROXY
337 cmdline = cmdline + "--sslv%d " % self.CURL_SSL_VERSION
340 cmdline = cmdline + "--cacert %s " % certpath
342 cmdline = cmdline + url
344 self.Message( "curl command: %s" % cmdline )
349 # setup the output file
350 outfile = open(DestFilePath,"wb")
352 self.Message( "Opened output file %s" % DestFilePath )
354 curl.setopt(pycurl.WRITEDATA, outfile)
356 self.Message( "Fetching..." )
358 self.Message( "Done." )
360 http_result= curl.getinfo(pycurl.HTTP_CODE)
364 self.Message( "Results saved in %s" % DestFilePath )
366 # check the code, return 1 if successfull
367 if http_result == self.HTTP_SUCCESS:
368 self.Message( "Successfull!" )
371 self.Message( "Failure, resultant http code: %d" % \
374 except pycurl.error, err:
376 self.Error( "connect to %s failed; curl error %d: '%s'\n" %
377 (server,errno,errstr) )
379 if not outfile.closed:
381 os.unlink(DestFilePath)
387 self.Message( "Fetching..." )
388 rc = os.system(cmdline)
389 self.Message( "Done." )
393 os.unlink( DestFilePath )
396 self.Message( "Failure, resultant curl code: %d" % rc )
397 self.Message( "Removed %s" % DestFilePath )
399 self.Message( "Successfull!" )
402 self.Error( "Unable to successfully contact any boot servers.\n" )
411 Usage: BootServerRequest.py [options] <partialpath>
413 -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing.
414 -h/--help This help text
415 -o/--output <file> Write result to file
416 -s/--ssl Make the request over HTTPS
417 -v Makes the operation more talkative
422 if __name__ == "__main__":
425 # check out our command line options
427 opt_list, arg_list = getopt.getopt(sys.argv[1:],
429 [ "output=", "verbose", \
430 "help","ssl","checkcert"])
437 for opt, arg in opt_list:
438 if opt in ("-h","--help"):
442 if opt in ("-c","--checkcert"):
445 if opt in ("-s","--ssl"):
448 if opt in ("-o","--output"):
454 if len(arg_list) != 1:
457 partialpath= arg_list[0]
458 if string.lower(partialpath[:4]) == "http":
465 # got the command line args straightened out
466 requestor= BootServerRequest(verbose)
469 requestor.DownloadFile( partialpath, None, None, ssl,
470 checkcert, output_file)
472 result= requestor.MakeRequest( partialpath, None, None, ssl, checkcert)