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