Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/env python
- """Script to connect your machine to SUSE IPsec RAS service"
- See print_help() below for a longer description."""
- #__revision__ = "$Id: ras.py,v 1.53 2005/07/19 20:15:41 garloff Exp $"
- __revision__ = "@hg_rev:173@"
- # (c) Kurt Garloff <garloff@suse.de>, 2005-04-17, Python license
- #
- # TODO:
- # * Track status in config file specific lock/logfiles, so more than one
- # instance can run.
- # * PyQt GUI.
- # * Allow doing telnet via ssh as well.
- import sys, os, time, re, signal, errno
- import ConfigParser
- # Colorize
- esc = chr(27)
- red = esc + '[0;31m'
- green = esc + '[0;32m'
- yellow = esc + '[0;33m'
- bold = esc + '[0;1m'
- norm = esc + '[0;0m'
- redraspy = red + 'ras.py:' + norm
- greenraspy = green + 'ras.py:' + norm
- yellowraspy = yellow + 'ras.py:' + norm
- # Global Vars
- passphrase = ''
- cfg = ConfigParser.RawConfigParser()
- configfile = '/etc/ras.config'
- timefmt = "%a %Y-%m-%d %H:%M:%S"
- interrupted = 0; reconnect = 0
- verbose = 0; quiet = 0
- outvfn = None; outqfn = sys.stdout.fileno()
- daemon = 0
- alreadyauth = 0
- connections = []
- iswhiterussian = False
- # Global vars for the daemon
- lockfilename = '/var/run/ras.pid'
- logfilename = '/var/log/ras-messages'
- NEWRASSESSION = '*** NEW RAS SESSION ***'
- BEGINRASSTATUS = '*** BEGIN RAS STATUS ***'
- ENDRASSTATUS = '*** END RAS STATUS ***'
- global_kwds = ('pinginterval', 'pingtimeout', 'pingfail', 'failholdoff',
- 'maxconnect', 'authhost', 'authretry', 'logname',
- 'virtip', 'netdev', 'gateway',
- 'ipseccmd', 'ipsecinit', 'ipsecconfread',
- 'ipcmd', 'telnetcmd', 'routeraw', 'netdevexist',
- 'cmdprefix')
- conn_kwds = ('route', 'mtu', 'pinghost', 'desc')
- def create_lockfile():
- "Creates lock file and writes pid to it"
- lockfd = os.open(lockfilename, os.O_WRONLY|os.O_CREAT|os.O_TRUNC)
- # No O_EXCL needed, the location is not world writable
- if not lockfd:
- print redraspy, "Can't create lock file %s" % lockfilename
- sys.exit(1)
- os.write(lockfd, "%i\n" % os.getpid())
- def check_lockfile():
- "Checks for lock file and returns pid if present and if process exists"
- try:
- lfile = open(lockfilename)
- except:
- return 0
- txt = lfile.readline()
- dpid = int(txt)
- lfile.close()
- if not os.access('/proc/%i' % dpid, os.R_OK):
- print yellowraspy, "removing stale lockfile for pid %i" % dpid
- os.remove(lockfilename)
- dpid = 0
- return dpid
- def del_lockfile():
- "Removes lock file"
- dpid = check_lockfile()
- if not dpid:
- return
- if dpid != os.getpid():
- print yellowraspy, "Won't remove lock file for pid %i, self %i" % \
- (dpid, os.getpid())
- os.remove(lockfilename)
- def daemonize():
- "Fork and exit parent. Child closes fds and logs to /var/log/ras-messages"
- if os.fork():
- os._exit(0)
- create_lockfile()
- os.setsid()
- #os.umask(077)
- for i in range(3):
- try:
- os.close(i)
- except OSError, err:
- if err.errno != errno.EBADF:
- raise
- os.open('/dev/null', os.O_RDONLY) # stdin
- os.open(logfilename, os.O_WRONLY|os.O_CREAT|os.O_APPEND)
- os.dup(1) # stdout and stderr
- signal.signal(signal.SIGHUP, sighandler_reconnect)
- print NEWRASSESSION
- def del_env(name):
- "Delete environment variables"
- try:
- del os.environ[name]
- except:
- pass
- def sanitize_env():
- "Make environment safe for root use, just in case ..."
- os.environ['PATH'] = '/sbin:/bin:/usr/sbin:/usr/bin'
- os.environ['SHELL'] = '/bin/sh'
- os.environ['LC_ALL'] = 'POSIX'
- del_env('LD_PRELOAD')
- del_env('LD_LIBRARY_PATH')
- del_env('LD_RUN_PATH')
- del_env('MALLOC_CHECK_')
- # We close all fds in daemonize()
- # Signal safe variants of time.sleep() and fdes.read()
- def my_sleep(secs):
- """Sleep secs seconds, but continue sleeping after receiving signal,
- don't sleep if interrupted is set."""
- starttm = time.time()
- #time.sleep(secs)
- while not interrupted and not reconnect:
- tosleep = starttm + secs - time.time()
- if (tosleep <= 0):
- break
- time.sleep(tosleep)
- def my_read(fdes):
- "Read from file descriptor fdes, but restart on EINTR"
- txt = ''
- while not interrupted:
- try:
- txt += fdes.read()
- return txt
- except IOError, err:
- if err.errno in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- def my_read_until(sess, what, tout = None):
- "sess.read_until with safety against EINTR"
- import socket, select
- txt = ''
- while 1:
- try:
- txt += sess.read_until(what, tout)
- return txt
- except socket.error, err:
- if err.args[0] in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- except select.error, err:
- if err.args[0] in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- def my_read_some(sess):
- "sess.read_some with safety against EINTR"
- import socket, select
- while 1:
- try:
- txt = sess.read_some()
- return txt
- except socket.error, err:
- #print err
- if err.args[0] in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- except select.error, err:
- if err.args[0] in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- def write_all(fdes, data):
- "Output data to fdes"
- while data:
- written = os.write(fdes, data)
- data = data[written:]
- def read_until(fdes, search, timeout = 4, fdout = None):
- """Read from fdes and wait for a list of strings (search) or EOF.
- Echos read data to fdout (if specified), returns all captured
- text and a status code:
- 0 .. N-1: string that matched
- -1 : EOF
- -2 : timeout
- -X : Other error
- Notes: fdes and fdout are file numbers not objects."""
- import select
- rdbuf = ''
- #print 'read_until(%i, %s, %i, %i)' % (fdes, search, timeout, fdout)
- while 1:
- try:
- rfd, wfd, xfd = select.select([fdes], [], [fdes], timeout)
- except select.error, err:
- # Hmm, why does select not raise OSError?
- if err.args[0] in (errno.EINTR, errno.EAGAIN):
- continue
- else:
- raise
- # Timeout
- if not fdes in rfd and not fdes in xfd:
- return rdbuf, -2
- #print yellowraspy, 'Timeout waiting for data'
- read = os.read(fdes, 1024)
- if not read:
- # EOF
- return rdbuf, -1
- rdbuf += read
- if fdout:
- write_all(fdout, read)
- # Search for strings
- idx = 0
- for sstr in search:
- if sstr in rdbuf:
- return rdbuf, idx
- idx += 1
- def check_exec(enm):
- "Check for executable enm"
- if not os.access(enm, os.X_OK):
- print redraspy, "Fatal: Can't execute %s" % enm
- sys.exit(5)
- def check_prereq():
- "Check for the existance of required executables"
- check_exec('/usr/bin/opiekey')
- check_exec('/bin/ping')
- # FIXME: This is fuzzy ...
- if not cfg.has_option('global', 'cmdprefix'):
- check_exec('/sbin/ip')
- check_exec('/etc/init.d/ipsec')
- check_exec('/usr/sbin/ipsec')
- def check_whiterussian():
- """Check whether we are running on OpenWRT (white russian)
- WhiteRussian 0.9 has a number of flaws in python (segfaults
- on importing platform, telnetlib/sockets not working, getpass
- not working ...
- This detects it and enables the workarounds."""
- global iswhiterussian
- iswhiterussian = False
- try:
- banner = open('/etc/banner', 'r')
- except:
- return
- wrre = re.compile(r'WHITE RUSSIAN \(0\.[0-9]')
- for ln in banner.readlines():
- if wrre.search(ln):
- iswhiterussian = True
- return
- def pass_timeout(sig, frame):
- "Timeout for password handling"
- print '\n', redraspy, 'Timeout waiting for OPIE Passphrase'
- #signal.signal(sig, signal.SIG_DFL)
- os._exit(128+sig)
- def mygetpass(prompt = "Password: ", safe = True):
- import termios
- fd = sys.stdin.fileno()
- old = termios.tcgetattr(fd)
- new = termios.tcgetattr(fd)
- new[3] = new[3] & ~termios.ECHO # lflags
- try:
- termios.tcsetattr(fd, termios.TCSADRAIN, new)
- except:
- if safe:
- raise
- os.popen('stty -echo')
- try:
- passwd = raw_input(prompt)
- finally:
- try:
- termios.tcsetattr(fd, termios.TCSADRAIN, old)
- except:
- os.popen('stty echo')
- print '\n',
- return passwd
- def input_passphrase():
- "Query OPIE passphrase"
- #import getpass
- global passphrase
- signal.alarm(60)
- signal.signal(signal.SIGALRM, pass_timeout)
- passphrase = mygetpass(greenraspy + ' Enter Passphrase for OPIE: ', False)
- signal.alarm(0)
- cfg_defaults = {'pinginterval': 20, 'pingtimeout': 4,
- 'pingfail': 4, 'failholdoff': 150, 'authretry': 3,
- 'ipseccmd': '/usr/sbin/ipsec auto',
- 'ipsecinit': '/etc/init.d/ipsec',
- 'ipsecconfread': 'cat /etc/ipsec.conf',
- 'ipcmd': '/sbin/ip',
- 'telnetcmd': '/usr/bin/telnet',
- 'routeraw': 'cat /proc/net/route',
- 'netdevexist': 'test -e /proc/sys/net/ipv4/conf/'}
- def set_config_defaults():
- "Fill in defaults into the global section"
- for key in cfg_defaults.keys():
- if not cfg.has_option('global', key):
- cfg.set('global', key, cfg_defaults[key])
- def hextoip(hstr):
- "Convert netw byte order hex number in hstr to ip addr notation"
- ipno = int(hstr, 16)
- return '%i.%i.%i.%i' % (ipno & 0xff, (ipno >> 8) & 0xff,
- (ipno >> 16) & 0xff, (ipno >> 24) & 0xff)
- def set_config_netdev():
- "Figure out the IPsec device of the default route"
- # For a kernel with KLIPS, assume ipsec0 is good
- if os.popen('%s%s' % \
- (cfg.get('global', 'netdevexist'), 'ipsec0')
- ).close() == None:
- cfg.set('global', 'netdev', 'ipsec0')
- else:
- # Parse /proc/net/route for the default route
- rfd = os.popen('%s' % cfg.get('global', 'routeraw'))
- for line in rfd.readlines():
- splitline = line.split('\t')
- if splitline[1] == '00000000':
- cfg.set('global', 'netdev', splitline[0])
- if not cfg.has_option('global', 'gateway'):
- cfg.set('global', 'gateway',
- 'via %s' % hextoip(splitline[2]))
- rfd.close()
- return
- rfd.close()
- print redraspy, 'Fatal: No netdev specified and no default route'
- sys.exit(1)
- def cfg_prefix(optname, prefix):
- "Prefix a global cfg option optname with prefix"
- opt = cfg.get('global', optname)
- cfg.set('global', optname, prefix + ' ' + opt)
- def read_config(cfgfile, reset = 0):
- """Read config file (specified by cfgfile) and
- fill in defaults; store in global var cfg"""
- #global cfg
- if not os.access(cfgfile, os.R_OK):
- print redraspy, "Fatal: Config file %s can't be accessed" % cfgfile
- sys.exit(6)
- if reset:
- logname = cfg.get('global', 'logname')
- for sect in cfg.sections():
- cfg.remove_section(sect)
- else:
- logname = os.getlogin()
- cfg.read(cfgfile)
- if not cfg.has_section('global'):
- print redraspy, 'Fatal: No [global] section found in %s' % cfgfile
- sys.exit(6)
- set_config_defaults()
- if cfg.has_option('global', 'cmdprefix'):
- prefix = cfg.get('global', 'cmdprefix')
- cfg_prefix('ipseccmd', prefix)
- cfg_prefix('ipsecinit', prefix)
- cfg_prefix('ipsecconfread', prefix)
- cfg_prefix('ipcmd', prefix)
- cfg_prefix('telnetcmd', prefix)
- cfg_prefix('routeraw', prefix)
- cfg_prefix('netdevexist', prefix)
- cfg.read(cfgfile)
- set_config_defaults()
- if len(cfg.sections()) < 2:
- print redraspy, 'Fatal: No tunnels configured in %s' % cfgfile
- sys.exit(6)
- if not cfg.has_option('global', 'netdev'):
- set_config_netdev()
- if not cfg.has_option('global', 'gateway'):
- cfg.set('global', 'gateway', 'scope link')
- if not cfg.has_option('global', 'logname'):
- cfg.set('global', 'logname', logname)
- #if verbose: print cfg.__dict__
- # Check for unknown options!
- for opt in cfg.options('global'):
- if not opt in global_kwds:
- print yellowraspy, "Unknown option %s in global section" % opt
- def get_opie_key(challenge):
- """Call opiekey, give it the challenge and the passphrase,
- get back and return response."""
- import popen2
- print challenge
- chall = challenge.split(' ')
- if not quiet:
- if int(chall[1]) < 50:
- print yellowraspy, "Renew your OPIE key, only %i logins left" \
- % int(chall[1])
- else:
- print greenraspy, "%i logins left" % int(chall[1])
- opie = popen2.Popen4('/usr/bin/opiekey %s %s %s' % \
- (chall[1], chall[2], chall[3]))
- fdes = opie.fromchild.fileno()
- txt, tout = read_until(fdes, ("pass phrase: ",), 4, outvfn)
- if tout < 0:
- print redraspy, 'Fatal: Timeout waiting for opiekey'
- sys.exit(1)
- # sys.stdout.flush()
- opie.tochild.write(passphrase + '\n')
- opie.tochild.flush()
- # opie.tochild.close()
- # Ignore one linefeed
- ret = opie.fromchild.readline()
- # The answer ...
- ret = my_read(opie.fromchild)
- excode = opie.wait()
- if verbose:
- print " %i " % excode,
- return ret
- def auth_session_telnetlib():
- "Do telnet to authhost for opie authentication via telnetlib"
- import telnetlib
- #global verbose
- host = cfg.get('global', 'authhost')
- if not quiet:
- print greenraspy, 'Trying auth session to %s' % host
- auth = 0
- authretry = cfg.get('global', 'authretry')
- while not authretry or auth < authretry:
- try:
- sess = telnetlib.Telnet(host)
- break
- except telnetlib.socket.error, err:
- auth += 1
- print yellowraspy, str(err)
- if authretry and auth < authretry:
- my_sleep(int(cfg.get('global', 'failholdoff')))
- else:
- print redraspy, "Fatal: Can't connect to %s" % host
- return 1
- txt = my_read_until(sess, 'login: ', 32)
- if verbose:
- print txt
- if not ('login: ' in txt) or interrupted:
- print redraspy, 'Fatal: Login prompt not found'
- sess.close()
- return 1
- #time.sleep(0.5)
- sess.write(cfg.get('global', 'logname') + '\n')
- txt = my_read_until(sess, 'Response: ', 16)
- if verbose:
- print txt
- resp = get_opie_key(txt.split('\n')[0])
- sess.write(resp)
- if verbose:
- print resp
- txt = my_read_some(sess)
- while txt:
- if verbose:
- sys.stdout.write(txt)
- if 'Login incorrect' in txt or interrupted:
- print redraspy, 'Fatal: Authentication failed'
- sess.close()
- return 1
- txt = my_read_some(sess)
- sess.close()
- return 0
- def auth_session():
- "Do telnet to authhost for opie authentication"
- telnetcmd = cfg.get('global', 'telnetcmd')
- # Handle OpenWRT specially; it's telnetlib is broken
- if telnetcmd == '/usr/bin/telnet' and not iswhiterussian:
- return auth_session_telnetlib()
- import popen2, signal
- #global verbose
- host = cfg.get('global', 'authhost')
- if not quiet:
- print greenraspy, 'Trying auth session to %s' % host
- auth = 0
- authretry = cfg.get('global', 'authretry')
- while not authretry or auth < authretry:
- sess = popen2.Popen3("%s %s" % \
- (telnetcmd, host), True)
- # Allow for nameserver or refused conn. to show
- if sess.poll() == -1:
- time.sleep(0.5)
- #print sess.__dict__
- if sess.poll() == -1:
- break
- auth += 1
- print yellowraspy, sess.childerr.read()
- if authretry and auth < authretry:
- my_sleep(int(cfg.get('global', 'failholdoff')))
- else:
- print redraspy, "Fatal: Can't connect to %s" % host
- return 1
- fromfd = sess.fromchild.fileno()
- txt, tout = read_until(fromfd, ('login: ', ), 32) #, sys.stdout.fileno())
- if verbose:
- print txt
- if not ('login: ' in txt) or interrupted:
- exitstat = sess.poll()
- exiterr = os.read(sess.childerr.fileno(), 4096)
- print redraspy, 'Fatal: Login prompt not found (%i/%i)\n %s' % \
- (tout, exitstat, exiterr)
- if exitstat == -1:
- os.kill(sess.pid, signal.SIGHUP)
- return 1
- #time.sleep(0.5)
- os.write(sess.tochild.fileno(), cfg.get('global', 'logname') + '\n')
- #print cfg.get('global', 'logname')
- txt, tout = read_until(fromfd, ('Response: ', ), 16, sys.stdout.fileno())
- if verbose:
- print txt
- resp = get_opie_key(txt.split('\n')[1])
- os.write(sess.tochild.fileno(), resp + '\n')
- #if verbose:
- # print resp
- exitstat = sess.poll()
- while exitstat == -1:
- txt = os.read(fromfd, 1024)
- if verbose:
- sys.stdout.write(txt)
- if 'Login incorrect' in txt or interrupted:
- print redraspy, 'Fatal: Authentication failed'
- if sess.poll() == -1:
- os.kill(sess.pid, signal.SIGHUP)
- return 1
- exitstat = sess.poll()
- if exitstat != 0 and verbose:
- print yellowraspy, 'Info: telnet exit status %i' % exitstat
- return 0
- def ipsec_parse(lines, conn, tag):
- "Find tag in conn, also understanding also= include syntax"
- inconn = 0
- also = []
- reconn = re.compile('[ \t]*conn[ \t]*([^ \t]*)[ \t]*$')
- retag = re.compile('[ \t]+%s[ \t]*=[ \t]*(.*)$' % tag)
- realso = re.compile('[ \t]+also[ \t]*=[ \t]*(.*)$')
- for line in lines:
- cmatch = reconn.match(line)
- if cmatch:
- if cmatch.group(1) == conn:
- inconn = 1
- else:
- inconn = 0
- continue
- if not inconn:
- continue
- tmatch = retag.match(line)
- #print line, tmatch
- if tmatch:
- return tmatch.group(1)
- amatch = realso.match(line)
- if amatch:
- also.append(amatch.group(1))
- # If we got here, we need to scan the also= sections
- for sect in also:
- ret = ipsec_parse(lines, sect, tag)
- if ret:
- return ret
- # Not found, error
- return None
- def extract_route(connname):
- "Fill find route in ipsec.conf, assuming we are left"
- ipsecconf = os.popen(cfg.get('global', 'ipsecconfread'), 'r').readlines()
- # Strip CRLF
- strip = re.compile(r'[\r\n]*$')
- #ipsecconf = map(lambda x: strip.sub('', x), ipsecconf)
- ipsecconf = [strip.sub('', line) for line in ipsecconf]
- # Strip comments
- strip = re.compile(r'#.*$')
- #ipsecconf = map(lambda x: strip.sub('', x), ipsecconf)
- ipsecconf = [strip.sub('', line) for line in ipsecconf]
- #print ipsecconf
- # Look for route
- route = ipsec_parse(ipsecconf, connname, 'rightsubnet')
- #vip = ipsec_parse(ipsecconf, connname, 'leftsubnet')
- if verbose:
- print greenraspy, "Info for conn %s: route %s" % (connname, route)
- return route
- def call_debug_cmd(cmd):
- "Call external binary and return output. (Used for debugging.)"
- fdes = os.popen(cmd);
- output = my_read(fdes)
- err = fdes.close()
- print output
- # IPsec (free/openswan) connection
- class IPsecConn:
- """Class that abstracts the free/openswan way of making
- IPsec connections, an object of this type represents
- one IPsec tunnel."""
- def __init__(self, config, section):
- """Setup data structures from config dict, we don't access
- the cfg global variable"""
- self.name = section
- self.route = extract_route(section)
- self.oldroute = None
- if config.has_option(section, 'route'):
- rte = config.get(section, 'route')
- if not self.route:
- self.route = rte
- elif rte != self.route:
- self.oldroute = self.route
- self.route = rte
- if not self.route:
- print redraspy, 'Connection %s has no route' % self.name
- sys.exit(1)
- if config.has_option(section, 'mtu'):
- self.mtu = int(config.get(section, 'mtu'))
- else:
- self.mtu = 1428
- if config.has_option(section, 'pinghost'):
- self.pinghost = config.get(section, 'pinghost')
- self.pingtimeout = int(config.get('global', 'pingtimeout'))
- self.pingstatus = None
- else:
- self.pinghost = ''
- self.pingtimeout = 0
- self.pingstatus = 'not configured'
- self.state = 'unknown'
- self.connstarted = 0
- self.connstopped = 0
- if config.has_option('global', 'virtip'):
- self.src = 'src %s' % config.get('global', 'virtip')
- else:
- self.src = ''
- self.netdev = config.get('global', 'netdev')
- self.gateway = config.get('global', 'gateway')
- self.ipseccmd = config.get('global', 'ipseccmd')
- self.ipcmd = config.get('global', 'ipcmd')
- if config.has_option(section, 'desc'):
- self.desc = "%s(%s)" % (section, config.get(section, 'desc'))
- else:
- self.desc = "%s(%s)" % (section, self.route)
- # Check for unknown options!
- for opt in cfg.options(section):
- if not opt in conn_kwds:
- print yellowraspy, "Unknown option %s in section %s" % (opt, section)
- def ipsec_up(self):
- "up this ipsec connection"
- global outvfn
- fdes = os.popen("%s --up %s" % \
- (self.ipseccmd, self.name))
- txt, tout = read_until(fdes.fileno(),
- ("IPsec SA established",), 16, outvfn)
- if tout < 0:
- if verbose:
- print redraspy, 'Connection %s failed' % self.desc
- return 1
- fdes.close()
- #time.sleep(1)
- return 0
- def ipsec_down_unroute(self, what):
- "down or unroute (specified by what) ipsec connection"
- fdes = os.popen("%s --%s %s" % \
- (self.ipseccmd, what, self.name))
- txt = my_read(fdes)
- if fdes.close():
- print txt
- def route_up(self, what):
- "add/replace (specified by what) route for connection"
- #if not self.src:
- # return 0
- rte = self.route
- if rte == 'AUTO':
- return 0
- fdes = os.popen("%s route %s %s %s dev %s %s mtu %i" % \
- (self.ipcmd, what,
- rte, self.gateway, self.netdev, self.src, self.mtu))
- txt = my_read(fdes)
- if fdes.close():
- if what == 'add' or what == 'replace' or verbose:
- print yellowraspy, '%s route for conn %s failed' % \
- (what, self.desc)
- print " %s route %s %s %s dev %s %s mtu %i" % \
- (self.ipcmd, what, rte, self.gateway,
- self.netdev, self.src, self.mtu)
- print txt
- return 1
- return 0
- def route_down(self, route = None):
- "delete route for connection"
- #if not self.src:
- # return 0
- if not route:
- route = self.route
- if route == 'AUTO':
- return 0
- fdes = os.popen("%s route delete %s dev %s" % \
- (self.ipcmd, route, self.netdev))
- txt = my_read(fdes)
- #if verbose:
- # print txt
- return 0
- def route_status(self):
- "Check routing table for entry with this connection"
- if self.route == 'AUTO':
- return 0
- fdes = os.popen("%s route show %s" % \
- (self.ipcmd, self.route))
- txt = my_read(fdes)
- if txt and not quiet:
- print txt,
- fdes.close()
- if not txt:
- return 3
- return 0
- def ipsec_status(self):
- "Return status of ipsec connection"
- fdes = os.popen("%s --status" % self.ipseccmd)
- txt = fdes.readlines()
- if fdes.close():
- print redraspy, 'Could not check connection'
- return 4
- rgx = re.compile(r'"%s" esp' % self.name)
- for line in txt:
- if rgx.search(line):
- print line,
- self.state = 'up'
- return 0
- return 3
- def doping(self):
- """ping with forced timeout (no -w option for ping)
- returns 0 on success, anything else on failure"""
- import subprocess
- process = subprocess.Popen(["/bin/ping", "-c", "1", "%s" % self.pinghost],
- bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- time.sleep(0.1)
- rc = process.poll()
- if rc != None:
- return rc
- for iter in range(0, self.pingtimeout):
- time.sleep(1)
- rc = process.poll()
- if rc != None:
- return rc
- os.kill(process.pid, signal.SIGTERM)
- return process.wait()
- def ping(self):
- """ping pinghost, ret -1 if not configured,
- 0 if failed, 1 is successful"""
- if self.pinghost:
- if self.doping() != 0:
- if not quiet:
- print yellowraspy, 'ping %s failed %s' % \
- (self.pinghost, time.strftime(timefmt))
- self.pingstatus = 'failed'
- return 0
- else:
- self.pingstatus = 'succeeded'
- return 1
- else:
- return -1
- def up(self):
- """Bring connection up and add route, return 0 for success,
- tear down again if not entirely successful."""
- ret = self.ipsec_up()
- if ret:
- return ret
- action = 'replace'
- #if self.oldroute and not self.route == 'AUTO':
- if self.route != 'AUTO':
- #call_debug_cmd('%s route show' % self.ipcmd)
- #call_debug_cmd('%s addr show' % self.ipcmd)
- if self.oldroute:
- ret = self.route_down(self.oldroute)
- else:
- ret = self.route_down(self.route)
- action = 'add'
- ret = self.route_up(action)
- # if 0 and ret:
- if ret:
- self.ipsec_down_unroute('down')
- self.ipsec_down_unroute('unroute')
- return ret
- self.state = 'up'
- self.connstarted = time.time()
- # return 0 ## ret
- return ret
- def down(self):
- "Bring connection down, delete route, no return code"
- self.ipsec_down_unroute('down')
- self.route_down()
- self.ipsec_down_unroute('unroute')
- self.state = 'down'
- self.connstopped = time.time()
- def status(self):
- "Check for conn status, return 0 for success"
- if not quiet:
- print greenraspy, 'Conn status %s: %s,' % \
- (self.desc, self.state),
- if self.connstarted:
- print "started %s\n\t" % \
- time.strftime(timefmt, time.localtime(self.connstarted)),
- if self.connstopped:
- print "stopped %s\n\t" % \
- time.strftime(timefmt, time.localtime(self.connstopped)),
- print "ping: %s" % self.pingstatus
- self.route_status()
- return self.ipsec_status()
- # Global functions again, right encapsulation pending ...
- def add_del_ip(what):
- "Add/Delete (specified by what) virtual IP address to net interface"
- if cfg.has_option('global', 'virtip'):
- ipaddr = cfg.get('global', 'virtip')
- else:
- return
- netdev = cfg.get('global', 'netdev')
- fdes = os.popen("%s addr %s %s dev %s" % \
- (cfg.get('global', 'ipcmd'), what, ipaddr, netdev))
- txt = my_read(fdes)
- if fdes.close() and not quiet:
- print yellowraspy, '%s addr %s %s dev %s failed' % \
- (cfg.get('global', 'ipcmd'), what, ipaddr, netdev)
- print txt
- def setup_connections(config):
- "Make and return a list of connections from config sections"
- conns = []
- for conn in cfg.sections():
- if conn == 'global':
- continue
- conns.append(IPsecConn(config, conn))
- return conns
- def check_start_ipsec(conn):
- "Check if IPsec is running, try to start if not, bail out this fails"
- rgx = re.compile(r'"%s"' % conn.name)
- outfd = os.popen('%s --status' % cfg.get('global', 'ipseccmd'), 'r')
- output = my_read(outfd)
- err = outfd.close()
- connknown = rgx.search(output)
- if err or not connknown:
- if err:
- sta = 'start'
- if not quiet:
- print greenraspy, 'Start IPsec'
- else:
- sta = 'restart'
- if not quiet:
- print greenraspy, 'Restart IPsec'
- # TODO: Disconnect from controlling terminal
- outfd = os.popen('%s %s' % (cfg.get('global', 'ipsecinit'), sta), 'r')
- output = my_read(outfd)
- err = outfd.close()
- if err:
- print redraspy, 'Fatal: Could not start IPsec'
- print output
- sys.exit(7)
- my_sleep(5)
- if verbose:
- print greenraspy, 'IPsec running'
- def up_connections(conns):
- "Start all IPsec connections from list conns"
- check_start_ipsec(conns[0])
- add_del_ip('add')
- upconns = []
- for conn in conns:
- if not quiet:
- print greenraspy, 'start ipsec connection %s' % conn.desc
- ret = conn.up()
- if ret or interrupted:
- if not quiet:
- print redraspy, 'Connection %s failed, tear down' % conn.desc
- down_connections(upconns)
- add_del_ip('del')
- return 1
- else:
- upconns.append(conn)
- if not quiet:
- print greenraspy, 'ipsec connections up %s' % \
- time.strftime(timefmt)
- return 0
- def do_pings(conns):
- "Ping all connections (in conns), returns 1 if _all_ fail"
- succ = 0
- pings = 0
- ival = int(cfg.get('global', 'pinginterval'))
- for conn in conns:
- if reconnect or interrupted:
- return 0
- ret = conn.ping()
- if ret == -1:
- # Not configured, try next connection
- continue
- else:
- # Count successful pings
- succ += ret
- # Count tried pings
- pings += 1
- my_sleep(ival)
- if not pings:
- # If no ping has been tried, we need to sleep anyway
- my_sleep(ival)
- return 0
- if not succ:
- # We have tried some pings, but none succeeded
- return pings
- return 0
- def down_connections(conns):
- "Stop all IPsec connections in conns"
- for conn in conns:
- if not quiet:
- print greenraspy, 'stop ipsec connection %s' % conn.desc
- conn.down()
- add_del_ip('del')
- if not quiet:
- print greenraspy, 'ipsec connections stopped %s' % \
- time.strftime(timefmt)
- def up_all(conns):
- """Up all connections in conns. This is the main loop for
- ras.py script."""
- global interrupted, reconnect, connections, alreadyauth
- notconn = 0
- # We need it anyway, even if we are connected already
- if cfg.has_option('global', 'authhost'):
- input_passphrase()
- start = time.time()
- if daemon:
- daemonize()
- if not quiet:
- print green + "ras.py started %s" % time.strftime(timefmt) + norm
- while not interrupted and ( \
- not cfg.has_option('global', 'maxconnect') \
- or time.time() < start + int(cfg.get('global', 'maxconnect'))):
- # Skip auth session if tunnels exist already
- if notconn or conns[0].status():
- if cfg.has_option('global', 'authhost'):
- if alreadyauth == 0 and auth_session():
- break
- else:
- alreadyauth = 0
- if up_connections(conns):
- interrupted = 1
- break
- else:
- if not quiet:
- print greenraspy, 'Connections already up'
- # Ping loop
- err = 0
- while err < int(cfg.get('global', 'pingfail')) \
- and not interrupted and not reconnect:
- ret = do_pings(conns)
- if ret:
- err += ret
- else:
- err = 0
- # Pings failed
- down_connections(conns)
- notconn = 1
- if reconnect:
- read_config(configfile, 1)
- conns = setup_connections(cfg)
- # FIXME: No point in passing conns here if we access the global
- connections = conns
- os.utime(lockfilename, None)
- reconnect = 0
- my_sleep(int(cfg.get('global', 'failholdoff')))
- if not quiet:
- print green + "ras.py stopped %s" % time.strftime(timefmt) + norm
- if daemon:
- del_lockfile()
- return interrupted
- def down_all(conns):
- "Down all connections (from list conns)"
- # We could add logic here to kill the right PID ...
- down_connections(conns)
- return 0
- def status_all(conns):
- "Query all connection objects (in conns) for status"
- ret = 0
- for conn in conns:
- retconn = conn.status()
- if retconn:
- ret = retconn
- if not quiet:
- if ret:
- print redraspy, 'down'
- else:
- print greenraspy, 'up'
- return ret
- def print_logfile(beginmarker, endmarker = '^@$%&#', beginmarker2 = '^@$%&#'):
- """Outputs the section from the logfile since the last
- beginmarker, avoiding endmarker."""
- logf = open(logfilename)
- logl = logf.readlines()
- endlog = ''
- for line in logl:
- if beginmarker in line:
- endlog = ''
- elif beginmarker2 in line:
- endlog = ''
- elif endmarker in line:
- pass
- else:
- endlog += line
- print endlog
- logf.close()
- return endlog
- def file_newer(file1, file2):
- "Returns a number > 0 if file1 is newer (mtime) than file2 else <= 0"
- stat1 = os.stat(file1)
- stat2 = os.stat(file2)
- return stat1.st_mtime - stat2.st_mtime
- def usage():
- "Tell the user how to use this script"
- print bold + "Usage: ras.py [options] action [cfg file]" + norm
- print bold + " options: -v (verbose), -q (quiet), \n\t-d (daemonize)," \
- + " -D (Direct) -a (alreadyauthenticated)"
- print " action: (start|up|status|help|stop|down|"
- print "\t try-restart|condrestart|restart|reload|force-reload|probe)" \
- + norm
- return 2
- def print_help():
- "Description of what this script does and how it's configured"
- print __revision__
- print bold + "This script connects you to an IPsec RAS service.\n" + norm
- print "Assumptions: The FreeS/WAN or openswan /etc/ipsec.conf and"
- print "/etc/ipsec.secrets config files are configured correctly and"
- print "contain the descriptions of the connections and the needed secrets."
- print "The connections should be configured to be added, NOT started"
- print "on /etc/init.d/ipsec start, i.e. auto=add in ipsec.conf."
- print "The script assumes that we are the left side of the connection."
- print " Note: It might work for people with auto=start, but I haven't"
- print " tested this. Nor have I tested without private virtual IP.\n"
- print "This script should be executed setuid root."
- print "It is configured by /etc/ras.config (or another cfg file specified)"
- print "which has the .ini file syntax as described below.\n"
- print "The [global] section has the following keys (all optional!):"
- print " pinginterval: How many seconds to wait between pings (default: 20)."
- print " pingtimeout: How long (s) to wait for our peer to answer (default: 4)."
- print " pingfail: How many subsequent pings need to fail to make the script"
- print "\tassume that the connectivity is down (default: 4)."
- print " failholdoff: How long to sleep before trying to reconnect (def: 150)."
- print " maxconnect: How many seconds we may be connected in total"
- print "\t(default: empty => infinite)."
- print " virtip: virtual IP of your end of the IPsec connection "
- print "\t(default: empty => no virtual IP is used)."
- print " netdev: name of the NIC that's used to send out IPsec traffic"
- print "\t(default: empty => ipsec0 for KLIPS, netdev of defaultroute otherwise)."
- print " gateway: set to an empty string or 'via IP' to explicitly set routing GW"
- print "\t(default: if no netdev is specified take defroute gw, otherwise '')."
- print " authhost: Machine to telnet to for the opie auth session"
- print "\t(default: none => skip opie auth session)."
- print " authretry: How many times to try auth session if telnet times out."
- print "\t0 means infinity (default: 3)."
- print " logname: username to log in as into authhost"
- print "\t(default: user running the script).\n"
- print "Expert options are:"
- print " ipseccmd: Let's you override the command to control ipsec tunnels"
- print "\twith --up/--down/--status conn.name (default: /usr/sbin/ipsec auto)"
- print " ipsecinit: Let's you the command to call the ipsec init script"
- print "\t(default: /etc/init.d/ipsec)"
- print " ipsecconfread: Let's you override how to read FreeSWAN's ipsec.conf"
- print "\t(default: cat /etc/ipsec.conf)"
- print " ipcmd: The iproute2 command (default: /sbin/ip)"
- print " routeraw: How to get raw routing table (default: cat /proc/net/route)"
- print " netdevexist: How to determine existence of a network device"
- print "\t(default: test -e /proc/sys/net/ipv4/conf/)"
- print "The most common use for these expoert options is to prefix commands with"
- print "ssh -t to remote control another machine who handles the tunnels. This"
- print "can be more easily achieved by specifying"
- print " cmdprefix: Let's you prefix the commands ipseccmd, ipsecinit,"
- print "\tipsecconfread, ipcmd, routeraw, and netdevexist."
- print "\tNote that individual settings are not prefixed automatically.\n"
- print "The global section is mandatory, but it can be empty if the"
- print "defaults all work for you.\n"
- print "There are additional sections where the section name does specify"
- print "the name of the IPsec connection as given in ipsec.conf."
- print "The following entries per section are supported:"
- print "route: The route that should be set, should be equal to the"
- print "\tother side's subnet, e.g. 10.0.0.0/8 (optional)."
- print "\tThe route is taken from rightsubnet in ipsec.conf, but can be"
- print "\toverriden here."
- print "\tThe special word 'AUTO' here prevents ras.py to affect your routes"
- print "\tbut assumes that the ipcseccmd has taken care of it."
- print "pinghost: Machine to periodically send ping commands to (optional).\n"
- print "mtu: MTU for the route (optional, default 1428).\n"
- print "desc: Description of the connection (optional).\n"
- print "Multiple connections can be specified this way.\n"
- print "The script is verbose about success or failure, the return codes"
- print "roughly match the ones from LSB init scripts.\n"
- print "The program can be run in daemon mode, in which case it forks"
- print "a background process; subsequent calls of this program will connect"
- print "to the daemon by sending it signals."
- print "The signals SIGTERM/SIGINT/SIGQUIT terminate the process/daemon,"
- print "the signal SIGUSR1 makes it log status information, and"
- print "the signal SIGUSR2 makes it close the connections, reread the config"
- print "file and reconnect. SIGHUP reacts like SIGUSR2 in daemon mode, and"
- print "like SIGTERM in foreground mode.\n"
- def sighandler(sig, frame):
- "Signal handler invoked to down connections and stop the script"
- global interrupted
- #global yellowraspy, quiet
- interrupted = sig + 128
- if not quiet:
- print yellowraspy, 'Signal %i caught %s, bailing out' % (sig, frame)
- def sighandler_status(sig, frame):
- "Signal handler to create status log entry"
- #global connections, BEGINRASSTATUS, ENDRASSTATUS
- print BEGINRASSTATUS
- status_all(connections)
- print ENDRASSTATUS
- def sighandler_reconnect(sig, frame):
- "Signal handler to reconnect connections"
- global reconnect
- #global yellowraspy, quiet
- reconnect = 1
- if not quiet:
- print yellowraspy, 'Signal %i caught %s, reconnecting' % (sig, frame)
- def setup_signal_handlers():
- "setup signal handlers"
- signal.signal(signal.SIGTERM, sighandler)
- signal.signal(signal.SIGINT , sighandler)
- signal.signal(signal.SIGQUIT, sighandler)
- signal.signal(signal.SIGHUP , sighandler) # override in daemon mode
- signal.signal(signal.SIGUSR1, sighandler_status)
- signal.signal(signal.SIGUSR2, sighandler_reconnect)
- def parse_args(argv):
- "Parse command line args"
- import getopt
- global daemon, quiet, verbose, outqfn, outvfn, alreadyauth, configfile
- # daemon running?
- lpid = check_lockfile()
- # options
- try:
- optlist, args = getopt.gnu_getopt(argv, 'vqdaDh', ('help',))
- except getopt.GetoptError, exc:
- print exc
- sys.exit(usage())
- for opt in optlist:
- if opt[0] == '-q':
- quiet = 1
- outqfn = None
- continue
- if opt[0] == '-v':
- verbose = 1
- quiet = 0
- outqfn = sys.stdout.fileno()
- outvfn = sys.stdout.fileno()
- continue
- if opt[0] == '-d':
- daemon = 1
- continue
- if opt[0] == '-h' or opt[0] == '--help':
- print_help()
- sys.exit(usage())
- #continue
- if opt[0] == '-a':
- alreadyauth = 1 # override
- continue
- if opt[0] == '-D':
- lpid = 0 # override
- continue
- if len(args) < 2 or len(args) > 3:
- sys.exit(usage())
- # Help
- if args[1] == 'help':
- print_help()
- sys.exit(usage())
- if len(args) > 2:
- configfile = args[2]
- return args[1], lpid
- def do_control_daemon(action, lpid):
- "Frontend to control ras.py that's running in daemon mode"
- global daemon
- if action in ('start', 'up'):
- print redraspy, "ras.py already running (pid %i)" % lpid
- return 0
- elif action == 'status':
- try:
- os.kill(lpid, signal.SIGUSR1)
- except OSError, err:
- print yellowraspy, str(err)
- return status_all(connections)
- my_sleep(1)
- log = print_logfile(BEGINRASSTATUS, ENDRASSTATUS, NEWRASSESSION)
- if (greenraspy + ' up') in log:
- return 0
- else:
- return 1
- elif action in ('stop', 'down'):
- try:
- os.kill(lpid, signal.SIGTERM)
- except OSError, err:
- print redraspy, str(err)
- return down_all(connections)
- my_sleep(1)
- print_logfile(NEWRASSESSION)
- return 0
- elif action in ('restart', 'try-restart', 'condrestart'):
- try:
- os.kill(lpid, signal.SIGTERM)
- except OSError, err:
- print redraspy, str(err)
- return 4
- my_sleep(1)
- daemon = 1
- return do_direct_operation('start')
- elif action in ('reload', 'force-reload'):
- try:
- os.kill(lpid, signal.SIGHUP)
- except OSError, err:
- print redraspy, str(err)
- return 4
- return 0
- elif action == 'probe':
- if file_newer(configfile, lockfilename) > 0:
- print 'reload'
- return 0
- else:
- return 1
- else:
- return -1
- def do_direct_operation(action):
- "start, stop, status in foreground mode"
- if action in ('start', 'up', 'restart', 'try-restart', 'condrestart'):
- # FIXME: This is fuzzy ...
- if os.getuid() != 0 and not cfg.has_option('global', 'cmdprefix'):
- print redraspy, 'Need to be root'
- return 4
- else:
- if action in ('try-restart', 'condrestart', 'restart'):
- ret = status_all(connections)
- if not ret:
- print redraspy, \
- "Can't stop running ras.py that's not in daemon mode"
- return 4
- elif action != 'restart':
- return 0
- else:
- down_all(connections)
- return up_all(connections)
- elif action == 'status':
- return status_all(connections)
- elif action in ('stop', 'down'):
- return down_all(connections)
- elif action in ('reload', 'force-reload', 'probe'):
- print redraspy, 'Not running as daemon'
- return 7
- else:
- return -1
- # main routine
- def main(argv):
- "Main program ras.py"
- #global daemon, quiet, verbose, outqfn, outvfn
- global connections, cfg, configfile
- sanitize_env()
- setup_signal_handlers()
- action, lpid = parse_args(argv)
- # connections
- check_whiterussian()
- read_config(configfile)
- check_prereq()
- connections = setup_connections(cfg)
- # action
- if lpid:
- ret = do_control_daemon(action, lpid)
- else:
- ret = do_direct_operation(action)
- if ret < 0:
- return usage()
- else:
- return ret
- # run main if called standalone
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement