Home | History | Annotate | Download | only in Lib
      1 """An object-oriented interface to .netrc files."""
      2 
      3 # Module and documentation by Eric S. Raymond, 21 Dec 1998
      4 
      5 import os, stat, shlex
      6 if os.name == 'posix':
      7     import pwd
      8 
      9 __all__ = ["netrc", "NetrcParseError"]
     10 
     11 
     12 class 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 
     24 class 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 
    144 if __name__ == '__main__':
    145     print netrc()
    146