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