3 # Copyright (c) 2003 Intel Corporation
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions are
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
13 # * Redistributions in binary form must reproduce the above
14 # copyright notice, this list of conditions and the following
15 # disclaimer in the documentation and/or other materials provided
16 # with the distribution.
18 # * Neither the name of the Intel Corporation nor the names of its
19 # contributors may be used to endorse or promote products derived
20 # from this software without specific prior written permission.
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INTEL OR
26 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 # EXPORT LAWS: THIS LICENSE ADDS NO RESTRICTIONS TO THE EXPORT LAWS OF
35 # YOUR JURISDICTION. It is licensee's responsibility to comply with any
36 # export regulations applicable in licensee's jurisdiction. Under
37 # CURRENT (May 2000) U.S. export regulations this software is eligible
38 # for export from the U.S. and can be downloaded by or otherwise
39 # exported or reexported worldwide EXCEPT to U.S. embargoed destinations
40 # which include Cuba, Iraq, Libya, North Korea, Iran, Syria, Sudan,
41 # Afghanistan and any other country to which the U.S. has embargoed
58 # if there is no cStringIO, fall back to the original
60 from cStringIO import StringIO
62 from StringIO import StringIO
66 class BootServerRequest:
70 # all possible places to check the cdrom mount point.
71 # /mnt/cdrom is typically after the machine has come up,
72 # and /usr is when the boot cd is running
73 CDROM_MOUNT_PATH = ("/mnt/cdrom/","/usr/")
75 # this is the server to contact if we don't have a bootcd
76 DEFAULT_BOOT_SERVER = "boot.planet-lab.org"
78 BOOTCD_VERSION_FILE = "bootme/ID"
79 BOOTCD_SERVER_FILE = "bootme/BOOTSERVER"
80 BOOTCD_SERVER_CERT_DIR = "bootme/cacert"
81 CACERT_NAME = "cacert.pem"
83 # location of file containing http/https proxy info, if needed
84 PROXY_FILE = '/etc/planetlab/http_proxy'
86 # location of curl executable, if pycurl isn't available
87 # and the DownloadFile method is called (backup, only
88 # really need for the boot cd environment where pycurl
92 # in seconds, how maximum time allowed for connect
93 DEFAULT_CURL_CONNECT_TIMEOUT = 30
95 # in seconds, maximum time allowed for any transfer
96 DEFAULT_CURL_MAX_TRANSFER_TIME = 3600
111 def __init__(self, verbose=0):
113 self.VERBOSE= verbose
115 # see if we have a boot cd mounted by checking for the version file
116 # if HAS_BOOTCD == 0 then either the machine doesn't have
117 # a boot cd, or something else is mounted
120 for path in self.CDROM_MOUNT_PATH:
121 self.Message( "Checking existance of boot cd on %s" % path )
123 os.system("/bin/mount %s > /dev/null 2>&1" % path )
125 version_file= path + self.BOOTCD_VERSION_FILE
126 self.Message( "Looking for version file %s" % version_file )
128 if os.access(version_file, os.R_OK) == 0:
129 self.Message( "No boot cd found." );
131 self.Message( "Found boot cd." )
138 # check the version of the boot cd, and locate the certs
139 self.Message( "Getting boot cd version." )
141 versionRegExp= re.compile(r"PlanetLab BootCD v(\S+)")
143 bootcd_version_f= file(version_file,"r")
144 line= string.strip(bootcd_version_f.readline())
145 bootcd_version_f.close()
147 match= versionRegExp.findall(line)
149 (self.BOOTCD_VERSION)= match[0]
151 # right now, all the versions of the bootcd are supported,
152 # so no need to check it
154 # create a list of the servers we should
155 # attempt to contact, and the certs for each
156 server_list= path + self.BOOTCD_SERVER_FILE
157 self.Message( "Getting list of servers off of cd from %s." %
160 bootservers_f= file(server_list,"r")
161 bootservers= bootservers_f.readlines()
162 bootservers_f.close()
164 for bootserver in bootservers:
165 bootserver = string.strip(bootserver)
166 cacert_path= "%s/%s/%s/%s" % \
167 (path,self.BOOTCD_SERVER_CERT_DIR,
168 bootserver,self.CACERT_NAME)
169 if os.access(cacert_path, os.R_OK):
170 self.BOOTSERVER_CERTS[bootserver]= cacert_path
172 self.Message( "Set of servers to contact: %s" %
173 str(self.BOOTSERVER_CERTS) )
175 self.Message( "Using default boot server address." )
176 self.BOOTSERVER_CERTS[self.DEFAULT_BOOT_SERVER]= ""
179 def CheckProxy( self ):
180 # see if we have any proxy info from the machine
182 self.Message( "Checking existance of proxy config file..." )
184 if os.access(self.PROXY_FILE, os.R_OK) and \
185 os.path.isfile(self.PROXY_FILE):
186 self.PROXY= string.strip(file(self.PROXY_FILE,'r').readline())
188 self.Message( "Using proxy %s." % self.PROXY )
190 self.Message( "Not using any proxy." )
194 def Message( self, Msg ):
200 def Error( self, Msg ):
201 sys.stderr.write( Msg + "\n" )
205 def Warning( self, Msg ):
210 def MakeRequest( self, PartialPath, GetVars,
211 PostVars, DoSSL, DoCertCheck,
212 ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
213 MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
216 if PYCURL_LOADED == 0:
217 self.Error( "MakeRequest method requires pycurl." )
222 self.Message( "Attempting to retrieve %s" % PartialPath )
224 # we can't do ssl and check the cert if we don't have a bootcd
225 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
226 self.Error( "No boot cd exists (needed to use -c and -s.\n" )
229 # didn't pass a path in? just get the root doc
230 if PartialPath == "":
233 # ConnectTimeout has to be greater than 0
234 if ConnectTimeout <= 0:
235 self.Error( "Connect timeout must be greater than zero.\n" )
238 # setup the post and get vars for the request
241 postdata = urllib.urlencode(PostVars)
242 self.Message( "Posting data:\n%s\n" % postdata )
248 getstr= "?" + urllib.urlencode(GetVars)
249 self.Message( "Get data:\n%s\n" % getstr )
251 # now, attempt to make the request, starting at the first
253 for server in self.BOOTSERVER_CERTS:
254 self.Message( "Contacting server %s." % server )
256 certpath = self.BOOTSERVER_CERTS[server]
260 # don't want curl sending any signals
261 curl.setopt(pycurl.NOSIGNAL, 1)
263 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
264 self.Message( "Connect timeout is %s seconds" % \
267 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
268 self.Message( "Max transfer time is %s seconds" % \
271 curl.setopt(pycurl.FOLLOWLOCATION, 1)
272 curl.setopt(pycurl.MAXREDIRS, 2)
275 curl.setopt(pycurl.PROXY, self.PROXY )
278 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
280 url = "https://%s/%s%s" % (server,PartialPath,getstr)
282 curl.setopt(pycurl.CAINFO, certpath)
283 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
284 self.Message( "Using SSL version %d and verifying peer." % \
285 self.CURL_SSL_VERSION )
287 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
288 self.Message( "Using SSL version %d" % \
289 self.CURL_SSL_VERSION )
291 url = "http://%s/%s%s" % (server,PartialPath,getstr)
294 curl.setopt(pycurl.POSTFIELDS, postdata)
296 # setup multipart/form-data upload
298 curl.setopt(pycurl.HTTPPOST, FormData)
300 curl.setopt(pycurl.URL, url)
301 self.Message( "URL: %s" % url )
303 # setup the output buffer
305 curl.setopt(pycurl.WRITEFUNCTION, buffer.write)
308 self.Message( "Fetching..." )
310 self.Message( "Done." )
312 http_result= curl.getinfo(pycurl.HTTP_CODE)
315 # check the code, return the string only if it was successfull
316 if http_result == self.HTTP_SUCCESS:
317 self.Message( "Successfull!" )
318 return buffer.getvalue()
320 self.Message( "Failure, resultant http code: %d" % \
324 except pycurl.error, err:
326 self.Error( "connect to %s failed; curl error %d: '%s'\n" %
327 (server,errno,errstr) )
329 self.Error( "Unable to successfully contact any boot servers.\n" )
334 def DownloadFile(self, PartialPath, GetVars, PostVars,
335 DoSSL, DoCertCheck, DestFilePath,
336 ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
337 MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME):
339 self.Message( "Attempting to retrieve %s" % PartialPath )
341 # we can't do ssl and check the cert if we don't have a bootcd
342 if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
343 self.Error( "No boot cd exists (needed to use -c and -s.\n" )
346 if DoSSL and not PYCURL_LOADED:
347 self.Warning( "Using SSL without pycurl will by default " \
348 "check at least standard certs." )
350 # ConnectTimeout has to be greater than 0
351 if ConnectTimeout <= 0:
352 self.Error( "Connect timeout must be greater than zero.\n" )
360 # setup the post and get vars for the request
363 postdata = urllib.urlencode(PostVars)
364 self.Message( "Posting data:\n%s\n" % postdata )
368 getstr= "?" + urllib.urlencode(GetVars)
369 self.Message( "Get data:\n%s\n" % getstr )
371 # now, attempt to make the request, starting at the first
374 for server in self.BOOTSERVER_CERTS:
375 self.Message( "Contacting server %s." % server )
377 certpath = self.BOOTSERVER_CERTS[server]
380 # output what we are going to be doing
381 self.Message( "Connect timeout is %s seconds" % \
384 self.Message( "Max transfer time is %s seconds" % \
388 url = "https://%s/%s%s" % (server,PartialPath,getstr)
390 if DoCertCheck and PYCURL_LOADED:
391 self.Message( "Using SSL version %d and verifying peer." %
392 self.CURL_SSL_VERSION )
394 self.Message( "Using SSL version %d." %
395 self.CURL_SSL_VERSION )
397 url = "http://%s/%s%s" % (server,PartialPath,getstr)
399 self.Message( "URL: %s" % url )
401 # setup a new pycurl instance, or a curl command line string
402 # if we don't have pycurl
407 # don't want curl sending any signals
408 curl.setopt(pycurl.NOSIGNAL, 1)
410 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
411 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
413 # do not follow location when attempting to download a file
414 curl.setopt(pycurl.FOLLOWLOCATION, 0)
417 curl.setopt(pycurl.PROXY, self.PROXY )
420 curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
423 curl.setopt(pycurl.CAINFO, certpath)
424 curl.setopt(pycurl.SSL_VERIFYPEER, 2)
427 curl.setopt(pycurl.SSL_VERIFYPEER, 0)
430 curl.setopt(pycurl.POSTFIELDS, postdata)
432 curl.setopt(pycurl.URL, url)
436 "--connect-timeout %d " \
438 "--header Pragma: " \
441 (self.CURL_CMD, ConnectTimeout,
442 MaxTransferTime, DestFilePath)
445 cmdline = cmdline + "--data '" + postdata + "' "
448 cmdline = cmdline + "--silent "
451 cmdline = cmdline + "--proxy %s " % self.PROXY
454 cmdline = cmdline + "--sslv%d " % self.CURL_SSL_VERSION
457 cmdline = cmdline + "--cacert %s " % certpath
459 cmdline = cmdline + url
461 self.Message( "curl command: %s" % cmdline )
466 # setup the output file
467 outfile = open(DestFilePath,"wb")
469 self.Message( "Opened output file %s" % DestFilePath )
471 curl.setopt(pycurl.WRITEDATA, outfile)
473 self.Message( "Fetching..." )
475 self.Message( "Done." )
477 http_result= curl.getinfo(pycurl.HTTP_CODE)
481 self.Message( "Results saved in %s" % DestFilePath )
483 # check the code, return 1 if successfull
484 if http_result == self.HTTP_SUCCESS:
485 self.Message( "Successfull!" )
488 self.Message( "Failure, resultant http code: %d" % \
491 except pycurl.error, err:
493 self.Error( "connect to %s failed; curl error %d: '%s'\n" %
494 (server,errno,errstr) )
496 if not outfile.closed:
498 os.unlink(DestFilePath)
504 self.Message( "Fetching..." )
505 rc = os.system(cmdline)
506 self.Message( "Done." )
510 os.unlink( DestFilePath )
513 self.Message( "Failure, resultant curl code: %d" % rc )
514 self.Message( "Removed %s" % DestFilePath )
516 self.Message( "Successfull!" )
519 self.Error( "Unable to successfully contact any boot servers.\n" )
528 Usage: BootServerRequest.py [options] <partialpath>
530 -c/--checkcert Check SSL certs. Ignored if -s/--ssl missing.
531 -h/--help This help text
532 -o/--output <file> Write result to file
533 -s/--ssl Make the request over HTTPS
534 -v Makes the operation more talkative
539 if __name__ == "__main__":
542 # check out our command line options
544 opt_list, arg_list = getopt.getopt(sys.argv[1:],
546 [ "output=", "verbose", \
547 "help","ssl","checkcert"])
554 for opt, arg in opt_list:
555 if opt in ("-h","--help"):
559 if opt in ("-c","--checkcert"):
562 if opt in ("-s","--ssl"):
565 if opt in ("-o","--output"):
571 if len(arg_list) != 1:
574 partialpath= arg_list[0]
575 if string.lower(partialpath[:4]) == "http":
582 # got the command line args straightened out
583 requestor= BootServerRequest(verbose)
586 requestor.DownloadFile( partialpath, None, None, ssl,
587 checkcert, output_file)
589 result= requestor.MakeRequest( partialpath, None, None, ssl, checkcert)