svn:keywords
[bootmanager.git] / source / BootServerRequest.py
1 #!/usr/bin/python
2 #
3 # $Id$
4 # $URL$
5 #
6 # Copyright (c) 2003 Intel Corporation
7 # All rights reserved.
8 #
9 # Copyright (c) 2004-2006 The Trustees of Princeton University
10 # All rights reserved.
11
12 import os, sys
13 import re
14 import string
15 import urllib
16 import tempfile
17
18 # try to load pycurl
19 try:
20     import pycurl
21     PYCURL_LOADED= 1
22 except:
23     PYCURL_LOADED= 0
24
25
26 # if there is no cStringIO, fall back to the original
27 try:
28     from cStringIO import StringIO
29 except:
30     from StringIO import StringIO
31
32
33
34 class BootServerRequest:
35
36     VERBOSE = 0
37
38     # all possible places to check the cdrom mount point.
39     # /mnt/cdrom is typically after the machine has come up,
40     # and /usr is when the boot cd is running
41     CDROM_MOUNT_PATH = ("/mnt/cdrom/","/usr/")
42     BOOTSERVER_CERTS= {}
43     MONITORSERVER_CERTS= {}
44     BOOTCD_VERSION=""
45     HTTP_SUCCESS=200
46     HAS_BOOTCD=0
47     USE_PROXY=0
48     PROXY=0
49
50     # in seconds, how maximum time allowed for connect
51     DEFAULT_CURL_CONNECT_TIMEOUT=30
52     # in seconds, maximum time allowed for any transfer
53     DEFAULT_CURL_MAX_TRANSFER_TIME=3600
54     # location of curl executable, if pycurl isn't available
55     # and the DownloadFile method is called (backup, only
56     # really need for the boot cd environment where pycurl
57     # doesn't exist
58     CURL_CMD = 'curl'
59     CURL_SSL_VERSION=3
60
61     def __init__(self, vars, verbose=0):
62
63         self.VERBOSE= verbose
64         self.VARS=vars
65             
66         # see if we have a boot cd mounted by checking for the version file
67         # if HAS_BOOTCD == 0 then either the machine doesn't have
68         # a boot cd, or something else is mounted
69         self.HAS_BOOTCD = 0
70
71         for path in self.CDROM_MOUNT_PATH:
72             self.Message( "Checking existance of boot cd on %s" % path )
73
74             os.system("/bin/mount %s > /dev/null 2>&1" % path )
75                 
76             version_file= self.VARS['BOOTCD_VERSION_FILE'] % {'path' : path}
77             self.Message( "Looking for version file %s" % version_file )
78
79             if os.access(version_file, os.R_OK) == 0:
80                 self.Message( "No boot cd found." );
81             else:
82                 self.Message( "Found boot cd." )
83                 self.HAS_BOOTCD=1
84                 break
85
86         if self.HAS_BOOTCD:
87
88             # check the version of the boot cd, and locate the certs
89             self.Message( "Getting boot cd version." )
90         
91             versionRegExp= re.compile(r"PlanetLab BootCD v(\S+)")
92                 
93             bootcd_version_f= file(version_file,"r")
94             line= string.strip(bootcd_version_f.readline())
95             bootcd_version_f.close()
96             
97             match= versionRegExp.findall(line)
98             if match:
99                 (self.BOOTCD_VERSION)= match[0]
100             
101             # right now, all the versions of the bootcd are supported,
102             # so no need to check it
103             
104             self.Message( "Getting server from configuration" )
105             
106             bootservers= [ self.VARS['BOOT_SERVER'] ]
107             for bootserver in bootservers:
108                 bootserver = string.strip(bootserver)
109                 cacert_path= "%s/%s/%s" % \
110                              (self.VARS['SERVER_CERT_DIR'] % {'path' : path},
111                               bootserver,self.VARS['CACERT_NAME'])
112                 if os.access(cacert_path, os.R_OK):
113                     self.BOOTSERVER_CERTS[bootserver]= cacert_path
114
115             monitorservers= [ self.VARS['MONITOR_SERVER'] ]
116             for monitorserver in monitorservers:
117                 monitorserver = string.strip(monitorserver)
118                 cacert_path= "%s/%s/%s" % \
119                              (self.VARS['SERVER_CERT_DIR'] % {'path' : path},
120                               monitorserver,self.VARS['CACERT_NAME'])
121                 if os.access(cacert_path, os.R_OK):
122                     self.MONITORSERVER_CERTS[monitorserver]= cacert_path
123
124             self.Message( "Set of servers to contact: %s" %
125                           str(self.BOOTSERVER_CERTS) )
126             self.Message( "Set of servers to upload to: %s" %
127                           str(self.MONITORSERVER_CERTS) )
128         else:
129             self.Message( "Using default boot server address." )
130             self.BOOTSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']]= ""
131             self.MONITORSERVER_CERTS[self.VARS['DEFAULT_BOOT_SERVER']]= ""
132
133
134     def CheckProxy( self ):
135         # see if we have any proxy info from the machine
136         self.USE_PROXY= 0
137         self.Message( "Checking existance of proxy config file..." )
138         
139         if os.access(self.VARS['PROXY_FILE'], os.R_OK) and \
140                os.path.isfile(self.VARS['PROXY_FILE']):
141             self.PROXY= string.strip(file(self.VARS['PROXY_FILE'],'r').readline())
142             self.USE_PROXY= 1
143             self.Message( "Using proxy %s." % self.PROXY)
144         else:
145             self.Message( "Not using any proxy." )
146
147
148
149     def Message( self, Msg ):
150         if( self.VERBOSE ):
151             print( Msg )
152
153
154
155     def Error( self, Msg ):
156         sys.stderr.write( Msg + "\n" )
157
158
159
160     def Warning( self, Msg ):
161         self.Error(Msg)
162
163
164
165     def MakeRequest( self, PartialPath, GetVars,
166                      PostVars, DoSSL, DoCertCheck,
167                      ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
168                      MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
169                      FormData= None):
170
171         (fd, buffer_name) = tempfile.mkstemp("MakeRequest-XXXXXX")
172         os.close(fd)
173         buffer = open(buffer_name, "w+b")
174
175         # the file "buffer_name" will be deleted by DownloadFile()
176
177         ok = self.DownloadFile(PartialPath, GetVars, PostVars,
178                                DoSSL, DoCertCheck, buffer_name,
179                                ConnectTimeout,
180                                MaxTransferTime,
181                                FormData)
182
183         # check the ok code, return the string only if it was successfull
184         if ok:
185             buffer.seek(0)
186             ret = buffer.read()
187         else:
188             ret = None
189
190         buffer.close()
191         try:
192             # just in case it is not deleted by DownloadFile()
193             os.unlink(buffer_name)
194         except OSError:
195             pass
196             
197         return ret
198
199     def DownloadFile(self, PartialPath, GetVars, PostVars,
200                      DoSSL, DoCertCheck, DestFilePath,
201                      ConnectTimeout= DEFAULT_CURL_CONNECT_TIMEOUT,
202                      MaxTransferTime= DEFAULT_CURL_MAX_TRANSFER_TIME,
203                      FormData= None):
204
205         self.Message( "Attempting to retrieve %s" % PartialPath )
206
207         # we can't do ssl and check the cert if we don't have a bootcd
208         if DoSSL and DoCertCheck and not self.HAS_BOOTCD:
209             self.Error( "No boot cd exists (needed to use -c and -s.\n" )
210             return 0
211
212         if DoSSL and not PYCURL_LOADED:
213             self.Warning( "Using SSL without pycurl will by default " \
214                           "check at least standard certs." )
215
216         # ConnectTimeout has to be greater than 0
217         if ConnectTimeout <= 0:
218             self.Error( "Connect timeout must be greater than zero.\n" )
219             return 0
220
221
222         self.CheckProxy()
223
224         dopostdata= 0
225
226         # setup the post and get vars for the request
227         if PostVars:
228             dopostdata= 1
229             postdata = urllib.urlencode(PostVars)
230             self.Message( "Posting data:\n%s\n" % postdata )
231             
232         getstr= ""
233         if GetVars:
234             getstr= "?" + urllib.urlencode(GetVars)
235             self.Message( "Get data:\n%s\n" % getstr )
236
237         # now, attempt to make the request, starting at the first
238         # server in the list
239         if FormData:
240             cert_list = self.MONITORSERVER_CERTS
241         else:
242             cert_list = self.BOOTSERVER_CERTS
243         
244         for server in cert_list:
245             self.Message( "Contacting server %s." % server )
246                         
247             certpath = cert_list[server]
248
249             
250             # output what we are going to be doing
251             self.Message( "Connect timeout is %s seconds" % \
252                           ConnectTimeout )
253
254             self.Message( "Max transfer time is %s seconds" % \
255                           MaxTransferTime )
256
257             if DoSSL:
258                 url = "https://%s/%s%s" % (server,PartialPath,getstr)
259                 
260                 if DoCertCheck and PYCURL_LOADED:
261                     self.Message( "Using SSL version %d and verifying peer." %
262                              self.CURL_SSL_VERSION )
263                 else:
264                     self.Message( "Using SSL version %d." %
265                              self.CURL_SSL_VERSION )
266             else:
267                 url = "http://%s/%s%s" % (server,PartialPath,getstr)
268                 
269             self.Message( "URL: %s" % url )
270             
271             # setup a new pycurl instance, or a curl command line string
272             # if we don't have pycurl
273             
274             if PYCURL_LOADED:
275                 curl= pycurl.Curl()
276
277                 # don't want curl sending any signals
278                 curl.setopt(pycurl.NOSIGNAL, 1)
279             
280                 curl.setopt(pycurl.CONNECTTIMEOUT, ConnectTimeout)
281                 curl.setopt(pycurl.TIMEOUT, MaxTransferTime)
282
283                 # do not follow location when attempting to download a file
284                 curl.setopt(pycurl.FOLLOWLOCATION, 0)
285
286                 if self.USE_PROXY:
287                     curl.setopt(pycurl.PROXY, self.PROXY )
288
289                 if DoSSL:
290                     curl.setopt(pycurl.SSLVERSION, self.CURL_SSL_VERSION)
291                 
292                     if DoCertCheck:
293                         curl.setopt(pycurl.CAINFO, certpath)
294                         curl.setopt(pycurl.SSL_VERIFYPEER, 2)
295                         
296                     else:
297                         curl.setopt(pycurl.SSL_VERIFYPEER, 0)
298                 
299                 if dopostdata:
300                     curl.setopt(pycurl.POSTFIELDS, postdata)
301
302                 # setup multipart/form-data upload
303                 if FormData:
304                     curl.setopt(pycurl.HTTPPOST, FormData)
305
306                 curl.setopt(pycurl.URL, url)
307             else:
308
309                 cmdline = "%s " \
310                           "--connect-timeout %d " \
311                           "--max-time %d " \
312                           "--header Pragma: " \
313                           "--output %s " \
314                           "--fail " % \
315                           (self.CURL_CMD, ConnectTimeout,
316                            MaxTransferTime, DestFilePath)
317
318                 if dopostdata:
319                     cmdline = cmdline + "--data '" + postdata + "' "
320
321                 if FormData:
322                     cmdline = cmdline + "".join(["--form '" + field + "' " for field in FormData])
323
324                 if not self.VERBOSE:
325                     cmdline = cmdline + "--silent "
326                     
327                 if self.USE_PROXY:
328                     cmdline = cmdline + "--proxy %s " % self.PROXY
329
330                 if DoSSL:
331                     cmdline = cmdline + "--sslv%d " % self.CURL_SSL_VERSION
332                     if DoCertCheck:
333                         cmdline = cmdline + "--cacert %s " % certpath
334                  
335                 cmdline = cmdline + url
336
337                 self.Message( "curl command: %s" % cmdline )
338                 
339                 
340             if PYCURL_LOADED:
341                 try:
342                     # setup the output file
343                     outfile = open(DestFilePath,"wb")
344                     
345                     self.Message( "Opened output file %s" % DestFilePath )
346                 
347                     curl.setopt(pycurl.WRITEDATA, outfile)
348                 
349                     self.Message( "Fetching..." )
350                     curl.perform()
351                     self.Message( "Done." )
352                 
353                     http_result= curl.getinfo(pycurl.HTTP_CODE)
354                     curl.close()
355                 
356                     outfile.close()
357                     self.Message( "Results saved in %s" % DestFilePath )
358
359                     # check the code, return 1 if successfull
360                     if http_result == self.HTTP_SUCCESS:
361                         self.Message( "Successfull!" )
362                         return 1
363                     else:
364                         self.Message( "Failure, resultant http code: %d" % \
365                                       http_result )
366
367                 except pycurl.error, err:
368                     errno, errstr= err
369                     self.Error( "connect to %s failed; curl error %d: '%s'\n" %
370                        (server,errno,errstr) )
371         
372                 if not outfile.closed:
373                     try:
374                         os.unlink(DestFilePath)
375                         outfile.close()
376                     except OSError:
377                         pass
378
379             else:
380                 self.Message( "Fetching..." )
381                 rc = os.system(cmdline)
382                 self.Message( "Done." )
383                 
384                 if rc != 0:
385                     try:
386                         os.unlink( DestFilePath )
387                     except OSError:
388                         pass
389                     self.Message( "Failure, resultant curl code: %d" % rc )
390                     self.Message( "Removed %s" % DestFilePath )
391                 else:
392                     self.Message( "Successfull!" )
393                     return 1
394             
395         self.Error( "Unable to successfully contact any boot servers.\n" )
396         return 0
397
398
399
400
401 def usage():
402     print(
403     """
404 Usage: BootServerRequest.py [options] <partialpath>
405 Options:
406  -c/--checkcert        Check SSL certs. Ignored if -s/--ssl missing.
407  -h/--help             This help text
408  -o/--output <file>    Write result to file
409  -s/--ssl              Make the request over HTTPS
410  -v                    Makes the operation more talkative
411 """);  
412
413
414
415 if __name__ == "__main__":
416     import getopt
417     
418     # check out our command line options
419     try:
420         opt_list, arg_list = getopt.getopt(sys.argv[1:],
421                                            "o:vhsc",
422                                            [ "output=", "verbose", \
423                                              "help","ssl","checkcert"])
424
425         ssl= 0
426         checkcert= 0
427         output_file= None
428         verbose= 0
429         
430         for opt, arg in opt_list:
431             if opt in ("-h","--help"):
432                 usage(0)
433                 sys.exit()
434             
435             if opt in ("-c","--checkcert"):
436                 checkcert= 1
437             
438             if opt in ("-s","--ssl"):
439                 ssl= 1
440
441             if opt in ("-o","--output"):
442                 output_file= arg
443
444             if opt == "-v":
445                 verbose= 1
446     
447         if len(arg_list) != 1:
448             raise Exception
449
450         partialpath= arg_list[0]
451         if string.lower(partialpath[:4]) == "http":
452             raise Exception
453
454     except:
455         usage()
456         sys.exit(2)
457
458     # got the command line args straightened out
459     requestor= BootServerRequest(verbose)
460         
461     if output_file:
462         requestor.DownloadFile( partialpath, None, None, ssl,
463                                 checkcert, output_file)
464     else:
465         result= requestor.MakeRequest( partialpath, None, None, ssl, checkcert)
466         if result:
467             print result
468         else:
469             sys.exit(1)