inetdconf.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. # -*- test-case-name: twisted.runner.test.test_inetdconf -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Parser for inetd.conf files
  6. """
  7. from typing import Optional
  8. # Various exceptions
  9. class InvalidConfError(Exception):
  10. """
  11. Invalid configuration file
  12. """
  13. class InvalidInetdConfError(InvalidConfError):
  14. """
  15. Invalid inetd.conf file
  16. """
  17. class InvalidServicesConfError(InvalidConfError):
  18. """
  19. Invalid services file
  20. """
  21. class UnknownService(Exception):
  22. """
  23. Unknown service name
  24. """
  25. class SimpleConfFile:
  26. """
  27. Simple configuration file parser superclass.
  28. Filters out comments and empty lines (which includes lines that only
  29. contain comments).
  30. To use this class, override parseLine or parseFields.
  31. """
  32. commentChar = "#"
  33. defaultFilename: Optional[str] = None
  34. def parseFile(self, file=None):
  35. """
  36. Parse a configuration file
  37. If file is None and self.defaultFilename is set, it will open
  38. defaultFilename and use it.
  39. """
  40. close = False
  41. if file is None and self.defaultFilename:
  42. file = open(self.defaultFilename)
  43. close = True
  44. try:
  45. for line in file.readlines():
  46. # Strip out comments
  47. comment = line.find(self.commentChar)
  48. if comment != -1:
  49. line = line[:comment]
  50. # Strip whitespace
  51. line = line.strip()
  52. # Skip empty lines (and lines which only contain comments)
  53. if not line:
  54. continue
  55. self.parseLine(line)
  56. finally:
  57. if close:
  58. file.close()
  59. def parseLine(self, line):
  60. """
  61. Override this.
  62. By default, this will split the line on whitespace and call
  63. self.parseFields (catching any errors).
  64. """
  65. try:
  66. self.parseFields(*line.split())
  67. except ValueError:
  68. raise InvalidInetdConfError("Invalid line: " + repr(line))
  69. def parseFields(self, *fields):
  70. """
  71. Override this.
  72. """
  73. class InetdService:
  74. """
  75. A simple description of an inetd service.
  76. """
  77. name = None
  78. port = None
  79. socketType = None
  80. protocol = None
  81. wait = None
  82. user = None
  83. group = None
  84. program = None
  85. programArgs = None
  86. def __init__(
  87. self, name, port, socketType, protocol, wait, user, group, program, programArgs
  88. ):
  89. self.name = name
  90. self.port = port
  91. self.socketType = socketType
  92. self.protocol = protocol
  93. self.wait = wait
  94. self.user = user
  95. self.group = group
  96. self.program = program
  97. self.programArgs = programArgs
  98. class InetdConf(SimpleConfFile):
  99. """
  100. Configuration parser for a traditional UNIX inetd(8)
  101. """
  102. defaultFilename = "/etc/inetd.conf"
  103. def __init__(self, knownServices=None):
  104. self.services = []
  105. if knownServices is None:
  106. knownServices = ServicesConf()
  107. knownServices.parseFile()
  108. self.knownServices = knownServices
  109. def parseFields(
  110. self, serviceName, socketType, protocol, wait, user, program, *programArgs
  111. ):
  112. """
  113. Parse an inetd.conf file.
  114. Implemented from the description in the Debian inetd.conf man page.
  115. """
  116. # Extract user (and optional group)
  117. user, group = (user.split(".") + [None])[:2]
  118. # Find the port for a service
  119. port = self.knownServices.services.get((serviceName, protocol), None)
  120. if not port and not protocol.startswith("rpc/"):
  121. # FIXME: Should this be discarded/ignored, rather than throwing
  122. # an exception?
  123. try:
  124. port = int(serviceName)
  125. serviceName = "unknown"
  126. except BaseException:
  127. raise UnknownService(f"Unknown service: {serviceName} ({protocol})")
  128. self.services.append(
  129. InetdService(
  130. serviceName,
  131. port,
  132. socketType,
  133. protocol,
  134. wait,
  135. user,
  136. group,
  137. program,
  138. programArgs,
  139. )
  140. )
  141. class ServicesConf(SimpleConfFile):
  142. """
  143. /etc/services parser
  144. @ivar services: dict mapping service names to (port, protocol) tuples.
  145. """
  146. defaultFilename = "/etc/services"
  147. def __init__(self):
  148. self.services = {}
  149. def parseFields(self, name, portAndProtocol, *aliases):
  150. try:
  151. port, protocol = portAndProtocol.split("/")
  152. port = int(port)
  153. except BaseException:
  154. raise InvalidServicesConfError(
  155. f"Invalid port/protocol: {repr(portAndProtocol)}"
  156. )
  157. self.services[(name, protocol)] = port
  158. for alias in aliases:
  159. self.services[(alias, protocol)] = port