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