netrc.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. """An object-oriented interface to .netrc files."""
  2. # Module and documentation by Eric S. Raymond, 21 Dec 1998
  3. import os, stat
  4. __all__ = ["netrc", "NetrcParseError"]
  5. class NetrcParseError(Exception):
  6. """Exception raised on syntax errors in the .netrc file."""
  7. def __init__(self, msg, filename=None, lineno=None):
  8. self.filename = filename
  9. self.lineno = lineno
  10. self.msg = msg
  11. Exception.__init__(self, msg)
  12. def __str__(self):
  13. return "%s (%s, line %s)" % (self.msg, self.filename, self.lineno)
  14. class _netrclex:
  15. def __init__(self, fp):
  16. self.lineno = 1
  17. self.instream = fp
  18. self.whitespace = "\n\t\r "
  19. self.pushback = []
  20. def _read_char(self):
  21. ch = self.instream.read(1)
  22. if ch == "\n":
  23. self.lineno += 1
  24. return ch
  25. def get_token(self):
  26. if self.pushback:
  27. return self.pushback.pop(0)
  28. token = ""
  29. fiter = iter(self._read_char, "")
  30. for ch in fiter:
  31. if ch in self.whitespace:
  32. continue
  33. if ch == '"':
  34. for ch in fiter:
  35. if ch == '"':
  36. return token
  37. elif ch == "\\":
  38. ch = self._read_char()
  39. token += ch
  40. else:
  41. if ch == "\\":
  42. ch = self._read_char()
  43. token += ch
  44. for ch in fiter:
  45. if ch in self.whitespace:
  46. return token
  47. elif ch == "\\":
  48. ch = self._read_char()
  49. token += ch
  50. return token
  51. def push_token(self, token):
  52. self.pushback.append(token)
  53. class netrc:
  54. def __init__(self, file=None):
  55. default_netrc = file is None
  56. if file is None:
  57. file = os.path.join(os.path.expanduser("~"), ".netrc")
  58. self.hosts = {}
  59. self.macros = {}
  60. try:
  61. with open(file, encoding="utf-8") as fp:
  62. self._parse(file, fp, default_netrc)
  63. except UnicodeDecodeError:
  64. with open(file, encoding="locale") as fp:
  65. self._parse(file, fp, default_netrc)
  66. def _parse(self, file, fp, default_netrc):
  67. lexer = _netrclex(fp)
  68. while 1:
  69. # Look for a machine, default, or macdef top-level keyword
  70. saved_lineno = lexer.lineno
  71. toplevel = tt = lexer.get_token()
  72. if not tt:
  73. break
  74. elif tt[0] == '#':
  75. if lexer.lineno == saved_lineno and len(tt) == 1:
  76. lexer.instream.readline()
  77. continue
  78. elif tt == 'machine':
  79. entryname = lexer.get_token()
  80. elif tt == 'default':
  81. entryname = 'default'
  82. elif tt == 'macdef':
  83. entryname = lexer.get_token()
  84. self.macros[entryname] = []
  85. while 1:
  86. line = lexer.instream.readline()
  87. if not line:
  88. raise NetrcParseError(
  89. "Macro definition missing null line terminator.",
  90. file, lexer.lineno)
  91. if line == '\n':
  92. # a macro definition finished with consecutive new-line
  93. # characters. The first \n is encountered by the
  94. # readline() method and this is the second \n.
  95. break
  96. self.macros[entryname].append(line)
  97. continue
  98. else:
  99. raise NetrcParseError(
  100. "bad toplevel token %r" % tt, file, lexer.lineno)
  101. if not entryname:
  102. raise NetrcParseError("missing %r name" % tt, file, lexer.lineno)
  103. # We're looking at start of an entry for a named machine or default.
  104. login = account = password = ''
  105. self.hosts[entryname] = {}
  106. while 1:
  107. prev_lineno = lexer.lineno
  108. tt = lexer.get_token()
  109. if tt.startswith('#'):
  110. if lexer.lineno == prev_lineno:
  111. lexer.instream.readline()
  112. continue
  113. if tt in {'', 'machine', 'default', 'macdef'}:
  114. self.hosts[entryname] = (login, account, password)
  115. lexer.push_token(tt)
  116. break
  117. elif tt == 'login' or tt == 'user':
  118. login = lexer.get_token()
  119. elif tt == 'account':
  120. account = lexer.get_token()
  121. elif tt == 'password':
  122. password = lexer.get_token()
  123. else:
  124. raise NetrcParseError("bad follower token %r" % tt,
  125. file, lexer.lineno)
  126. self._security_check(fp, default_netrc, self.hosts[entryname][0])
  127. def _security_check(self, fp, default_netrc, login):
  128. if os.name == 'posix' and default_netrc and login != "anonymous":
  129. prop = os.fstat(fp.fileno())
  130. if prop.st_uid != os.getuid():
  131. import pwd
  132. try:
  133. fowner = pwd.getpwuid(prop.st_uid)[0]
  134. except KeyError:
  135. fowner = 'uid %s' % prop.st_uid
  136. try:
  137. user = pwd.getpwuid(os.getuid())[0]
  138. except KeyError:
  139. user = 'uid %s' % os.getuid()
  140. raise NetrcParseError(
  141. (f"~/.netrc file owner ({fowner}, {user}) does not match"
  142. " current user"))
  143. if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
  144. raise NetrcParseError(
  145. "~/.netrc access too permissive: access"
  146. " permissions must restrict access to only"
  147. " the owner")
  148. def authenticators(self, host):
  149. """Return a (user, account, password) tuple for given host."""
  150. if host in self.hosts:
  151. return self.hosts[host]
  152. elif 'default' in self.hosts:
  153. return self.hosts['default']
  154. else:
  155. return None
  156. def __repr__(self):
  157. """Dump the class data in the format of a .netrc file."""
  158. rep = ""
  159. for host in self.hosts.keys():
  160. attrs = self.hosts[host]
  161. rep += f"machine {host}\n\tlogin {attrs[0]}\n"
  162. if attrs[1]:
  163. rep += f"\taccount {attrs[1]}\n"
  164. rep += f"\tpassword {attrs[2]}\n"
  165. for macro in self.macros.keys():
  166. rep += f"macdef {macro}\n"
  167. for line in self.macros[macro]:
  168. rep += line
  169. rep += "\n"
  170. return rep
  171. if __name__ == '__main__':
  172. print(netrc())