PageRenderTime 84ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/openvpn-dbus-poc-server.py

https://gitlab.com/dazo/openvpn-dbus-poc
Python | 351 lines | 193 code | 65 blank | 93 comment | 49 complexity | d71e517ed2eefeb04500d6944d3b927d MD5 | raw file
  1. #!/usr/bin/python2
  2. #
  3. # Simple and stupid Proof-of-Concept D-Bus server proxy for OpenVPN
  4. #
  5. # Copyright (C) 2016 David Sommerseth <davids@openvpn.net>
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License
  9. # as published by the Free Software Foundation; either version 2
  10. # of the License.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. #
  21. #
  22. # !!!!! THIS IS PROOF-OF-CONCEPT CODE !!!!!
  23. #
  24. # That means this is NOT intended for production usage at all.
  25. # This code is not pretty, it is hacky and it will have bugs
  26. #
  27. # This code does also NOT guarantee the D-Bus methods, signals
  28. # and properties will change later on.
  29. #
  30. #
  31. # HOW CAN I TEST IT?
  32. #
  33. # This D-Bus server proxy works as a bridge between the
  34. # TCP based OpenVPN management interface on the server side
  35. # and provides services over the D-Bus. Any D-Bus client can
  36. # introspect and call methods provided by this service by
  37. # connecting to the net.openvpn.management D-Bus destination.
  38. #
  39. # Requirements for this to work:
  40. #
  41. # - A functional OpenVPN server configured with --management
  42. #
  43. # - A dbus-proxy configuration file consisting of *three* options:
  44. # management_ip, management_port and management_password
  45. # See example below. Obviously enough, this information must
  46. # match the OpenVPN values provided to --management. A password
  47. # is required.
  48. #
  49. # - Starting this proxy with the configuration file name
  50. # as the only argument:
  51. #
  52. # $ python ./openvpn-dbus-poc-server.py myconfig.ini
  53. #
  54. # - Once running, first run some tests using the PoC client
  55. # from a different shell
  56. #
  57. # $ python ./openvpn-dbus-poc-client.py
  58. #
  59. # - Run some gdbus tests:
  60. #
  61. # $ gdbus introspect --session -d net.openvpn.management \
  62. # -o /net/openvpn/management
  63. #
  64. # $ gdbus call --session -d net.openvpn.management \
  65. # -o /net/openvpn/management \
  66. # -m net.openvpn.management.server.GetStatus
  67. #
  68. # The functions in the net.openvpn.management.sessions interface
  69. # requires the object path (-o) to be extended with the session ID
  70. # returned by net.openvpn.management.server.GetActiveSessions
  71. #
  72. #
  73. # Example for myconfig.ini:
  74. #
  75. # [openvpn]
  76. # management_ip: 127.0.0.1
  77. # management_port: 2294
  78. # management_password: MY_BR3AKABL3_M4NAG3MENT_PSWD
  79. #
  80. #
  81. import sys
  82. import ConfigParser
  83. import socket
  84. import hashlib
  85. from gi.repository import GLib
  86. import dbus
  87. import dbus.service
  88. from dbus.mainloop.glib import DBusGMainLoop
  89. #
  90. # OpenVPN TCP based Management Interface glue
  91. #
  92. MANAGEMENTBUFLEN = 8192
  93. class OpenVPN_serverstatus(object):
  94. def __init__(self, data):
  95. self.__status = {"sessions": [], "routing": []}
  96. columns = {}
  97. for line in data:
  98. rec = [e.strip() for e in line.split('\t')]
  99. if rec[0] == "TITLE":
  100. s = rec[1].split(' ')
  101. self.__status["version"] = s[1]
  102. self.__status["build"] = s[2]
  103. elif rec[0] == "TIME":
  104. self.__status["updated"] = rec[1]
  105. self.__status["uptime"] = rec[2]
  106. elif rec[0] == "HEADER":
  107. columns[rec[1]] = rec[2:]
  108. elif rec[0] == "CLIENT_LIST":
  109. self.__status["sessions"].append(self.__parse_columns(columns, rec))
  110. elif rec[0] == "ROUTING_TABLE":
  111. self.__status["routing"].append(self.__parse_columns(columns, rec))
  112. elif rec[0] == "END":
  113. break
  114. def Get(self):
  115. return self.__status
  116. def __parse_columns(self, columns, rec):
  117. i = 0
  118. cl = {}
  119. reckey = None
  120. for r in rec[1:]:
  121. cl[columns[rec[0]][i]] = r
  122. i += 1
  123. if rec[0] == "CLIENT_LIST" or rec[0] == "ROUTING_TABLE":
  124. sessid = "%s%s" % (cl["Common Name"], cl["Real Address"])
  125. cl["sessid"] = hashlib.sha256(sessid).hexdigest()
  126. if rec[0] == "ROUTING_TABLE":
  127. routid = "%s%s%s%s" % (
  128. rec[0] == "CLIENT_LIST" and "CL" or "RT",
  129. cl["Common Name"], cl["Real Address"], cl["Virtual Address"])
  130. cl["routid"] = hashlib.sha256(routid).hexdigest()
  131. return cl
  132. class OpenVPNmanagement_TCP(object):
  133. def __init__(self, host, port, passwd = None):
  134. self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  135. self.__sock.connect((host, port))
  136. self.__passwd = passwd
  137. self.__loggedin = False
  138. def Login(self):
  139. if not self.__login(self.__passwd):
  140. self.__passwd = None
  141. return False
  142. self.__passwd = None
  143. self.__loggedin = True
  144. return True
  145. def GetPID(self):
  146. result, data = self.__communicate("pid")
  147. if result == "SUCCESS":
  148. pid = data[0].split("=")[1]
  149. return int(pid) > 0 and int(pid) or False
  150. else:
  151. return False
  152. def GetStatusObj(self):
  153. result, tdata = self.__communicate("status 3")
  154. data = list(tdata)
  155. data[0] = result
  156. status = OpenVPN_serverstatus(data)
  157. return status
  158. def __login(self, passwd):
  159. if self.__loggedin:
  160. return True
  161. data = self.__sock.recv(MANAGEMENTBUFLEN)
  162. if data == "ENTER PASSWORD:":
  163. self.__sock.send(passwd + "\n")
  164. else:
  165. return True
  166. data = self.__sock.recv(MANAGEMENTBUFLEN)
  167. if data.split(':')[0] == "SUCCESS":
  168. return True
  169. else:
  170. return False
  171. def __communicate(self, command):
  172. if not self.__loggedin:
  173. raise Exception("Not logged in")
  174. retdata = None
  175. result = None
  176. self.__sock.send(command + "\n")
  177. rawdata = self.__sock.recv(MANAGEMENTBUFLEN)
  178. if len(rawdata) > 0:
  179. linecount = 0
  180. for line in [l.strip() for l in rawdata.strip().split("\n")]:
  181. linecount += 1
  182. if linecount == 1:
  183. data = [l.strip() for l in line.split(':')]
  184. result = data[0]
  185. retdata = [":".join(data[1:]),]
  186. else:
  187. retdata.append(line)
  188. return (result, tuple(retdata))
  189. #
  190. # OpenVPN server side D-Bus interface
  191. #
  192. class OpenVPNdbus_Server(dbus.service.FallbackObject):
  193. def __init__(self, ovpnmgr):
  194. self.__ovpnmgr = ovpnmgr
  195. dbus.service.FallbackObject.__init__(self, dbus.SessionBus(), '/net/openvpn/management')
  196. print "OpenVPNdbus_Server initialized"
  197. @dbus.service.method('net.openvpn.management')
  198. def GetPID(self):
  199. pid = self.__ovpnmgr.GetPID()
  200. if pid:
  201. return int(pid)
  202. else:
  203. raise Exception("Failed to query OpenVPN server for its PID")
  204. @dbus.service.method(dbus_interface='net.openvpn.management.server',
  205. out_signature="v")
  206. def GetStatus(self):
  207. statobj = self.__ovpnmgr.GetStatusObj()
  208. if not statobj:
  209. raise Exception("Failed to query OpenVPN management interface")
  210. s = statobj.Get()
  211. return {"build": s["build"],
  212. "status_timestamp": s["updated"],
  213. "uptime": s["uptime"],
  214. "version": s["version"]}
  215. @dbus.service.method(dbus_interface='net.openvpn.management.server',
  216. out_signature="v")
  217. def GetActiveSessions(self):
  218. statobj = self.__ovpnmgr.GetStatusObj()
  219. if not statobj:
  220. raise Exception("Failed to query OpenVPN management interface")
  221. s = statobj.Get()
  222. ret = []
  223. for client in s["sessions"]:
  224. ret.append(client["sessid"])
  225. return ret
  226. @dbus.service.method(dbus_interface='net.openvpn.management.sessions',
  227. out_signature="v", rel_path_keyword="sessid",)
  228. def GetSessionInfo(self, sessid):
  229. statobj = self.__ovpnmgr.GetStatusObj()
  230. if not statobj:
  231. raise Exception("Failed to query OPenVPN management interface")
  232. s = statobj.Get()
  233. for s in s["sessions"]:
  234. if s["sessid"] == sessid[1:]:
  235. return s
  236. raise Exception("Session ID not found")
  237. @dbus.service.method(dbus_interface='net.openvpn.management.sessions',
  238. out_signature="v", rel_path_keyword="sessid",)
  239. def GetRouting(self, sessid):
  240. statobj = self.__ovpnmgr.GetStatusObj()
  241. if not statobj:
  242. raise Exception("Failed to query OPenVPN management interface")
  243. s = statobj.Get()
  244. ret = []
  245. for s in s["routing"]:
  246. if s["sessid"] == sessid[1:]:
  247. ret.append({"Virtual Address": s["Virtual Address"],
  248. "Last Ref": s["Last Ref"],
  249. "Last Ref (time_t)": s["Last Ref (time_t)"]})
  250. if len(ret) == 0:
  251. raise Exception("Session ID not found")
  252. return ret
  253. #
  254. # M A I N F U N C T I O N
  255. #
  256. if __name__ == "__main__":
  257. print len(sys.argv)
  258. if len(sys.argv) != 2:
  259. print "Usage: %s <configuration file>" % sys.argv[0]
  260. sys.exit(1)
  261. # Parse config and do some very simple validation
  262. cfg = ConfigParser.ConfigParser()
  263. cfg.read(sys.argv[1])
  264. if not cfg.has_section("openvpn"):
  265. print "ERROR: Configuration file '%s' is missing the [openvpn] section." % sys.argv[1]
  266. sys.exit(1)
  267. # Connect to the TCP based management interface
  268. openvpn_mngr = OpenVPNmanagement_TCP(cfg.get("openvpn", "management_ip"),
  269. cfg.getint("openvpn", "management_port"),
  270. cfg.get("openvpn", "management_passwd")
  271. )
  272. # Try to login
  273. if not openvpn_mngr.Login():
  274. print "Failed to login into OpenVPN management interface!"
  275. sys.exit(2)
  276. print "Logged into OpenVPN management interface"
  277. # Configure our D-Bus main event loop
  278. DBusGMainLoop(set_as_default=True)
  279. bus_name = dbus.service.BusName('net.openvpn.management', bus=dbus.SessionBus())
  280. dbus_service = OpenVPNdbus_Server(openvpn_mngr)
  281. try:
  282. print "Starting DBus service"
  283. GLib.MainLoop().run()
  284. except KeyboardInterrupt:
  285. print("\nThe MainLoop will close...")
  286. GLib.MainLoop().quit()