/openvpn-dbus-poc-server.py
Python | 351 lines | 193 code | 65 blank | 93 comment | 49 complexity | d71e517ed2eefeb04500d6944d3b927d MD5 | raw file
- #!/usr/bin/python2
- #
- # Simple and stupid Proof-of-Concept D-Bus server proxy for OpenVPN
- #
- # Copyright (C) 2016 David Sommerseth <davids@openvpn.net>
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- #
- #
- # !!!!! THIS IS PROOF-OF-CONCEPT CODE !!!!!
- #
- # That means this is NOT intended for production usage at all.
- # This code is not pretty, it is hacky and it will have bugs
- #
- # This code does also NOT guarantee the D-Bus methods, signals
- # and properties will change later on.
- #
- #
- # HOW CAN I TEST IT?
- #
- # This D-Bus server proxy works as a bridge between the
- # TCP based OpenVPN management interface on the server side
- # and provides services over the D-Bus. Any D-Bus client can
- # introspect and call methods provided by this service by
- # connecting to the net.openvpn.management D-Bus destination.
- #
- # Requirements for this to work:
- #
- # - A functional OpenVPN server configured with --management
- #
- # - A dbus-proxy configuration file consisting of *three* options:
- # management_ip, management_port and management_password
- # See example below. Obviously enough, this information must
- # match the OpenVPN values provided to --management. A password
- # is required.
- #
- # - Starting this proxy with the configuration file name
- # as the only argument:
- #
- # $ python ./openvpn-dbus-poc-server.py myconfig.ini
- #
- # - Once running, first run some tests using the PoC client
- # from a different shell
- #
- # $ python ./openvpn-dbus-poc-client.py
- #
- # - Run some gdbus tests:
- #
- # $ gdbus introspect --session -d net.openvpn.management \
- # -o /net/openvpn/management
- #
- # $ gdbus call --session -d net.openvpn.management \
- # -o /net/openvpn/management \
- # -m net.openvpn.management.server.GetStatus
- #
- # The functions in the net.openvpn.management.sessions interface
- # requires the object path (-o) to be extended with the session ID
- # returned by net.openvpn.management.server.GetActiveSessions
- #
- #
- # Example for myconfig.ini:
- #
- # [openvpn]
- # management_ip: 127.0.0.1
- # management_port: 2294
- # management_password: MY_BR3AKABL3_M4NAG3MENT_PSWD
- #
- #
- import sys
- import ConfigParser
- import socket
- import hashlib
- from gi.repository import GLib
- import dbus
- import dbus.service
- from dbus.mainloop.glib import DBusGMainLoop
- #
- # OpenVPN TCP based Management Interface glue
- #
- MANAGEMENTBUFLEN = 8192
- class OpenVPN_serverstatus(object):
- def __init__(self, data):
- self.__status = {"sessions": [], "routing": []}
- columns = {}
- for line in data:
- rec = [e.strip() for e in line.split('\t')]
- if rec[0] == "TITLE":
- s = rec[1].split(' ')
- self.__status["version"] = s[1]
- self.__status["build"] = s[2]
- elif rec[0] == "TIME":
- self.__status["updated"] = rec[1]
- self.__status["uptime"] = rec[2]
- elif rec[0] == "HEADER":
- columns[rec[1]] = rec[2:]
- elif rec[0] == "CLIENT_LIST":
- self.__status["sessions"].append(self.__parse_columns(columns, rec))
- elif rec[0] == "ROUTING_TABLE":
- self.__status["routing"].append(self.__parse_columns(columns, rec))
- elif rec[0] == "END":
- break
- def Get(self):
- return self.__status
- def __parse_columns(self, columns, rec):
- i = 0
- cl = {}
- reckey = None
- for r in rec[1:]:
- cl[columns[rec[0]][i]] = r
- i += 1
- if rec[0] == "CLIENT_LIST" or rec[0] == "ROUTING_TABLE":
- sessid = "%s%s" % (cl["Common Name"], cl["Real Address"])
- cl["sessid"] = hashlib.sha256(sessid).hexdigest()
- if rec[0] == "ROUTING_TABLE":
- routid = "%s%s%s%s" % (
- rec[0] == "CLIENT_LIST" and "CL" or "RT",
- cl["Common Name"], cl["Real Address"], cl["Virtual Address"])
- cl["routid"] = hashlib.sha256(routid).hexdigest()
- return cl
- class OpenVPNmanagement_TCP(object):
- def __init__(self, host, port, passwd = None):
- self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.__sock.connect((host, port))
- self.__passwd = passwd
- self.__loggedin = False
- def Login(self):
- if not self.__login(self.__passwd):
- self.__passwd = None
- return False
- self.__passwd = None
- self.__loggedin = True
- return True
- def GetPID(self):
- result, data = self.__communicate("pid")
- if result == "SUCCESS":
- pid = data[0].split("=")[1]
- return int(pid) > 0 and int(pid) or False
- else:
- return False
- def GetStatusObj(self):
- result, tdata = self.__communicate("status 3")
- data = list(tdata)
- data[0] = result
- status = OpenVPN_serverstatus(data)
- return status
- def __login(self, passwd):
- if self.__loggedin:
- return True
- data = self.__sock.recv(MANAGEMENTBUFLEN)
- if data == "ENTER PASSWORD:":
- self.__sock.send(passwd + "\n")
- else:
- return True
- data = self.__sock.recv(MANAGEMENTBUFLEN)
- if data.split(':')[0] == "SUCCESS":
- return True
- else:
- return False
- def __communicate(self, command):
- if not self.__loggedin:
- raise Exception("Not logged in")
- retdata = None
- result = None
- self.__sock.send(command + "\n")
- rawdata = self.__sock.recv(MANAGEMENTBUFLEN)
- if len(rawdata) > 0:
- linecount = 0
- for line in [l.strip() for l in rawdata.strip().split("\n")]:
- linecount += 1
- if linecount == 1:
- data = [l.strip() for l in line.split(':')]
- result = data[0]
- retdata = [":".join(data[1:]),]
- else:
- retdata.append(line)
- return (result, tuple(retdata))
- #
- # OpenVPN server side D-Bus interface
- #
- class OpenVPNdbus_Server(dbus.service.FallbackObject):
- def __init__(self, ovpnmgr):
- self.__ovpnmgr = ovpnmgr
- dbus.service.FallbackObject.__init__(self, dbus.SessionBus(), '/net/openvpn/management')
- print "OpenVPNdbus_Server initialized"
- @dbus.service.method('net.openvpn.management')
- def GetPID(self):
- pid = self.__ovpnmgr.GetPID()
- if pid:
- return int(pid)
- else:
- raise Exception("Failed to query OpenVPN server for its PID")
- @dbus.service.method(dbus_interface='net.openvpn.management.server',
- out_signature="v")
- def GetStatus(self):
- statobj = self.__ovpnmgr.GetStatusObj()
- if not statobj:
- raise Exception("Failed to query OpenVPN management interface")
- s = statobj.Get()
- return {"build": s["build"],
- "status_timestamp": s["updated"],
- "uptime": s["uptime"],
- "version": s["version"]}
- @dbus.service.method(dbus_interface='net.openvpn.management.server',
- out_signature="v")
- def GetActiveSessions(self):
- statobj = self.__ovpnmgr.GetStatusObj()
- if not statobj:
- raise Exception("Failed to query OpenVPN management interface")
- s = statobj.Get()
- ret = []
- for client in s["sessions"]:
- ret.append(client["sessid"])
- return ret
- @dbus.service.method(dbus_interface='net.openvpn.management.sessions',
- out_signature="v", rel_path_keyword="sessid",)
- def GetSessionInfo(self, sessid):
- statobj = self.__ovpnmgr.GetStatusObj()
- if not statobj:
- raise Exception("Failed to query OPenVPN management interface")
- s = statobj.Get()
- for s in s["sessions"]:
- if s["sessid"] == sessid[1:]:
- return s
- raise Exception("Session ID not found")
- @dbus.service.method(dbus_interface='net.openvpn.management.sessions',
- out_signature="v", rel_path_keyword="sessid",)
- def GetRouting(self, sessid):
- statobj = self.__ovpnmgr.GetStatusObj()
- if not statobj:
- raise Exception("Failed to query OPenVPN management interface")
- s = statobj.Get()
- ret = []
- for s in s["routing"]:
- if s["sessid"] == sessid[1:]:
- ret.append({"Virtual Address": s["Virtual Address"],
- "Last Ref": s["Last Ref"],
- "Last Ref (time_t)": s["Last Ref (time_t)"]})
- if len(ret) == 0:
- raise Exception("Session ID not found")
- return ret
- #
- # M A I N F U N C T I O N
- #
- if __name__ == "__main__":
- print len(sys.argv)
- if len(sys.argv) != 2:
- print "Usage: %s <configuration file>" % sys.argv[0]
- sys.exit(1)
- # Parse config and do some very simple validation
- cfg = ConfigParser.ConfigParser()
- cfg.read(sys.argv[1])
- if not cfg.has_section("openvpn"):
- print "ERROR: Configuration file '%s' is missing the [openvpn] section." % sys.argv[1]
- sys.exit(1)
- # Connect to the TCP based management interface
- openvpn_mngr = OpenVPNmanagement_TCP(cfg.get("openvpn", "management_ip"),
- cfg.getint("openvpn", "management_port"),
- cfg.get("openvpn", "management_passwd")
- )
- # Try to login
- if not openvpn_mngr.Login():
- print "Failed to login into OpenVPN management interface!"
- sys.exit(2)
- print "Logged into OpenVPN management interface"
- # Configure our D-Bus main event loop
- DBusGMainLoop(set_as_default=True)
- bus_name = dbus.service.BusName('net.openvpn.management', bus=dbus.SessionBus())
- dbus_service = OpenVPNdbus_Server(openvpn_mngr)
- try:
- print "Starting DBus service"
- GLib.MainLoop().run()
- except KeyboardInterrupt:
- print("\nThe MainLoop will close...")
- GLib.MainLoop().quit()