source: TI12-security/trunk/python/Tests/MyProxyClient/m2CryptoMyPxClnt.py @ 1852

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/Tests/MyProxyClient/m2CryptoMyPxClnt.py@1852
Revision 1852, 26.8 KB checked in by pjkersha, 13 years ago (diff)

Added working info and destroy methods. TODO: info command returns True/False?
if credential exists but additional information can be parsed from the
response such as the owner of the credential and the remaining time before
the credential expires.

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2"""MyProxy Client interface
3
4Based on original program myproxy_logon Tom Uram <turam@mcs.anl.gov>
5
6NERC Data Grid Project
7
8@author P J Kershaw 05/12/06
9
10@copyright (C) 2006 CCLRC & NERC
11
12@license This software may be distributed under the terms of the Q Public
13License, version 1.0 or later.
14"""
15
16import os
17import socket
18from M2Crypto import X509, RSA, EVP, m2, BIO
19from M2Crypto.SSL.Context import Context
20from M2Crypto.SSL.Connection import Connection
21
22import re
23import base64
24
25
26class MyProxyClientError(Exception):
27    """Catch all exception class"""
28   
29class GetError(Exception):
30    """Exceptions arising from get request to server"""
31   
32class RetrieveError(Exception):
33    """Error recovering a response from MyProxy"""
34
35
36debug = 0
37def debuglevel(level):
38    global debug
39    return debug >= level
40
41class MyProxyClient(object):
42    """MyProxy client interface
43   
44    @cvar __getCmd: get command string
45    @cvar __storeCmd: store command string
46    @cvar _certReqParamName: names of parameters needed to generate a
47    certificate request e.g. CN, OU etc.
48    """
49     
50    __getCmd="""VERSION=MYPROXYv2
51COMMAND=0
52USERNAME=%s
53PASSPHRASE=%s
54LIFETIME=%d\0"""
55
56    __infoCmd="""VERSION=MYPROXYv2
57COMMAND=2
58USERNAME=%s
59PASSPHRASE=PASSPHRASE
60LIFETIME=0"""
61 
62    __destroyCmd="""VERSION=MYPROXYv2
63COMMAND=3
64USERNAME=%s
65PASSPHRASE=PASSPHRASE
66LIFETIME=0"""
67   
68    __storeCmd="""VERSION=MYPROXYv2
69COMMAND=5
70USERNAME=%s
71PASSPHRASE=
72LIFETIME=%d\0"""
73
74 
75    _certReqParamName = ('O', 'OU')
76
77    #_________________________________________________________________________           
78    def __init__(self, 
79                 hostname=os.environ.get('MYPROXY_SERVER'), 
80                 port=7512,
81                 **certReqKw):
82        """
83        @param hostname string for MyProxy server - defaults to
84        MYPROXY_SERVER environment variable
85        @param integer port number MyProxy is running on
86        """
87        self.hostname = hostname
88        self.port = port
89       
90        # Set-up parameter names for certificate request
91        self.__certReqParam = {}.fromkeys(MyProxyClient._certReqParamName)
92       
93        # Check for parameter names set from input
94        self.certReqParam = certReqKw
95
96    #_________________________________________________________________________       
97    def __setCertReqParam(self, dict):
98        '''certReqParam property set method - forces setting of certificate
99        request parameter names to valid values
100       
101        @param dict: dictionary of parameters'''
102       
103        invalidKw = [k for k in dict \
104                     if k not in MyProxyClient._certReqParamName]
105        if invalidKw:
106            raise MyProxyClientError, \
107    "Invalid certificate request keyword(s): %s.  Valid keywords are: %s" % \
108    (', '.join(invalidKw), ', '.join(MyProxyClient._certReqParamName))
109   
110        self.__certReqParam.update(dict)
111
112    #_________________________________________________________________________       
113    def __getCertReqParam(self):
114        """certReqParam property set method - for Certificate request
115        parameters dict"""
116        return self.__certReqParam
117   
118   
119    certReqParam = property(fset=__setCertReqParam,
120                            fget=__getCertReqParam,
121                            doc="Dictionary of parameters for cert. request")
122   
123    #_________________________________________________________________________       
124    def _createCertReq(self, CN, nBitsForKey=1024, messageDigest="md5"):
125        """
126        Create a certificate request.
127       
128        @param CN: Common Name for certificate - effectively the same as the
129        username for the MyProxy credential
130        @param nBitsForKey: number of bits for private key generation -
131        default is 1024
132        @param messageDigest: message disgest type - default is MD5
133        @return tuple of certificate request PEM text and private key PEM text
134        """
135       
136        # Check all required certifcate request DN parameters are set               
137        # Create certificate request
138        req = X509.Request()
139   
140        # Generate keys
141        key = RSA.gen_key(nBitsForKey, m2.RSA_F4)
142   
143        # Create public key object
144        pubKey = EVP.PKey()
145        pubKey.assign_rsa(key)
146       
147        # Add the public key to the request
148        req.set_version(0)
149        req.set_pubkey(pubKey)
150       
151        # Set DN
152        x509Name = X509.X509_Name()
153        x509Name.CN = CN
154        x509Name.OU = self.__certReqParam['OU']
155        x509Name.O = self.__certReqParam['O']
156        req.set_subject_name(x509Name)
157       
158        req.sign(pubKey, messageDigest)
159       
160        return (req.as_asn1(), key.as_pem(cipher=None))
161   
162   
163    #_________________________________________________________________________           
164    def _deserializeResponse(self, msg):
165        """
166        Deserialize a MyProxy server response
167       
168        @param msg: string response message from MyProxy server
169        @return tuple of integer response and errorTxt string (if any)
170        """
171       
172        lines = msg.split('\n')
173       
174        # get response value
175        responselines = filter(lambda x: x.startswith('RESPONSE'), lines)
176        responseline = responselines[0]
177        respCode = int(responseline.split('=')[1])
178       
179        # get error text
180        errorTxt = ""
181        errorlines = filter(lambda x: x.startswith('ERROR'), lines)
182        for e in errorlines:
183            etext = e.split('=')[1]
184            errorTxt += etext
185       
186        return respCode, errorTxt
187   
188 
189    #_________________________________________________________________________             
190    def _deserializeCerts(self, inputDat):
191        """Unpack certificates returned from a get delegation call to the
192        server
193       
194        @param inputDat: string containing the proxy cert and private key
195        and signing cert all in DER format
196       
197        @return list containing the equivalent to the input in PEM format"""
198        pemCerts = []       
199        dat = inputDat
200       
201        while dat:   
202            # find start of cert, get length       
203            ind = dat.find('\x30\x82')
204            if ind < 0:
205                break
206               
207            len = 256*ord(dat[ind+2]) + ord(dat[ind+3])
208   
209            # extract der-format cert, and convert to pem
210            derCert = dat[ind:ind+len+4]
211           
212            x509 = X509.load_cert_string(derCert, type=X509.TYPE_ASN1)
213            pemCert = x509.as_pem()
214           
215            pemCerts.append(pemCert)
216   
217            # trim cert from data
218            dat = dat[ind + len + 4:]
219           
220        return pemCerts
221
222
223    #_________________________________________________________________________   
224    def info(self,
225             username, 
226             ownerCertFile=None,
227             ownerKeyFile=None,
228             ownerPassphrase=None):
229        """return True/False whether credentials exist on the server for a
230        given username
231       
232        Exceptions:  GetError, StoreCredError
233       
234        @param username: username selected for credential
235        @param ownerCertFile: certificate used for client authentication with
236        the MyProxy server SSL connection.  This ID will be set as the owner
237        of the stored credentials.  Only the owner can later remove
238        credentials with myproxy-destroy or the destroy method.  If not set,
239        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
240        @param ownerKeyFile: corresponding private key file.  See explanation
241        for ownerCertFile
242        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
243        private key is not password protected. 
244        @return none
245        """
246        globusLoc = os.environ.get('GLOBUS_LOCATION')
247        if not ownerCertFile or not ownerKeyFile:
248            if globusLoc:
249                ownerCertFile = os.path.join(globusLoc, 'etc', 'hostcert.pem')
250                ownerKeyFile = os.path.join(globusLoc, 'etc', 'hostkey.pem')
251            else:
252                raise MyProxyClientError, \
253            "No client authentication cert. and private key file were given"
254       
255        import pdb;pdb.set_trace()
256        context = Context(protocol='sslv3')
257        context.load_cert(ownerCertFile,
258                          keyfile=ownerKeyFile,
259                          callback=lambda *ar, **kw: ownerPassphrase)
260   
261        # Disable for compatibility with myproxy server (er, globus)
262        # globus doesn't handle this case, apparently, and instead
263        # chokes in proxy delegation code
264        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
265       
266        # connect to myproxy server
267        if debuglevel(1):   print "debug: connect to myproxy server"
268        conn = Connection(context, sock=socket.socket())
269       
270        # Fudge to avoid checking client cert - seems to pick globus
271        # host/<hostname> one
272        conn.clientPostConnectionCheck = None
273        conn.connect((self.hostname, self.port))
274       
275        # send globus compatibility stuff
276        if debuglevel(1):   
277            print "debug: send globus compat byte"
278        conn.write('0')
279   
280        # send destroy command
281        if debuglevel(1): 
282            print "debug: send info command"
283           
284        storeCmd = MyProxyClient.__infoCmd % (username)
285        conn.write(storeCmd)
286   
287        # process server response
288        if debuglevel(1):   
289            print "debug: get server response for info command request"
290           
291        dat = conn.recv(8192)
292        if debuglevel(1):   
293            print dat
294           
295        respCode, errorTxt = self._deserializeResponse(dat)
296        if respCode:
297            raise GetError, errorTxt
298        else:
299            if debuglevel(1):   
300                print "debug: server response ok"
301               
302        return bool(respCode)
303
304
305    #_________________________________________________________________________   
306    def destroy(self,
307                username, 
308                ownerCertFile=None,
309                ownerKeyFile=None,
310                ownerPassphrase=None):
311        """destroy credentials from the server for a given username
312       
313        Exceptions:  GetError, StoreCredError
314       
315        @param username: username selected for credential
316        @param ownerCertFile: certificate used for client authentication with
317        the MyProxy server SSL connection.  This ID will be set as the owner
318        of the stored credentials.  Only the owner can later remove
319        credentials with myproxy-destroy or the destroy method.  If not set,
320        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem
321        @param ownerKeyFile: corresponding private key file.  See explanation
322        for ownerCertFile
323        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
324        private key is not password protected. 
325        @return none
326        """
327        globusLoc = os.environ.get('GLOBUS_LOCATION')
328        if not ownerCertFile or not ownerKeyFile:
329            if globusLoc:
330                ownerCertFile = os.path.join(globusLoc, 'etc', 'hostcert.pem')
331                ownerKeyFile = os.path.join(globusLoc, 'etc', 'hostkey.pem')
332            else:
333                raise MyProxyClientError, \
334            "No client authentication cert. and private key file were given"
335       
336        import pdb;pdb.set_trace()
337        context = Context(protocol='sslv3')
338        context.load_cert(ownerCertFile,
339                          keyfile=ownerKeyFile,
340                          callback=lambda *ar, **kw: ownerPassphrase)
341   
342        # Disable for compatibility with myproxy server (er, globus)
343        # globus doesn't handle this case, apparently, and instead
344        # chokes in proxy delegation code
345        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
346       
347        # connect to myproxy server
348        if debuglevel(1):   print "debug: connect to myproxy server"
349        conn = Connection(context, sock=socket.socket())
350       
351        # Fudge to avoid checking client cert - seems to pick globus
352        # host/<hostname> one
353        conn.clientPostConnectionCheck = None
354        conn.connect((self.hostname, self.port))
355       
356        # send globus compatibility stuff
357        if debuglevel(1):   
358            print "debug: send globus compat byte"
359        conn.write('0')
360   
361        # send destroy command
362        if debuglevel(1): 
363            print "debug: send destroy command"
364           
365        storeCmd = MyProxyClient.__destroyCmd % (username)
366        conn.write(storeCmd)
367   
368        # process server response
369        if debuglevel(1):   
370            print "debug: get server response for destroy command request"
371           
372        dat = conn.recv(8192)
373        if debuglevel(1):   
374            print dat
375           
376        respCode, errorTxt = self._deserializeResponse(dat)
377        if respCode:
378            raise GetError, errorTxt
379        else:
380            if debuglevel(1):   
381                print "debug: server response ok"
382
383
384    #_________________________________________________________________________   
385    def store(self,
386              username, 
387              certFile,
388              keyFile,
389              ownerCertFile=None,
390              ownerKeyFile=None,
391              ownerPassphrase=None,
392              lifetime=43200):
393        """Upload credentials to the server
394       
395        Exceptions:  GetError, StoreCredError
396       
397        @param username: username selected for credential
398        @param certFile: user's X.509 certificate in PEM format
399        @param keyFile: equivalent private key file in PEM format
400        @param ownerCertFile: certificate used for client authentication with
401        the MyProxy server SSL connection.  This ID will be set as the owner
402        of the stored credentials.  Only the owner can later remove
403        credentials with myproxy-destroy or the destroy method.  If not set,
404        this argument defaults to $GLOBUS_LOCATION/etc/hostcert.pem or if this
405        is not set, certFile
406        @param ownerKeyFile: corresponding private key file.  See explanation
407        for ownerCertFile
408        @param ownerPassphrase: passphrase for ownerKeyFile.  Omit if the
409        private key is not password protected.  Nb. keyFile is expected to
410        be passphrase protected as this will be the passphrase used for
411        logon / getDelegation.
412        @return none
413        """
414        globusLoc = os.environ.get('GLOBUS_LOCATION')
415        if not ownerCertFile or not ownerKeyFile:
416            if globusLoc:
417                ownerCertFile = os.path.join(globusLoc, 'etc', 'hostcert.pem')
418                ownerKeyFile = os.path.join(globusLoc, 'etc', 'hostkey.pem')
419            else:
420                ownerCertFile = certFile
421                ownerKeyFile = keyFile
422       
423        import pdb;pdb.set_trace()
424        context = Context(protocol='sslv3')
425        context.load_cert(ownerCertFile,
426                          keyfile=ownerKeyFile,
427                          callback=lambda *ar, **kw: ownerPassphrase)
428#        context.load_cert('../hostcert.pem',
429#                          keyfile='../hostkey.pem',
430#                          callback=lambda *ar, **kw: ownerPassphrase)
431   
432        # Disable for compatibility with myproxy server (er, globus)
433        # globus doesn't handle this case, apparently, and instead
434        # chokes in proxy delegation code
435        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
436       
437        # connect to myproxy server
438        if debuglevel(1):   print "debug: connect to myproxy server"
439        conn = Connection(context, sock=socket.socket())
440       
441        # Fudge to avoid checking client cert - seems to pick globus
442        # host/<hostname> one
443        conn.clientPostConnectionCheck = None
444        conn.connect((self.hostname, self.port))
445       
446        # send globus compatibility stuff
447        if debuglevel(1):   
448            print "debug: send globus compat byte"
449        conn.write('0')
450   
451        # send store command
452        if debuglevel(1): 
453            print "debug: send store command"
454           
455        storeCmd = MyProxyClient.__storeCmd % (username, lifetime)
456        conn.write(storeCmd)
457   
458        # process server response
459        if debuglevel(1):   
460            print "debug: get server response for store command request"
461           
462        dat = conn.recv(8192)
463        if debuglevel(1):   
464            print dat
465           
466        respCode, errorTxt = self._deserializeResponse(dat)
467        if respCode:
468            raise GetError, errorTxt
469        else:
470            if debuglevel(1):   
471                print "debug: server response ok"
472       
473        # Send certificate and private key
474        certTxt = X509.load_cert(certFile).as_pem()
475        keyTxt = open(keyFile).read()
476       
477        conn.send(certTxt + keyTxt)
478   
479   
480        # process server response
481        if debuglevel(1):   
482            print "debug: get server response for store command completed"
483        resp = conn.recv(8192)
484        respCode, errorTxt = self._deserializeResponse(resp)
485        if respCode:
486            raise RetrieveError, errorTxt
487        else:
488            if debuglevel(1):
489                print "debug: server response ok"
490       
491       
492    def logon(self, username, passphrase, lifetime=43200):
493        """Retrieve a proxy credential from a MyProxy server
494       
495        Exceptions:  GetError, RetrieveError
496       
497        @param username: username of credential
498        @param passphrase: pass-phrase for private key of credential held on
499        server
500        @return list containing the credentials as strings in PEM format: the
501        proxy certificate, it's private key and the signing certificate.
502        """
503   
504        context = Context(protocol='sslv3')
505       
506        # disable for compatibility with myproxy server (er, globus)
507        # globus doesn't handle this case, apparently, and instead
508        # chokes in proxy delegation code
509        context.set_options(m2.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
510       
511        # connect to myproxy server
512        if debuglevel(1):   print "debug: connect to myproxy server"
513        conn = Connection(context, sock=socket.socket())
514       
515        # Fudge to avoid checking client cert - seems to pick globus
516        # host/<hostname> one
517        conn.clientPostConnectionCheck = None
518        conn.connect((self.hostname, self.port))
519       
520        # send globus compatibility stuff
521        if debuglevel(1):   print "debug: send globus compat byte"
522        conn.write('0')
523   
524        # send get command
525        if debuglevel(1):   print "debug: send get command"
526        getCmd = MyProxyClient.__getCmd % (username,passphrase,lifetime)
527        conn.write(getCmd)
528   
529        # process server response
530        if debuglevel(1):   print "debug: get server response"
531        dat = conn.recv(8192)
532        if debuglevel(1):   print dat
533        respCode, errorTxt = self._deserializeResponse(dat)
534        if respCode:
535            raise GetError, errorTxt
536        else:
537            if debuglevel(1):   print "debug: server response ok"
538       
539        # generate and send certificate request
540        # - The client will generate a public/private key pair and send a
541        #   NULL-terminated PKCS#10 certificate request to the server.
542        if debuglevel(1):   print "debug: send cert request"
543   
544        certReq, priKey = self._createCertReq(username)
545        conn.send(certReq)
546   
547        # process certificates
548        # - 1 byte , number of certs
549        dat = conn.recv(1)
550        nCerts = ord(dat[0])
551       
552        # - n certs
553        if debuglevel(1):   print "debug: receive certs"
554        dat = conn.recv(8192)
555        if debuglevel(2):
556            print "debug: dumping cert data to myproxy.dump"
557            f = file('myproxy.dump','w')
558            f.write(dat)
559            f.close()
560   
561        # process server response
562        if debuglevel(1):   print "debug: get server response"
563        resp = conn.recv(8192)
564        respCode, errorTxt = self._deserializeResponse(resp)
565        if respCode:
566            raise RetrieveError, errorTxt
567        else:
568            if debuglevel(1):   print "debug: server response ok"
569   
570        # deserialize certs from received cert data
571        pemCerts = self._deserializeCerts(dat)
572        if len(pemCerts) != nCerts:
573            RetrieveError, "%d certs expected, %d received" % \
574                                                    (nCerts, len(pemCerts))
575   
576        # write certs and private key to file
577        # - proxy cert
578        # - private key
579        # - rest of cert chain
580        if debuglevel(1):   print "debug: write proxy and certs to",outfile
581       
582        creds = pemCerts[0]+priKey+''.join([cert for cert in pemCerts[1:]])
583       
584        return creds
585       
586
587    def getDelegation(self, *arg, **kw):
588        """Retrieve proxy cert for user - same as logon"""
589        self.logon(*arg, **kw)
590
591
592#_____________________________________________________________________________   
593def main():
594    import sys
595    import optparse
596    import getpass
597   
598    parser = optparse.OptionParser()
599    parser.add_option("-i", 
600                      "--info", 
601                      dest="info", 
602                      default=False,
603                      action="store_true",
604                      help="check whether a credential exists")
605
606    parser.add_option("-z", 
607                      "--destroy", 
608                      dest="destroy", 
609                      default=False,
610                      action="store_true",
611                      help="destroy credential")
612
613    parser.add_option("-g", 
614                      "--get-delegation", 
615                      dest="getDelegation", 
616                      default=False,
617                      action="store_true",
618                      help="Get delegation / logon")
619   
620    parser.add_option("-c", 
621                      "--certfile", 
622                      dest="certFile", 
623                      default=None,
624                      help="Certificate to be stored")
625   
626    parser.add_option("-y", 
627                      "--keyfile", 
628                      dest="keyFile", 
629                      default=None,
630                      help="Private key to be stored")
631   
632    parser.add_option("-w", 
633                      "--keyfile-passphrase", 
634                      dest="ownerPassphrase", 
635                      default=None,
636                      help="Pass-phrase for Private key used for SSL client")
637
638    parser.add_option("-s", 
639                      "--pshost", 
640                      dest="host", 
641                      help="The hostname of the MyProxy server to contact")
642   
643    parser.add_option("-p", 
644                      "--psport", 
645                      dest="port", 
646                      default=7512,
647                      type="int",
648                      help="The port of the MyProxy server to contact")
649   
650    parser.add_option("-l", 
651                      "--username", 
652                      dest="username", 
653                      help=\
654    "The username with which the credential is stored on the MyProxy server")
655
656    parser.add_option("-o", 
657                      "--out", 
658                      dest="outfile", 
659                      help=\
660    "The username with which the credential is stored on the MyProxy server")
661
662    parser.add_option("-t", 
663                      "--proxy-lifetime", 
664                      dest="lifetime", 
665                      default=43200,
666                      type="int",
667                      help=\
668    "The username with which the credential is stored on the MyProxy server")
669
670    parser.add_option("-d", 
671                      "--debug", 
672                      dest="debug", 
673                      type="int",
674                      default=0,
675                      help=\
676"Debug mode: 1=print debug info; 2=print as in (1), and dump data to myproxy.dump")
677
678    (options, args) = parser.parse_args()
679   
680    debug = options.debug
681   
682    # process options   
683    username = options.username
684    if not username:
685        if sys.platform == 'win32':
686            username = os.environ["USERNAME"]
687        else:
688            import pwd
689            username = pwd.getpwuid(os.geteuid())[0]
690
691    hostname = options.host or os.environ.get('MYPROXY_SERVER')
692    myProxy = MyProxyClient(hostname=hostname,
693                            port=options.port,
694                            O='NDG',
695                            OU='BADC')
696   
697    if options.getDelegation:
698               
699        outfile = options.outfile
700        if not outfile:
701            if sys.platform == 'win32':
702                outfile = 'proxy'
703            elif sys.platform in ['linux2','darwin']:
704                outfile = '/tmp/x509up_u%s' % (os.getuid())
705   
706        # Get MyProxy password
707        passphrase = getpass.getpass()
708           
709        # Retrieve proxy cert
710        try:
711            creds = myProxy.logon(username, 
712                                  passphrase, 
713                                  lifetime=options.lifetime)
714            open(outfile, 'w').write(creds)
715            print "A proxy has been received for user %s in %s." % \
716                (username, outfile)
717           
718        except Exception,e:
719            if debuglevel(1):
720                import traceback
721                traceback.print_exc()
722            else:
723                print "Error:", e
724               
725    elif options.info:
726        try:
727            myProxy.info(username, 
728                         options.certFile,
729                         options.keyFile,
730                         ownerPassphrase=open('../tmp2').read().strip())
731           
732        except Exception, e:
733            if debuglevel(1):
734                import traceback
735                traceback.print_exc()
736            else:
737                import traceback
738                traceback.print_exc()
739                print "Error:", e
740               
741    elif options.destroy:
742        try:
743            myProxy.destroy(username, 
744                            ownerCertFile=options.certFile,
745                            ownerKeyFile=options.keyFile,
746                            ownerPassphrase=open('../tmp2').read().strip())
747           
748        except Exception, e:
749            if debuglevel(1):
750                import traceback
751                traceback.print_exc()
752            else:
753                import traceback
754                traceback.print_exc()
755                print "Error:", e
756    else:
757        try:
758            myProxy.store(username, 
759                          options.certFile,
760                          options.keyFile,
761                          ownerCertFile=options.certFile,
762                          ownerKeyFile=options.keyFile,
763                          ownerPassphrase=open('../tmp2').read().strip(),
764                          lifetime=options.lifetime)
765           
766        except Exception, e:
767            if debuglevel(1):
768                import traceback
769                traceback.print_exc()
770            else:
771                import traceback
772                traceback.print_exc()
773                print "Error:", e
774
775
776if __name__ == '__main__':
777    main()
778   
Note: See TracBrowser for help on using the repository browser.