sydent.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014 OpenMarket Ltd
  3. # Copyright 2018 New Vector Ltd
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import ConfigParser
  17. import logging
  18. import logging.handlers
  19. import os
  20. import twisted.internet.reactor
  21. from twisted.python import log
  22. from db.sqlitedb import SqliteDatabase
  23. from http.httpcommon import SslComponents
  24. from http.httpserver import (
  25. ClientApiHttpServer, ReplicationHttpsServer,
  26. InternalApiHttpServer,
  27. )
  28. from http.httpsclient import ReplicationHttpsClient
  29. from http.servlets.blindlysignstuffservlet import BlindlySignStuffServlet
  30. from http.servlets.pubkeyservlets import EphemeralPubkeyIsValidServlet, PubkeyIsValidServlet
  31. from validators.emailvalidator import EmailValidator
  32. from validators.msisdnvalidator import MsisdnValidator
  33. from hs_federation.verifier import Verifier
  34. from sign.ed25519 import SydentEd25519
  35. from http.servlets.emailservlet import EmailRequestCodeServlet, EmailValidateCodeServlet
  36. from http.servlets.msisdnservlet import MsisdnRequestCodeServlet, MsisdnValidateCodeServlet
  37. from http.servlets.lookupservlet import LookupServlet
  38. from http.servlets.bulklookupservlet import BulkLookupServlet
  39. from http.servlets.pubkeyservlets import Ed25519Servlet
  40. from http.servlets.threepidbindservlet import ThreePidBindServlet
  41. from http.servlets.threepidunbindservlet import ThreePidUnbindServlet
  42. from http.servlets.replication import ReplicationPushServlet
  43. from http.servlets.getvalidated3pidservlet import GetValidated3pidServlet
  44. from http.servlets.store_invite_servlet import StoreInviteServlet
  45. from http.servlets.v1_servlet import V1Servlet
  46. from threepid.bind import ThreepidBinder
  47. from replication.pusher import Pusher
  48. logger = logging.getLogger(__name__)
  49. CONFIG_DEFAULTS = {
  50. 'general': {
  51. 'server.name': '',
  52. 'log.path': '',
  53. 'pidfile.path': 'sydent.pid',
  54. },
  55. 'db': {
  56. 'db.file': 'sydent.db',
  57. },
  58. 'http': {
  59. 'clientapi.http.port': '8090',
  60. 'internalapi.http.port': '',
  61. 'replication.https.certfile': '',
  62. 'replication.https.cacert': '', # This should only be used for testing
  63. 'replication.https.port': '4434',
  64. 'obey_x_forwarded_for': 'False',
  65. },
  66. 'email': {
  67. 'email.template': 'res/email.template',
  68. 'email.from': 'Sydent Validation <noreply@{hostname}>',
  69. 'email.subject': 'Your Validation Token',
  70. 'email.invite.subject': '%(sender_display_name)s has invited you to chat',
  71. 'email.smtphost': 'localhost',
  72. 'email.smtpport': '25',
  73. 'email.smtpusername': '',
  74. 'email.smtppassword': '',
  75. 'email.hostname': '',
  76. 'email.tlsmode': '0',
  77. },
  78. 'sms': {
  79. 'bodyTemplate': 'Your code is {token}',
  80. },
  81. 'crypto': {
  82. 'ed25519.signingkey': '',
  83. },
  84. }
  85. class Sydent:
  86. def __init__(self):
  87. self.config_file = os.environ.get('SYDENT_CONF', "sydent.conf")
  88. self.cfg = parse_config(self.config_file)
  89. log_format = (
  90. "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s"
  91. " - %(message)s"
  92. )
  93. formatter = logging.Formatter(log_format)
  94. logPath = self.cfg.get('general', "log.path")
  95. if logPath != '':
  96. handler = logging.handlers.TimedRotatingFileHandler(
  97. logPath, when='midnight', backupCount=365
  98. )
  99. handler.setFormatter(formatter)
  100. def sighup(signum, stack):
  101. logger.info("Closing log file due to SIGHUP")
  102. handler.doRollover()
  103. logger.info("Opened new log file due to SIGHUP")
  104. else:
  105. handler = logging.StreamHandler()
  106. handler.setFormatter(formatter)
  107. rootLogger = logging.getLogger('')
  108. rootLogger.setLevel(logging.INFO)
  109. rootLogger.addHandler(handler)
  110. logger.info("Starting Sydent server")
  111. self.pidfile = self.cfg.get('general', "pidfile.path");
  112. observer = log.PythonLoggingObserver()
  113. observer.start()
  114. self.db = SqliteDatabase(self).db
  115. self.server_name = self.cfg.get('general', 'server.name')
  116. if self.server_name == '':
  117. self.server_name = os.uname()[1]
  118. logger.warn(("You had not specified a server name. I have guessed that this server is called '%s' "
  119. + " and saved this in the config file. If this is incorrect, you should edit server.name in "
  120. + "the config file.") % (self.server_name,))
  121. self.cfg.set('general', 'server.name', self.server_name)
  122. self.save_config()
  123. self.validators = Validators()
  124. self.validators.email = EmailValidator(self)
  125. self.validators.msisdn = MsisdnValidator(self)
  126. self.keyring = Keyring()
  127. self.keyring.ed25519 = SydentEd25519(self).signing_key
  128. self.keyring.ed25519.alg = 'ed25519'
  129. self.sig_verifier = Verifier(self)
  130. self.servlets = Servlets()
  131. self.servlets.v1 = V1Servlet(self)
  132. self.servlets.emailRequestCode = EmailRequestCodeServlet(self)
  133. self.servlets.emailValidate = EmailValidateCodeServlet(self)
  134. self.servlets.msisdnRequestCode = MsisdnRequestCodeServlet(self)
  135. self.servlets.msisdnValidate = MsisdnValidateCodeServlet(self)
  136. self.servlets.lookup = LookupServlet(self)
  137. self.servlets.bulk_lookup = BulkLookupServlet(self)
  138. self.servlets.pubkey_ed25519 = Ed25519Servlet(self)
  139. self.servlets.pubkeyIsValid = PubkeyIsValidServlet(self)
  140. self.servlets.ephemeralPubkeyIsValid = EphemeralPubkeyIsValidServlet(self)
  141. self.servlets.threepidBind = ThreePidBindServlet(self)
  142. self.servlets.threepidUnbind = ThreePidUnbindServlet(self)
  143. self.servlets.replicationPush = ReplicationPushServlet(self)
  144. self.servlets.getValidated3pid = GetValidated3pidServlet(self)
  145. self.servlets.storeInviteServlet = StoreInviteServlet(self)
  146. self.servlets.blindlySignStuffServlet = BlindlySignStuffServlet(self)
  147. self.threepidBinder = ThreepidBinder(self)
  148. self.sslComponents = SslComponents(self)
  149. self.clientApiHttpServer = ClientApiHttpServer(self)
  150. self.replicationHttpsServer = ReplicationHttpsServer(self)
  151. self.replicationHttpsClient = ReplicationHttpsClient(self)
  152. self.pusher = Pusher(self)
  153. def save_config(self):
  154. fp = open(self.config_file, 'w')
  155. self.cfg.write(fp)
  156. fp.close()
  157. def run(self):
  158. self.clientApiHttpServer.setup()
  159. self.replicationHttpsServer.setup()
  160. self.pusher.setup()
  161. internalport = self.cfg.get('http', 'internalapi.http.port')
  162. if internalport:
  163. try:
  164. interface = self.cfg.get('http', 'internalapi.http.bind_address')
  165. except ConfigParser.NoOptionError:
  166. interface = '::1'
  167. self.internalApiHttpServer = InternalApiHttpServer(self)
  168. self.internalApiHttpServer.setup(interface, int(internalport))
  169. if self.pidfile:
  170. with open(self.pidfile, 'w') as pidfile:
  171. pidfile.write(str(os.getpid()) + "\n")
  172. twisted.internet.reactor.run()
  173. def ip_from_request(self, request):
  174. if (self.cfg.get('http', 'obey_x_forwarded_for') and
  175. request.requestHeaders.hasHeader("X-Forwarded-For")):
  176. return request.requestHeaders.getRawHeaders("X-Forwarded-For")[0]
  177. return request.getClientIP()
  178. class Validators:
  179. pass
  180. class Servlets:
  181. pass
  182. class Keyring:
  183. pass
  184. def parse_config(config_file):
  185. """Parse the given config file, populating missing items and sections
  186. Args:
  187. config_file (str): the file to be parsed
  188. """
  189. cfg = ConfigParser.SafeConfigParser()
  190. # if the config file doesn't exist, prepopulate the config object
  191. # with the defaults, in the right section.
  192. if not os.path.exists(config_file):
  193. for sect, entries in CONFIG_DEFAULTS.items():
  194. cfg.add_section(sect)
  195. for k, v in entries.items():
  196. cfg.set(sect, k, v)
  197. else:
  198. # otherwise, we have to put the defaults in the DEFAULT section,
  199. # to ensure that they don't override anyone's settings which are
  200. # in their config file in the default section (which is likely,
  201. # because sydent used to be braindead).
  202. for sect, entries in CONFIG_DEFAULTS.items():
  203. cfg.add_section(sect)
  204. for k, v in entries.items():
  205. cfg.set(ConfigParser.DEFAULTSECT, k, v)
  206. cfg.read(config_file)
  207. return cfg
  208. if __name__ == '__main__':
  209. syd = Sydent()
  210. syd.run()