Pastebin launched a little side project called VERYVIRAL.com, check it out ;-) Want more features on Pastebin? Sign Up, it's FREE!
Guest

Untitled

By: a guest on Apr 2nd, 2012  |  syntax: None  |  size: 46.38 KB  |  views: 25  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. #!/usr/bin/env python
  2. """Script to connect your machine to SUSE IPsec RAS service"
  3.  
  4.    See print_help() below for a longer description."""
  5.  
  6. #__revision__ = "$Id: ras.py,v 1.53 2005/07/19 20:15:41 garloff Exp $"
  7. __revision__ = "@hg_rev:173@"
  8.  
  9. # (c) Kurt Garloff <garloff@suse.de>, 2005-04-17, Python license
  10. #
  11. # TODO:
  12. # * Track status in config file specific lock/logfiles, so more than one
  13. #   instance can run.
  14. # * PyQt GUI.
  15. # * Allow doing telnet via ssh as well.
  16.  
  17. import sys, os, time, re, signal, errno
  18. import ConfigParser
  19.  
  20. # Colorize
  21. esc = chr(27)
  22. red    = esc + '[0;31m'
  23. green  = esc + '[0;32m'
  24. yellow = esc + '[0;33m'
  25. bold   = esc + '[0;1m'
  26. norm   = esc + '[0;0m'
  27. redraspy    = red    + 'ras.py:' + norm
  28. greenraspy  = green  + 'ras.py:' + norm
  29. yellowraspy = yellow + 'ras.py:' + norm
  30.  
  31. # Global Vars
  32. passphrase = ''
  33. cfg = ConfigParser.RawConfigParser()
  34. configfile = '/etc/ras.config'
  35. timefmt = "%a %Y-%m-%d %H:%M:%S"
  36. interrupted = 0; reconnect = 0
  37. verbose = 0; quiet = 0
  38. outvfn = None; outqfn = sys.stdout.fileno()
  39. daemon = 0
  40. alreadyauth = 0
  41. connections = []
  42. iswhiterussian = False
  43.  
  44. # Global vars for the daemon
  45. lockfilename = '/var/run/ras.pid'
  46. logfilename = '/var/log/ras-messages'
  47. NEWRASSESSION  = '*** NEW   RAS SESSION ***'
  48. BEGINRASSTATUS = '*** BEGIN RAS  STATUS ***'
  49. ENDRASSTATUS   = '*** END   RAS  STATUS ***'
  50.  
  51. global_kwds = ('pinginterval', 'pingtimeout', 'pingfail', 'failholdoff',
  52.                 'maxconnect', 'authhost', 'authretry', 'logname',
  53.                 'virtip', 'netdev', 'gateway',
  54.                 'ipseccmd', 'ipsecinit', 'ipsecconfread',
  55.                 'ipcmd', 'telnetcmd', 'routeraw', 'netdevexist',
  56.                 'cmdprefix')
  57.  
  58. conn_kwds = ('route', 'mtu', 'pinghost', 'desc')
  59.  
  60. def create_lockfile():
  61.     "Creates lock file and writes pid to it"
  62.     lockfd = os.open(lockfilename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
  63.     # No O_EXCL needed, the location is not world writable
  64.     if not lockfd:
  65.         print redraspy, "Can't create lock file %s" % lockfilename
  66.         sys.exit(1)
  67.     os.write(lockfd, "%i\n" % os.getpid())
  68.  
  69. def check_lockfile():
  70.     "Checks for lock file and returns pid if present and if process exists"
  71.     try:
  72.         lfile = open(lockfilename)
  73.     except:
  74.         return 0
  75.     txt = lfile.readline()
  76.     dpid = int(txt)
  77.     lfile.close()
  78.     if not os.access('/proc/%i' % dpid, os.R_OK):
  79.         print yellowraspy, "removing stale lockfile for pid %i" % dpid
  80.         os.remove(lockfilename)
  81.         dpid = 0
  82.     return dpid
  83.  
  84. def del_lockfile():
  85.     "Removes lock file"
  86.     dpid = check_lockfile()
  87.     if not dpid:
  88.         return
  89.     if dpid != os.getpid():
  90.         print yellowraspy, "Won't remove lock file for pid %i, self %i" % \
  91.                 (dpid, os.getpid())
  92.     os.remove(lockfilename)
  93.  
  94. def daemonize():
  95.     "Fork and exit parent. Child closes fds and logs to /var/log/ras-messages"
  96.     if os.fork():
  97.         os._exit(0)
  98.  
  99.     create_lockfile()
  100.     os.setsid()
  101.     #os.umask(077)
  102.     for i in range(3):
  103.         try:
  104.             os.close(i)
  105.         except OSError, err:
  106.             if err.errno != errno.EBADF:
  107.                 raise
  108.     os.open('/dev/null', os.O_RDONLY)       # stdin
  109.     os.open(logfilename, os.O_WRONLY|os.O_CREAT|os.O_APPEND)
  110.     os.dup(1)                   # stdout and stderr
  111.     signal.signal(signal.SIGHUP, sighandler_reconnect)
  112.     print NEWRASSESSION
  113.  
  114. def del_env(name):
  115.     "Delete environment variables"
  116.     try:
  117.         del os.environ[name]
  118.     except:
  119.         pass
  120.  
  121. def sanitize_env():
  122.     "Make environment safe for root use, just in case ..."
  123.     os.environ['PATH'] = '/sbin:/bin:/usr/sbin:/usr/bin'
  124.     os.environ['SHELL'] = '/bin/sh'
  125.     os.environ['LC_ALL'] = 'POSIX'
  126.     del_env('LD_PRELOAD')
  127.     del_env('LD_LIBRARY_PATH')
  128.     del_env('LD_RUN_PATH')
  129.     del_env('MALLOC_CHECK_')
  130.     # We close all fds in daemonize()
  131.  
  132. # Signal safe variants of time.sleep() and fdes.read()
  133. def my_sleep(secs):
  134.     """Sleep secs seconds, but continue sleeping after receiving signal,
  135.        don't sleep if interrupted is set."""
  136.     starttm = time.time()
  137.     #time.sleep(secs)
  138.     while not interrupted and not reconnect:
  139.         tosleep = starttm + secs - time.time()
  140.         if (tosleep <= 0):
  141.             break
  142.         time.sleep(tosleep)
  143.  
  144. def my_read(fdes):
  145.     "Read from file descriptor fdes, but restart on EINTR"
  146.     txt = ''
  147.     while not interrupted:
  148.         try:
  149.             txt += fdes.read()
  150.             return txt
  151.         except IOError, err:
  152.             if err.errno in (errno.EINTR, errno.EAGAIN):
  153.                 continue
  154.             else:
  155.                 raise
  156.  
  157. def my_read_until(sess, what, tout = None):
  158.     "sess.read_until with safety against EINTR"
  159.     import socket, select
  160.     txt = ''
  161.     while 1:
  162.         try:
  163.             txt += sess.read_until(what, tout)
  164.             return txt
  165.         except socket.error, err:
  166.             if err.args[0] in (errno.EINTR, errno.EAGAIN):
  167.                 continue
  168.             else:
  169.                 raise
  170.         except select.error, err:
  171.             if err.args[0] in (errno.EINTR, errno.EAGAIN):
  172.                 continue
  173.             else:
  174.                 raise
  175.  
  176. def my_read_some(sess):
  177.     "sess.read_some with safety against EINTR"
  178.     import socket, select
  179.     while 1:
  180.         try:
  181.             txt = sess.read_some()
  182.             return txt
  183.         except socket.error, err:
  184.             #print err
  185.             if err.args[0] in (errno.EINTR, errno.EAGAIN):
  186.                 continue
  187.             else:
  188.                 raise
  189.         except select.error, err:
  190.             if err.args[0] in (errno.EINTR, errno.EAGAIN):
  191.                 continue
  192.             else:
  193.                 raise
  194.  
  195. def write_all(fdes, data):
  196.     "Output data to fdes"
  197.     while data:
  198.         written = os.write(fdes, data)
  199.         data = data[written:]
  200.  
  201. def read_until(fdes, search, timeout = 4, fdout = None):
  202.     """Read from fdes and wait for a list of strings (search) or EOF.
  203.        Echos read data to fdout (if specified), returns all captured
  204.        text and a status code:
  205.        0 .. N-1: string that matched
  206.        -1      : EOF
  207.        -2      : timeout
  208.        -X      : Other error
  209.        Notes: fdes and fdout are file numbers not objects."""
  210.     import select
  211.     rdbuf = ''
  212.     #print 'read_until(%i, %s, %i, %i)' % (fdes, search, timeout, fdout)
  213.     while 1:
  214.         try:
  215.             rfd, wfd, xfd = select.select([fdes], [], [fdes], timeout)
  216.         except select.error, err:
  217.             # Hmm, why does select not raise OSError?
  218.             if err.args[0] in (errno.EINTR, errno.EAGAIN):
  219.                 continue
  220.             else:
  221.                 raise
  222.         # Timeout
  223.         if not fdes in rfd and not fdes in xfd:
  224.             return rdbuf, -2
  225.             #print yellowraspy, 'Timeout waiting for data'
  226.         read = os.read(fdes, 1024)
  227.         if not read:
  228.             # EOF
  229.             return rdbuf, -1
  230.         rdbuf += read
  231.         if fdout:
  232.             write_all(fdout, read)
  233.         # Search for strings
  234.         idx = 0
  235.         for sstr in search:
  236.             if sstr in rdbuf:
  237.                 return rdbuf, idx
  238.             idx += 1
  239.  
  240. def check_exec(enm):
  241.     "Check for executable enm"
  242.     if not os.access(enm, os.X_OK):
  243.         print redraspy, "Fatal: Can't execute %s" % enm
  244.         sys.exit(5)
  245.  
  246. def check_prereq():
  247.     "Check for the existance of required executables"
  248.     check_exec('/usr/bin/opiekey')
  249.     check_exec('/bin/ping')
  250.     # FIXME: This is fuzzy ...
  251.     if not cfg.has_option('global', 'cmdprefix'):
  252.         check_exec('/sbin/ip')
  253.         check_exec('/etc/init.d/ipsec')
  254.         check_exec('/usr/sbin/ipsec')
  255.  
  256. def check_whiterussian():
  257.     """Check whether we are running on OpenWRT (white russian)
  258.        WhiteRussian 0.9 has a number of flaws in python (segfaults
  259.        on importing platform, telnetlib/sockets not working, getpass
  260.        not working ...
  261.        This detects it and enables the workarounds."""
  262.     global iswhiterussian
  263.     iswhiterussian = False
  264.     try:
  265.         banner = open('/etc/banner', 'r')
  266.     except:
  267.         return
  268.     wrre = re.compile(r'WHITE RUSSIAN \(0\.[0-9]')
  269.     for ln in banner.readlines():
  270.         if wrre.search(ln):
  271.             iswhiterussian = True
  272.             return
  273.  
  274. def pass_timeout(sig, frame):
  275.     "Timeout for password handling"
  276.     print '\n', redraspy, 'Timeout waiting for OPIE Passphrase'
  277.     #signal.signal(sig, signal.SIG_DFL)
  278.     os._exit(128+sig)
  279.  
  280. def mygetpass(prompt = "Password: ", safe = True):
  281.     import termios
  282.     fd = sys.stdin.fileno()
  283.     old = termios.tcgetattr(fd)
  284.     new = termios.tcgetattr(fd)
  285.     new[3] = new[3] & ~termios.ECHO          # lflags
  286.     try:
  287.         termios.tcsetattr(fd, termios.TCSADRAIN, new)
  288.     except:
  289.         if safe:
  290.             raise
  291.         os.popen('stty -echo')
  292.     try:
  293.         passwd = raw_input(prompt)
  294.     finally:
  295.         try:
  296.             termios.tcsetattr(fd, termios.TCSADRAIN, old)
  297.         except:
  298.             os.popen('stty echo')
  299.             print '\n',
  300.     return passwd
  301.  
  302. def input_passphrase():
  303.     "Query OPIE passphrase"
  304.     #import getpass
  305.     global passphrase
  306.     signal.alarm(60)
  307.     signal.signal(signal.SIGALRM, pass_timeout)
  308.     passphrase = mygetpass(greenraspy + ' Enter Passphrase for OPIE: ', False)
  309.     signal.alarm(0)
  310.  
  311. cfg_defaults = {'pinginterval': 20, 'pingtimeout': 4,
  312.                 'pingfail': 4, 'failholdoff': 150, 'authretry': 3,
  313.                 'ipseccmd': '/usr/sbin/ipsec auto',
  314.                 'ipsecinit': '/etc/init.d/ipsec',
  315.                 'ipsecconfread': 'cat /etc/ipsec.conf',
  316.                 'ipcmd': '/sbin/ip',
  317.                 'telnetcmd': '/usr/bin/telnet',
  318.                 'routeraw': 'cat /proc/net/route',
  319.                 'netdevexist': 'test -e /proc/sys/net/ipv4/conf/'}
  320.  
  321. def set_config_defaults():
  322.     "Fill in defaults into the global section"
  323.     for key in cfg_defaults.keys():
  324.         if not cfg.has_option('global', key):
  325.             cfg.set('global', key, cfg_defaults[key])
  326.  
  327. def hextoip(hstr):
  328.     "Convert netw byte order hex number in hstr to ip addr notation"
  329.     ipno = int(hstr, 16)
  330.     return '%i.%i.%i.%i' % (ipno & 0xff, (ipno >> 8) & 0xff,
  331.                 (ipno >> 16) & 0xff, (ipno >> 24) & 0xff)
  332.  
  333. def set_config_netdev():
  334.     "Figure out the IPsec device of the default route"
  335.     # For a kernel with KLIPS, assume ipsec0 is good
  336.     if os.popen('%s%s' % \
  337.                   (cfg.get('global', 'netdevexist'), 'ipsec0')
  338.                 ).close() == None:
  339.         cfg.set('global', 'netdev', 'ipsec0')
  340.     else:
  341.         # Parse /proc/net/route for the default route
  342.         rfd = os.popen('%s' % cfg.get('global', 'routeraw'))
  343.         for line in rfd.readlines():
  344.             splitline = line.split('\t')
  345.             if splitline[1] == '00000000':
  346.                 cfg.set('global', 'netdev', splitline[0])
  347.                 if not cfg.has_option('global', 'gateway'):
  348.                     cfg.set('global', 'gateway',
  349.                         'via %s' % hextoip(splitline[2]))
  350.                 rfd.close()
  351.                 return
  352.         rfd.close()      
  353.         print redraspy, 'Fatal: No netdev specified and no default route'
  354.         sys.exit(1)
  355.  
  356. def cfg_prefix(optname, prefix):
  357.     "Prefix a global cfg option optname with prefix"
  358.     opt = cfg.get('global', optname)
  359.     cfg.set('global', optname, prefix + ' ' + opt)
  360.  
  361. def read_config(cfgfile, reset = 0):
  362.     """Read config file (specified by cfgfile) and
  363.        fill in defaults; store in global var cfg"""
  364.     #global cfg
  365.     if not os.access(cfgfile, os.R_OK):
  366.         print redraspy, "Fatal: Config file %s can't be accessed" % cfgfile
  367.         sys.exit(6)
  368.     if reset:
  369.         logname = cfg.get('global', 'logname')  
  370.         for sect in cfg.sections():
  371.             cfg.remove_section(sect)
  372.     else:
  373.         logname = os.getlogin()
  374.     cfg.read(cfgfile)
  375.     if not cfg.has_section('global'):
  376.         print redraspy, 'Fatal: No [global] section found in %s' % cfgfile
  377.         sys.exit(6)
  378.     set_config_defaults()
  379.     if cfg.has_option('global', 'cmdprefix'):
  380.         prefix = cfg.get('global', 'cmdprefix')  
  381.         cfg_prefix('ipseccmd', prefix)
  382.         cfg_prefix('ipsecinit', prefix)
  383.         cfg_prefix('ipsecconfread', prefix)
  384.         cfg_prefix('ipcmd', prefix)
  385.         cfg_prefix('telnetcmd', prefix)
  386.         cfg_prefix('routeraw', prefix)
  387.         cfg_prefix('netdevexist', prefix)
  388.     cfg.read(cfgfile)
  389.     set_config_defaults()
  390.     if len(cfg.sections()) < 2:
  391.         print redraspy, 'Fatal: No tunnels configured in %s' % cfgfile
  392.         sys.exit(6)
  393.     if not cfg.has_option('global', 'netdev'):
  394.         set_config_netdev()
  395.     if not cfg.has_option('global', 'gateway'):
  396.         cfg.set('global', 'gateway', 'scope link')
  397.     if not cfg.has_option('global', 'logname'):
  398.         cfg.set('global', 'logname', logname)
  399.     #if verbose: print cfg.__dict__
  400.     # Check for unknown options!
  401.     for opt in cfg.options('global'):
  402.         if not opt in global_kwds:
  403.             print yellowraspy, "Unknown option %s in global section" % opt
  404.  
  405.  
  406. def get_opie_key(challenge):
  407.     """Call opiekey, give it the challenge and the passphrase,
  408.        get back and return response."""
  409.     import popen2
  410.     print challenge
  411.     chall = challenge.split(' ')
  412.     if not quiet:
  413.         if int(chall[1]) < 50:
  414.             print yellowraspy, "Renew your OPIE key, only %i logins left" \
  415.                 % int(chall[1])
  416.  
  417.         else:
  418.             print greenraspy, "%i logins left" % int(chall[1])
  419.     opie = popen2.Popen4('/usr/bin/opiekey %s %s %s' % \
  420.             (chall[1], chall[2], chall[3]))
  421.     fdes = opie.fromchild.fileno()
  422.     txt, tout = read_until(fdes, ("pass phrase: ",), 4, outvfn)
  423.     if tout < 0:
  424.         print redraspy, 'Fatal: Timeout waiting for opiekey'
  425.         sys.exit(1)
  426.     # sys.stdout.flush()
  427.     opie.tochild.write(passphrase + '\n')
  428.     opie.tochild.flush()
  429.     # opie.tochild.close()
  430.     # Ignore one linefeed
  431.     ret = opie.fromchild.readline()
  432.     # The answer ...
  433.     ret = my_read(opie.fromchild)
  434.     excode = opie.wait()
  435.     if verbose:
  436.         print " %i " % excode,
  437.     return ret
  438.  
  439. def auth_session_telnetlib():
  440.     "Do telnet to authhost for opie authentication via telnetlib"
  441.     import telnetlib
  442.     #global verbose
  443.     host = cfg.get('global', 'authhost')
  444.     if not quiet:
  445.         print greenraspy, 'Trying auth session to %s' % host
  446.     auth = 0
  447.     authretry = cfg.get('global', 'authretry')
  448.     while not authretry or auth < authretry:
  449.         try:
  450.             sess = telnetlib.Telnet(host)
  451.             break
  452.         except telnetlib.socket.error, err:
  453.             auth += 1
  454.             print yellowraspy, str(err)
  455.             if authretry and auth < authretry:
  456.                 my_sleep(int(cfg.get('global', 'failholdoff')))
  457.             else:
  458.                 print redraspy, "Fatal: Can't connect to %s" % host
  459.                 return 1
  460.     txt = my_read_until(sess, 'login: ', 32)
  461.     if verbose:
  462.         print txt
  463.     if not ('login: ' in txt) or interrupted:
  464.         print redraspy, 'Fatal: Login prompt not found'
  465.         sess.close()
  466.         return 1
  467.     #time.sleep(0.5)
  468.     sess.write(cfg.get('global', 'logname') + '\n')
  469.     txt = my_read_until(sess, 'Response: ', 16)
  470.     if verbose:
  471.         print txt
  472.     resp = get_opie_key(txt.split('\n')[0])
  473.     sess.write(resp)
  474.     if verbose:
  475.         print resp
  476.     txt = my_read_some(sess)
  477.     while txt:
  478.         if verbose:
  479.             sys.stdout.write(txt)
  480.         if 'Login incorrect' in txt or interrupted:
  481.             print redraspy, 'Fatal: Authentication failed'
  482.             sess.close()
  483.             return 1
  484.         txt = my_read_some(sess)
  485.     sess.close()
  486.     return 0
  487.  
  488. def auth_session():
  489.     "Do telnet to authhost for opie authentication"
  490.     telnetcmd = cfg.get('global', 'telnetcmd')
  491.     # Handle OpenWRT specially; it's telnetlib is broken
  492.     if telnetcmd == '/usr/bin/telnet' and not iswhiterussian:
  493.         return auth_session_telnetlib()
  494.     import popen2, signal
  495.     #global verbose
  496.     host = cfg.get('global', 'authhost')
  497.     if not quiet:
  498.         print greenraspy, 'Trying auth session to %s' % host
  499.     auth = 0
  500.     authretry = cfg.get('global', 'authretry')
  501.     while not authretry or auth < authretry:
  502.         sess = popen2.Popen3("%s %s" % \
  503.                (telnetcmd, host), True)
  504.         # Allow for nameserver or refused conn. to show
  505.         if sess.poll() == -1:
  506.             time.sleep(0.5)
  507.         #print sess.__dict__
  508.         if sess.poll() == -1:
  509.             break
  510.         auth += 1
  511.         print yellowraspy, sess.childerr.read()
  512.         if authretry and auth < authretry:
  513.             my_sleep(int(cfg.get('global', 'failholdoff')))
  514.         else:
  515.             print redraspy, "Fatal: Can't connect to %s" % host
  516.             return 1
  517.     fromfd = sess.fromchild.fileno()
  518.     txt, tout = read_until(fromfd, ('login: ', ), 32) #, sys.stdout.fileno())
  519.     if verbose:
  520.         print txt
  521.     if not ('login: ' in txt) or interrupted:
  522.         exitstat = sess.poll()
  523.         exiterr = os.read(sess.childerr.fileno(), 4096)
  524.         print redraspy, 'Fatal: Login prompt not found (%i/%i)\n %s' % \
  525.             (tout, exitstat, exiterr)
  526.         if exitstat == -1:
  527.             os.kill(sess.pid, signal.SIGHUP)
  528.         return 1
  529.     #time.sleep(0.5)
  530.     os.write(sess.tochild.fileno(), cfg.get('global', 'logname') + '\n')
  531.     #print cfg.get('global', 'logname')
  532.     txt, tout = read_until(fromfd, ('Response: ', ), 16, sys.stdout.fileno())
  533.     if verbose:
  534.         print txt
  535.     resp = get_opie_key(txt.split('\n')[1])
  536.     os.write(sess.tochild.fileno(), resp + '\n')
  537.     #if verbose:
  538.     #    print resp
  539.     exitstat = sess.poll()
  540.     while exitstat == -1:
  541.         txt = os.read(fromfd, 1024)
  542.         if verbose:
  543.             sys.stdout.write(txt)
  544.         if 'Login incorrect' in txt or interrupted:
  545.             print redraspy, 'Fatal: Authentication failed'
  546.             if sess.poll() == -1:
  547.                 os.kill(sess.pid, signal.SIGHUP)
  548.             return 1
  549.         exitstat = sess.poll()
  550.     if exitstat != 0 and verbose:
  551.         print yellowraspy, 'Info: telnet exit status %i' % exitstat
  552.     return 0
  553.  
  554.  
  555. def ipsec_parse(lines, conn, tag):
  556.     "Find tag in conn, also understanding also= include syntax"
  557.     inconn = 0
  558.     also = []
  559.     reconn = re.compile('[ \t]*conn[ \t]*([^ \t]*)[ \t]*$')
  560.     retag  = re.compile('[ \t]+%s[ \t]*=[ \t]*(.*)$' % tag)
  561.     realso = re.compile('[ \t]+also[ \t]*=[ \t]*(.*)$')
  562.     for line in lines:
  563.         cmatch = reconn.match(line)
  564.         if cmatch:
  565.             if cmatch.group(1) == conn:
  566.                 inconn = 1
  567.             else:
  568.                 inconn = 0
  569.             continue
  570.         if not inconn:
  571.             continue
  572.         tmatch = retag.match(line)
  573.         #print line, tmatch
  574.         if tmatch:
  575.             return tmatch.group(1)
  576.         amatch = realso.match(line)
  577.         if amatch:
  578.             also.append(amatch.group(1))
  579.     # If we got here, we need to scan the also= sections
  580.     for sect in also:
  581.         ret = ipsec_parse(lines, sect, tag)
  582.         if ret:
  583.             return ret
  584.     # Not found, error
  585.     return None
  586.  
  587. def extract_route(connname):
  588.     "Fill find route in ipsec.conf, assuming we are left"
  589.     ipsecconf = os.popen(cfg.get('global', 'ipsecconfread'), 'r').readlines()
  590.     # Strip CRLF
  591.     strip = re.compile(r'[\r\n]*$')
  592.     #ipsecconf = map(lambda x: strip.sub('', x), ipsecconf)
  593.     ipsecconf = [strip.sub('', line) for line in ipsecconf]
  594.     # Strip comments
  595.     strip = re.compile(r'#.*$')
  596.     #ipsecconf = map(lambda x: strip.sub('', x), ipsecconf)
  597.     ipsecconf = [strip.sub('', line) for line in ipsecconf]
  598.     #print ipsecconf
  599.     # Look for route
  600.     route = ipsec_parse(ipsecconf, connname, 'rightsubnet')
  601.     #vip = ipsec_parse(ipsecconf, connname, 'leftsubnet')
  602.     if verbose:
  603.         print greenraspy, "Info for conn %s: route %s" % (connname, route)
  604.     return route
  605.  
  606. def call_debug_cmd(cmd):
  607.     "Call external binary and return output. (Used for debugging.)"
  608.     fdes = os.popen(cmd);
  609.     output = my_read(fdes)
  610.     err = fdes.close()
  611.     print output
  612.    
  613. # IPsec (free/openswan) connection
  614. class IPsecConn:
  615.     """Class that abstracts the free/openswan way of making
  616.        IPsec connections, an object of this type represents
  617.        one IPsec tunnel."""
  618.    
  619.     def __init__(self, config, section):
  620.         """Setup data structures from config dict, we don't access
  621.            the cfg global variable"""
  622.         self.name = section
  623.         self.route = extract_route(section)
  624.         self.oldroute = None
  625.         if config.has_option(section, 'route'):
  626.             rte = config.get(section, 'route')
  627.             if not self.route:
  628.                 self.route = rte
  629.             elif rte != self.route:
  630.                 self.oldroute = self.route
  631.                 self.route = rte
  632.         if not self.route:
  633.             print redraspy, 'Connection %s has no route' % self.name
  634.             sys.exit(1)
  635.         if config.has_option(section, 'mtu'):
  636.             self.mtu = int(config.get(section, 'mtu'))
  637.         else:
  638.             self.mtu = 1428
  639.         if config.has_option(section, 'pinghost'):
  640.             self.pinghost = config.get(section, 'pinghost')
  641.             self.pingtimeout = int(config.get('global', 'pingtimeout'))
  642.             self.pingstatus = None
  643.         else:
  644.             self.pinghost = ''
  645.             self.pingtimeout = 0
  646.             self.pingstatus = 'not configured'
  647.         self.state = 'unknown'
  648.         self.connstarted = 0
  649.         self.connstopped = 0
  650.         if config.has_option('global', 'virtip'):
  651.             self.src = 'src %s' % config.get('global', 'virtip')
  652.         else:
  653.             self.src = ''
  654.         self.netdev = config.get('global', 'netdev')
  655.         self.gateway = config.get('global', 'gateway')
  656.         self.ipseccmd = config.get('global', 'ipseccmd')
  657.         self.ipcmd = config.get('global', 'ipcmd')
  658.         if config.has_option(section, 'desc'):
  659.             self.desc = "%s(%s)" % (section, config.get(section, 'desc'))
  660.         else:
  661.             self.desc = "%s(%s)" % (section, self.route)
  662.         # Check for unknown options!
  663.         for opt in cfg.options(section):
  664.             if not opt in conn_kwds:
  665.                 print yellowraspy, "Unknown option %s in section %s" % (opt, section)
  666.  
  667.  
  668.     def ipsec_up(self):
  669.         "up this ipsec connection"
  670.         global outvfn
  671.         fdes = os.popen("%s --up %s" % \
  672.                (self.ipseccmd, self.name))
  673.         txt, tout = read_until(fdes.fileno(),
  674.                                ("IPsec SA established",), 16, outvfn)
  675.         if tout < 0:
  676.             if verbose:
  677.                 print redraspy, 'Connection %s failed' % self.desc
  678.             return 1
  679.         fdes.close()
  680.         #time.sleep(1)
  681.         return 0
  682.  
  683.     def ipsec_down_unroute(self, what):
  684.         "down or unroute (specified by what) ipsec connection"
  685.         fdes = os.popen("%s --%s %s" % \
  686.                (self.ipseccmd, what, self.name))
  687.         txt = my_read(fdes)
  688.         if fdes.close():
  689.             print txt
  690.  
  691.     def route_up(self, what):
  692.         "add/replace (specified by what) route for connection"
  693.         #if not self.src:
  694.         #       return 0
  695.         rte = self.route
  696.         if rte == 'AUTO':
  697.             return 0
  698.         fdes = os.popen("%s route %s %s %s dev %s %s mtu %i" % \
  699.                (self.ipcmd, what,
  700.                 rte, self.gateway, self.netdev, self.src, self.mtu))
  701.         txt = my_read(fdes)
  702.         if fdes.close():
  703.             if what == 'add' or what == 'replace' or verbose:
  704.                 print yellowraspy, '%s route for conn %s failed' % \
  705.                     (what, self.desc)
  706.                 print " %s route %s %s %s dev %s %s mtu %i" % \
  707.                     (self.ipcmd, what, rte, self.gateway,
  708.                      self.netdev, self.src, self.mtu)
  709.                 print txt
  710.             return 1
  711.         return 0
  712.  
  713.     def route_down(self, route = None):
  714.         "delete route for connection"
  715.         #if not self.src:
  716.         #       return 0
  717.         if not route:
  718.             route = self.route
  719.         if route == 'AUTO':
  720.             return 0
  721.         fdes = os.popen("%s route delete %s dev %s" % \
  722.             (self.ipcmd, route, self.netdev))
  723.         txt = my_read(fdes)
  724.         #if verbose:
  725.         #    print txt
  726.         return 0
  727.  
  728.     def route_status(self):
  729.         "Check routing table for entry with this connection"
  730.         if self.route == 'AUTO':
  731.             return 0
  732.         fdes = os.popen("%s route show %s" % \
  733.                (self.ipcmd, self.route))
  734.         txt = my_read(fdes)
  735.         if txt and not quiet:
  736.             print txt,
  737.         fdes.close()
  738.         if not txt:
  739.             return 3
  740.         return 0
  741.    
  742.     def ipsec_status(self):
  743.         "Return status of ipsec connection"
  744.         fdes = os.popen("%s --status" % self.ipseccmd)
  745.         txt = fdes.readlines()
  746.         if fdes.close():
  747.             print redraspy, 'Could not check connection'
  748.             return 4
  749.         rgx = re.compile(r'"%s" esp' % self.name)
  750.         for line in txt:
  751.             if rgx.search(line):
  752.                 print line,
  753.                 self.state = 'up'
  754.                 return 0
  755.         return 3
  756.  
  757.     def doping(self):
  758.         """ping with forced timeout (no -w option for ping)
  759.            returns 0 on success, anything else on failure"""
  760.         import subprocess
  761.         process = subprocess.Popen(["/bin/ping", "-c", "1", "%s" % self.pinghost],
  762.                 bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
  763.         time.sleep(0.1)
  764.         rc = process.poll()
  765.         if rc != None:
  766.             return rc
  767.         for iter in range(0, self.pingtimeout):
  768.             time.sleep(1)
  769.             rc = process.poll()
  770.             if rc != None:
  771.                 return rc
  772.         os.kill(process.pid, signal.SIGTERM)
  773.         return process.wait()
  774.  
  775.     def ping(self):
  776.         """ping pinghost, ret -1 if not configured,
  777.            0 if failed, 1 is successful"""
  778.         if self.pinghost:
  779.             if self.doping() != 0:
  780.                 if not quiet:
  781.                     print yellowraspy, 'ping %s failed %s' % \
  782.                         (self.pinghost, time.strftime(timefmt))
  783.                 self.pingstatus = 'failed'
  784.                 return 0
  785.             else:
  786.                 self.pingstatus = 'succeeded'
  787.                 return 1
  788.         else:
  789.             return -1
  790.    
  791.     def up(self):
  792.         """Bring connection up and add route, return 0 for success,
  793.            tear down again if not entirely successful."""
  794.         ret = self.ipsec_up()
  795.         if ret:
  796.             return ret
  797.         action = 'replace'
  798.         #if self.oldroute and not self.route == 'AUTO':
  799.         if self.route != 'AUTO':
  800.             #call_debug_cmd('%s route show' % self.ipcmd)
  801.             #call_debug_cmd('%s addr show'  % self.ipcmd)
  802.             if self.oldroute:
  803.                 ret = self.route_down(self.oldroute)
  804.             else:    
  805.                 ret = self.route_down(self.route)
  806.             action = 'add'
  807.         ret = self.route_up(action)
  808.         # if 0 and ret:
  809.         if ret:
  810.             self.ipsec_down_unroute('down')
  811.             self.ipsec_down_unroute('unroute')
  812.             return ret
  813.         self.state = 'up'
  814.         self.connstarted = time.time()
  815.         # return 0 ## ret
  816.         return ret
  817.        
  818.     def down(self):
  819.         "Bring connection down, delete route, no return code"
  820.         self.ipsec_down_unroute('down')
  821.         self.route_down()
  822.         self.ipsec_down_unroute('unroute')
  823.         self.state = 'down'
  824.         self.connstopped = time.time()
  825.    
  826.     def status(self):
  827.         "Check for conn status, return 0 for success"
  828.         if not quiet:
  829.             print greenraspy, 'Conn status %s: %s,' % \
  830.                 (self.desc, self.state),
  831.             if self.connstarted:
  832.                 print "started %s\n\t" % \
  833.                     time.strftime(timefmt, time.localtime(self.connstarted)),
  834.             if self.connstopped:
  835.                 print "stopped %s\n\t" % \
  836.                     time.strftime(timefmt, time.localtime(self.connstopped)),
  837.             print "ping: %s" % self.pingstatus
  838.         self.route_status()
  839.         return self.ipsec_status()
  840.  
  841.  
  842. # Global functions again, right encapsulation pending ...
  843. def add_del_ip(what):
  844.     "Add/Delete (specified by what) virtual IP address to net interface"
  845.     if cfg.has_option('global', 'virtip'):
  846.         ipaddr = cfg.get('global', 'virtip')
  847.     else:
  848.         return
  849.     netdev = cfg.get('global', 'netdev')
  850.     fdes = os.popen("%s addr %s %s dev %s" % \
  851.         (cfg.get('global', 'ipcmd'), what, ipaddr, netdev))
  852.     txt = my_read(fdes)
  853.     if fdes.close() and not quiet:
  854.         print yellowraspy, '%s addr %s %s dev %s failed' % \
  855.             (cfg.get('global', 'ipcmd'), what, ipaddr, netdev)
  856.         print txt
  857.  
  858. def setup_connections(config):
  859.     "Make and return a list of connections from config sections"
  860.     conns = []
  861.     for conn in cfg.sections():
  862.         if conn == 'global':
  863.             continue
  864.         conns.append(IPsecConn(config, conn))
  865.     return conns
  866.  
  867. def check_start_ipsec(conn):
  868.     "Check if IPsec is running, try to start if not, bail out this fails"
  869.     rgx = re.compile(r'"%s"' % conn.name)
  870.     outfd = os.popen('%s --status' % cfg.get('global', 'ipseccmd'), 'r')
  871.     output = my_read(outfd)
  872.     err = outfd.close()
  873.     connknown = rgx.search(output)
  874.     if err or not connknown:
  875.         if err:        
  876.             sta = 'start'
  877.             if not quiet:
  878.                 print greenraspy, 'Start IPsec'
  879.         else:
  880.             sta = 'restart'
  881.             if not quiet:
  882.                 print greenraspy, 'Restart IPsec'
  883.         # TODO: Disconnect from controlling terminal
  884.         outfd = os.popen('%s %s' % (cfg.get('global', 'ipsecinit'), sta), 'r')
  885.         output = my_read(outfd)
  886.         err = outfd.close()
  887.         if err:
  888.             print redraspy, 'Fatal: Could not start IPsec'
  889.             print output
  890.             sys.exit(7)
  891.         my_sleep(5)
  892.     if verbose:
  893.         print greenraspy, 'IPsec running'
  894.  
  895. def up_connections(conns):
  896.     "Start all IPsec connections from list conns"
  897.     check_start_ipsec(conns[0])
  898.     add_del_ip('add')
  899.     upconns = []
  900.     for conn in conns:
  901.         if not quiet:
  902.             print greenraspy, 'start ipsec connection %s' % conn.desc
  903.         ret = conn.up()
  904.         if ret or interrupted:
  905.             if not quiet:
  906.                 print redraspy, 'Connection %s failed, tear down' % conn.desc
  907.             down_connections(upconns)
  908.             add_del_ip('del')
  909.             return 1
  910.         else:
  911.             upconns.append(conn)
  912.     if not quiet:
  913.         print greenraspy, 'ipsec connections up %s' % \
  914.             time.strftime(timefmt)
  915.     return 0
  916.  
  917. def do_pings(conns):
  918.     "Ping all connections (in conns), returns 1 if _all_ fail"
  919.     succ = 0
  920.     pings = 0
  921.     ival = int(cfg.get('global', 'pinginterval'))
  922.     for conn in conns:
  923.         if reconnect or interrupted:
  924.             return 0
  925.         ret = conn.ping()
  926.         if ret == -1:
  927.             # Not configured, try next connection
  928.             continue
  929.         else:
  930.             # Count successful pings
  931.             succ += ret
  932.         # Count tried pings
  933.         pings += 1
  934.         my_sleep(ival)
  935.     if not pings:
  936.         # If no ping has been tried, we need to sleep anyway
  937.         my_sleep(ival)
  938.         return 0
  939.     if not succ:
  940.         # We have tried some pings, but none succeeded
  941.         return pings
  942.     return 0
  943.  
  944. def down_connections(conns):
  945.     "Stop all IPsec connections in conns"
  946.     for conn in conns:
  947.         if not quiet:
  948.             print greenraspy, 'stop ipsec connection %s' % conn.desc
  949.         conn.down()
  950.     add_del_ip('del')
  951.     if not quiet:
  952.         print greenraspy, 'ipsec connections stopped %s' % \
  953.             time.strftime(timefmt)
  954.  
  955. def up_all(conns):
  956.     """Up all connections in conns. This is the main loop for
  957.        ras.py script."""
  958.     global interrupted, reconnect, connections, alreadyauth
  959.     notconn = 0
  960.     # We need it anyway, even if we are connected already
  961.     if cfg.has_option('global', 'authhost'):
  962.         input_passphrase()
  963.     start = time.time()
  964.     if daemon:
  965.         daemonize()
  966.     if not quiet:
  967.         print green + "ras.py started %s" % time.strftime(timefmt) + norm
  968.     while not interrupted and ( \
  969.             not cfg.has_option('global', 'maxconnect') \
  970.             or time.time() < start + int(cfg.get('global', 'maxconnect'))):
  971.         # Skip auth session if tunnels exist already
  972.         if notconn or conns[0].status():
  973.             if cfg.has_option('global', 'authhost'):
  974.                 if alreadyauth == 0 and auth_session():
  975.                     break
  976.                 else:
  977.                     alreadyauth = 0
  978.             if up_connections(conns):
  979.                 interrupted = 1
  980.                 break
  981.         else:
  982.             if not quiet:
  983.                 print greenraspy, 'Connections already up'
  984.         # Ping loop
  985.         err = 0
  986.         while err < int(cfg.get('global', 'pingfail')) \
  987.                 and not interrupted and not reconnect:
  988.             ret = do_pings(conns)
  989.             if ret:
  990.                 err += ret
  991.             else:
  992.                 err = 0
  993.         # Pings failed      
  994.         down_connections(conns)
  995.         notconn = 1
  996.         if reconnect:
  997.             read_config(configfile, 1)
  998.             conns = setup_connections(cfg)
  999.             # FIXME: No point in passing conns here if we access the global
  1000.             connections = conns
  1001.             os.utime(lockfilename, None)
  1002.             reconnect = 0
  1003.         my_sleep(int(cfg.get('global', 'failholdoff')))
  1004.  
  1005.     if not quiet:
  1006.         print green + "ras.py stopped %s" % time.strftime(timefmt) + norm
  1007.     if daemon:
  1008.         del_lockfile()
  1009.     return interrupted
  1010.  
  1011. def down_all(conns):
  1012.     "Down all connections (from list conns)"
  1013.     # We could add logic here to kill the right PID ...
  1014.     down_connections(conns)
  1015.     return 0
  1016.  
  1017. def status_all(conns):
  1018.     "Query all connection objects (in conns) for status"
  1019.     ret = 0
  1020.     for conn in conns:
  1021.         retconn = conn.status()
  1022.         if retconn:
  1023.             ret = retconn
  1024.     if not quiet:      
  1025.         if ret:
  1026.             print redraspy, 'down'
  1027.         else:
  1028.             print greenraspy, 'up'
  1029.     return ret
  1030.  
  1031. def print_logfile(beginmarker, endmarker = '^@$%&#', beginmarker2 = '^@$%&#'):
  1032.     """Outputs the section from the logfile since the last
  1033.        beginmarker, avoiding endmarker."""
  1034.     logf = open(logfilename)
  1035.     logl = logf.readlines()
  1036.     endlog = ''
  1037.     for line in logl:
  1038.         if beginmarker in line:
  1039.             endlog = ''
  1040.         elif beginmarker2 in line:
  1041.             endlog = ''
  1042.         elif endmarker in line:
  1043.             pass
  1044.         else:
  1045.             endlog += line
  1046.     print endlog
  1047.     logf.close()
  1048.     return endlog
  1049.  
  1050. def file_newer(file1, file2):
  1051.     "Returns a number > 0 if file1 is newer (mtime) than file2 else <= 0"
  1052.     stat1 = os.stat(file1)
  1053.     stat2 = os.stat(file2)
  1054.     return stat1.st_mtime - stat2.st_mtime
  1055.  
  1056. def usage():
  1057.     "Tell the user how to use this script"
  1058.     print bold + "Usage: ras.py [options] action [cfg file]" + norm
  1059.     print bold + " options: -v (verbose), -q (quiet), \n\t-d (daemonize)," \
  1060.         + " -D (Direct) -a (alreadyauthenticated)"
  1061.     print " action: (start|up|status|help|stop|down|"
  1062.     print "\t try-restart|condrestart|restart|reload|force-reload|probe)" \
  1063.         + norm              
  1064.     return 2
  1065.  
  1066. def print_help():
  1067.     "Description of what this script does and how it's configured"
  1068.     print __revision__
  1069.     print bold + "This script connects you to an IPsec RAS service.\n" + norm
  1070.    
  1071.     print "Assumptions: The FreeS/WAN or openswan /etc/ipsec.conf and"
  1072.     print "/etc/ipsec.secrets config files are configured correctly and"
  1073.     print "contain the descriptions of the connections and the needed secrets."
  1074.     print "The connections should be configured to be added, NOT started"
  1075.     print "on /etc/init.d/ipsec start, i.e. auto=add in ipsec.conf."
  1076.     print "The script assumes that we are the left side of the connection."
  1077.     print " Note: It might work for people with auto=start, but I haven't"
  1078.     print " tested this. Nor have I tested without private virtual IP.\n"
  1079.  
  1080.     print "This script should be executed setuid root."
  1081.     print "It is configured by /etc/ras.config (or another cfg file specified)"
  1082.     print "which has the .ini file syntax as described below.\n"
  1083.  
  1084.     print "The [global] section has the following keys (all optional!):"
  1085.     print " pinginterval: How many seconds to wait between pings (default: 20)."
  1086.     print " pingtimeout: How long (s) to wait for our peer to answer (default: 4)."
  1087.     print " pingfail: How many subsequent pings need to fail to make the script"
  1088.     print "\tassume that the connectivity is down (default: 4)."
  1089.     print " failholdoff: How long to sleep before trying to reconnect (def: 150)."
  1090.     print " maxconnect: How many seconds we may be connected in total"
  1091.     print "\t(default: empty => infinite)."
  1092.     print " virtip: virtual IP of your end of the IPsec connection "
  1093.     print "\t(default: empty => no virtual IP is used)."
  1094.     print " netdev: name of the NIC that's used to send out IPsec traffic"
  1095.     print "\t(default: empty => ipsec0 for KLIPS, netdev of defaultroute otherwise)."
  1096.     print " gateway: set to an empty string or 'via IP' to explicitly set routing GW"
  1097.     print "\t(default: if no netdev is specified take defroute gw, otherwise '')."
  1098.     print " authhost: Machine to telnet to for the opie auth session"
  1099.     print "\t(default: none => skip opie auth session)."
  1100.     print " authretry: How many times to try auth session if telnet times out."
  1101.     print "\t0 means infinity (default: 3)."
  1102.     print " logname: username to log in as into authhost"
  1103.     print "\t(default: user running the script).\n"
  1104.     print "Expert options are:"
  1105.     print " ipseccmd: Let's you override the command to control ipsec tunnels"
  1106.     print "\twith --up/--down/--status conn.name (default: /usr/sbin/ipsec auto)"
  1107.     print " ipsecinit: Let's you the command to call the ipsec init script"
  1108.     print "\t(default: /etc/init.d/ipsec)"
  1109.     print " ipsecconfread: Let's you override how to read FreeSWAN's ipsec.conf"
  1110.     print "\t(default: cat /etc/ipsec.conf)"
  1111.     print " ipcmd: The iproute2 command (default: /sbin/ip)"
  1112.     print " routeraw: How to get raw routing table (default: cat /proc/net/route)"
  1113.     print " netdevexist: How to determine existence of a network device"
  1114.     print "\t(default: test -e /proc/sys/net/ipv4/conf/)"
  1115.     print "The most common use for these expoert options is to prefix commands with"
  1116.     print "ssh -t to remote control another machine who handles the tunnels. This"
  1117.     print "can be more easily achieved by specifying"
  1118.     print " cmdprefix: Let's you prefix the commands ipseccmd, ipsecinit,"
  1119.     print "\tipsecconfread, ipcmd, routeraw, and netdevexist."
  1120.     print "\tNote that individual settings are not prefixed automatically.\n"
  1121.  
  1122.     print "The global section is mandatory, but it can be empty if the"
  1123.     print "defaults all work for you.\n"
  1124.  
  1125.     print "There are additional sections where the section name does specify"
  1126.     print "the name of the IPsec connection as given in ipsec.conf."
  1127.     print "The following entries per section are supported:"
  1128.     print "route: The route that should be set, should be equal to the"
  1129.     print "\tother side's subnet, e.g. 10.0.0.0/8 (optional)."
  1130.     print "\tThe route is taken from rightsubnet in ipsec.conf, but can be"
  1131.     print "\toverriden here."
  1132.     print "\tThe special word 'AUTO' here prevents ras.py to affect your routes"
  1133.     print "\tbut assumes that the ipcseccmd has taken care of it."
  1134.     print "pinghost: Machine to periodically send ping commands to (optional).\n"
  1135.     print "mtu: MTU for the route (optional, default 1428).\n"
  1136.     print "desc: Description of the connection (optional).\n"
  1137.  
  1138.     print "Multiple connections can be specified this way.\n"
  1139.  
  1140.     print "The script is verbose about success or failure, the return codes"
  1141.     print "roughly match the ones from LSB init scripts.\n"
  1142.  
  1143.     print "The program can be run in daemon mode, in which case it forks"
  1144.     print "a background process; subsequent calls of this program will connect"
  1145.     print "to the daemon by sending it signals."
  1146.     print "The signals SIGTERM/SIGINT/SIGQUIT terminate the process/daemon,"
  1147.     print "the signal  SIGUSR1 makes it log status information, and"
  1148.     print "the signal  SIGUSR2 makes it close the connections, reread the config"
  1149.     print "file and reconnect. SIGHUP reacts like SIGUSR2 in daemon mode, and"
  1150.     print "like SIGTERM in foreground mode.\n"
  1151.  
  1152. def sighandler(sig, frame):
  1153.     "Signal handler invoked to down connections and stop the script"
  1154.     global interrupted
  1155.     #global yellowraspy, quiet
  1156.     interrupted = sig + 128
  1157.     if not quiet:
  1158.         print yellowraspy, 'Signal %i caught %s, bailing out' % (sig, frame)
  1159.  
  1160.  
  1161. def sighandler_status(sig, frame):
  1162.     "Signal handler to create status log entry"
  1163.     #global connections, BEGINRASSTATUS, ENDRASSTATUS
  1164.     print BEGINRASSTATUS
  1165.     status_all(connections)
  1166.     print ENDRASSTATUS
  1167.  
  1168. def sighandler_reconnect(sig, frame):
  1169.     "Signal handler to reconnect connections"
  1170.     global reconnect
  1171.     #global yellowraspy, quiet
  1172.     reconnect = 1
  1173.     if not quiet:
  1174.         print yellowraspy, 'Signal %i caught %s, reconnecting' % (sig, frame)
  1175.  
  1176. def setup_signal_handlers():
  1177.     "setup signal handlers"
  1178.     signal.signal(signal.SIGTERM, sighandler)
  1179.     signal.signal(signal.SIGINT , sighandler)
  1180.     signal.signal(signal.SIGQUIT, sighandler)
  1181.     signal.signal(signal.SIGHUP , sighandler)   # override in daemon mode
  1182.     signal.signal(signal.SIGUSR1, sighandler_status)
  1183.     signal.signal(signal.SIGUSR2, sighandler_reconnect)
  1184.  
  1185. def parse_args(argv):
  1186.     "Parse command line args"
  1187.     import getopt
  1188.     global daemon, quiet, verbose, outqfn, outvfn, alreadyauth, configfile
  1189.  
  1190.     # daemon running?
  1191.     lpid = check_lockfile()
  1192.     # options
  1193.     try:
  1194.         optlist, args = getopt.gnu_getopt(argv, 'vqdaDh', ('help',))
  1195.     except getopt.GetoptError, exc:
  1196.         print exc
  1197.         sys.exit(usage())
  1198.     for opt in optlist:    
  1199.         if opt[0] == '-q':
  1200.             quiet = 1
  1201.             outqfn = None
  1202.             continue
  1203.         if opt[0] == '-v':
  1204.             verbose = 1
  1205.             quiet = 0
  1206.             outqfn = sys.stdout.fileno()
  1207.             outvfn = sys.stdout.fileno()
  1208.             continue
  1209.         if opt[0] == '-d':
  1210.             daemon = 1
  1211.             continue
  1212.         if opt[0] == '-h' or opt[0] == '--help':
  1213.             print_help()
  1214.             sys.exit(usage())
  1215.             #continue
  1216.         if opt[0] == '-a':
  1217.             alreadyauth = 1    # override
  1218.             continue
  1219.         if opt[0] == '-D':
  1220.             lpid = 0    # override
  1221.             continue
  1222.     if len(args) < 2 or len(args) > 3:
  1223.         sys.exit(usage())
  1224.    
  1225.     # Help
  1226.     if args[1] == 'help':
  1227.         print_help()
  1228.         sys.exit(usage())
  1229.    
  1230.     if len(args) > 2:
  1231.         configfile = args[2]
  1232.    
  1233.     return args[1], lpid
  1234.  
  1235. def do_control_daemon(action, lpid):
  1236.     "Frontend to control ras.py that's running in daemon mode"
  1237.    
  1238.     global daemon
  1239.     if action in ('start', 'up'):
  1240.         print redraspy, "ras.py already running (pid %i)" % lpid
  1241.         return 0
  1242.    
  1243.     elif action == 'status':
  1244.         try:    
  1245.             os.kill(lpid, signal.SIGUSR1)
  1246.         except OSError, err:
  1247.             print yellowraspy, str(err)
  1248.             return status_all(connections)
  1249.         my_sleep(1)
  1250.         log = print_logfile(BEGINRASSTATUS, ENDRASSTATUS, NEWRASSESSION)
  1251.         if (greenraspy + ' up') in log:
  1252.             return 0
  1253.         else:
  1254.             return 1
  1255.    
  1256.     elif action in ('stop', 'down'):
  1257.         try:
  1258.             os.kill(lpid, signal.SIGTERM)
  1259.         except OSError, err:
  1260.             print redraspy, str(err)
  1261.             return down_all(connections)
  1262.         my_sleep(1)
  1263.         print_logfile(NEWRASSESSION)
  1264.         return 0
  1265.    
  1266.     elif action in ('restart', 'try-restart', 'condrestart'):
  1267.         try:
  1268.             os.kill(lpid, signal.SIGTERM)
  1269.         except OSError, err:
  1270.             print redraspy, str(err)
  1271.             return 4
  1272.         my_sleep(1)
  1273.         daemon = 1
  1274.         return do_direct_operation('start')
  1275.  
  1276.     elif action in ('reload', 'force-reload'):
  1277.         try:
  1278.             os.kill(lpid, signal.SIGHUP)
  1279.         except OSError, err:
  1280.             print redraspy, str(err)
  1281.             return 4
  1282.         return 0
  1283.  
  1284.     elif action == 'probe':
  1285.         if file_newer(configfile, lockfilename) > 0:
  1286.             print 'reload'
  1287.             return 0
  1288.         else:
  1289.             return 1
  1290.  
  1291.     else:
  1292.         return -1
  1293.  
  1294. def do_direct_operation(action):
  1295.     "start, stop, status in foreground mode"
  1296.    
  1297.     if action in ('start', 'up', 'restart', 'try-restart', 'condrestart'):
  1298.         # FIXME: This is fuzzy ...
  1299.         if os.getuid() != 0 and not cfg.has_option('global', 'cmdprefix'):
  1300.             print redraspy, 'Need to be root'
  1301.             return 4
  1302.         else:
  1303.             if action in ('try-restart', 'condrestart', 'restart'):
  1304.                 ret = status_all(connections)
  1305.                 if not ret:
  1306.                     print redraspy, \
  1307.                         "Can't stop running ras.py that's not in daemon mode"
  1308.                     return 4
  1309.                 elif action != 'restart':
  1310.                     return 0
  1311.                 else:
  1312.                     down_all(connections)
  1313.             return up_all(connections)
  1314.    
  1315.     elif action == 'status':
  1316.         return status_all(connections)
  1317.    
  1318.     elif action in ('stop', 'down'):
  1319.         return down_all(connections)
  1320.  
  1321.     elif action in ('reload', 'force-reload', 'probe'):
  1322.         print redraspy, 'Not running as daemon'
  1323.         return 7
  1324.  
  1325.     else:
  1326.         return -1
  1327.  
  1328.  
  1329. # main routine
  1330. def main(argv):
  1331.     "Main program ras.py"
  1332.    
  1333.     #global daemon, quiet, verbose, outqfn, outvfn
  1334.     global connections, cfg, configfile
  1335.    
  1336.     sanitize_env()
  1337.     setup_signal_handlers()
  1338.    
  1339.     action, lpid = parse_args(argv)
  1340.    
  1341.     # connections
  1342.     check_whiterussian()
  1343.     read_config(configfile)
  1344.     check_prereq()
  1345.     connections = setup_connections(cfg)
  1346.    
  1347.     # action
  1348.     if lpid:
  1349.         ret = do_control_daemon(action, lpid)
  1350.     else:
  1351.         ret = do_direct_operation(action)
  1352.    
  1353.     if ret < 0:
  1354.         return usage()
  1355.     else:
  1356.         return ret
  1357.  
  1358. # run main if called standalone
  1359. if __name__ == "__main__":
  1360.     sys.exit(main(sys.argv))