Source code for bronx.net.netrc

"""An object-oriented interface to .netrc files.

This code has been extracted from Python 3.5 and two patches have been applied:

* With this class, the user **login** is taken into account when looking for .netrc
  lines (see the **login** argument for the :func:`netrc.authenticators` method).
* Password can be surrounded with simple or double quotes (for compatibility
  with the standard Linux FTP client).

"""

# Module and documentation by Eric S. Raymond, 21 Dec 1998

# 09/02/2017
# Original version: Python 3.5
#                   Copyright 2001-2017 Python Software Foundation; All Rights Reserved
# Proposed patch applied: https://bugs.python.org/issue11416
# LFM: netrc._passwd_clean regex introduced to remove matching quotes

import os
import re
import shlex
import stat

#: Wildchar exports
__all__ = ["netrc", "NetrcParseError"]


[docs]class NetrcParseError(Exception): """ Exception raised by the :class:`netrc` class when syntactical errors are encountered in source text. Instances of this exception provide three interesting attributes: ``msg`` is a textual explanation of the error, ``filename`` is the name of the source file, and ``lineno`` gives the line number on which the error was found. """ def __init__(self, msg, filename=None, lineno=None): self.filename = filename self.lineno = lineno self.msg = msg Exception.__init__(self, msg) def __str__(self): return "{} ({}, line {})".format(self.msg, self.filename, self.lineno)
[docs]class netrc: """ A :class:`netrc` instance or subclass instance encapsulates data from a netrc file. """ _passwd_clean = re.compile(r'''^(?P<quote>["'])(?P<pass>.*)(?P=quote)$''') def __init__(self, file=None): """ The initialisation argument, if present, specifies the file to parse. If no argument is given, the file ``.netrc`` in the user's home directory will be read. Parse errors will raise :class:`NetrcParseError` with diagnostic information including the file name, line number, and terminating token. If no argument is specified on a POSIX system, the presence of passwords in the ``.netrc`` file will raise a :class:`NetrcParseError` if the file ownership or permissions are insecure (owned by a user other than the user running the process, or accessible for read or write by any other user). This implements security behavior equivalent to that of ftp and other programs that use ``.netrc``. .. attribute:: netrc.hosts Dictionary mapping host names to ``(login, account, password)`` tuples. The 'default' entry, if any, is represented as a pseudo-host by that name. """ default_netrc = file is None if file is None: try: file = os.path.join(os.environ['HOME'], ".netrc") except KeyError: raise OSError("Could not find .netrc: $HOME is not set") self.hosts = {} self.allhosts = {} self.macros = {} with open(file) as fp: self._parse(file, fp, default_netrc) def _parse(self, file, fp, default_netrc): lexer = shlex.shlex(fp) lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" lexer.commenters = lexer.commenters.replace('#', '') while 1: # Look for a machine, default, or macdef top-level keyword saved_lineno = lexer.lineno toplevel = tt = lexer.get_token() if not tt: break elif tt[0] == '#': if lexer.lineno == saved_lineno and len(tt) == 1: lexer.instream.readline() continue elif tt == 'machine': entryname = lexer.get_token() elif tt == 'default': entryname = 'default' elif tt == 'macdef': # Just skip to end of macdefs entryname = lexer.get_token() self.macros[entryname] = [] lexer.whitespace = ' \t' while 1: line = lexer.instream.readline() if not line or line == '\012': lexer.whitespace = ' \t\r\n' break self.macros[entryname].append(line) continue else: raise NetrcParseError( "bad toplevel token %r" % tt, file, lexer.lineno) # We're looking at start of an entry for a named machine or default. login = '' account = password = None self.allhosts.setdefault(entryname, []) self.hosts[entryname] = {} while 1: tt = lexer.get_token() if (tt.startswith('#') or tt in {'', 'machine', 'default', 'macdef'}): if password: current_entry = (login, account, password) self.allhosts[entryname].append(current_entry) self.hosts[entryname] = current_entry lexer.push_token(tt) break else: raise NetrcParseError( "malformed %s entry %s terminated by %s" % (toplevel, entryname, repr(tt)), file, lexer.lineno) elif tt == 'login' or tt == 'user': login = lexer.get_token() elif tt == 'account': account = lexer.get_token() elif tt == 'password': if os.name == 'posix' and default_netrc: prop = os.fstat(fp.fileno()) if prop.st_uid != os.getuid(): import pwd try: fowner = pwd.getpwuid(prop.st_uid)[0] except KeyError: fowner = 'uid %s' % prop.st_uid try: user = pwd.getpwuid(os.getuid())[0] except KeyError: user = 'uid %s' % os.getuid() raise NetrcParseError( ("~/.netrc file owner (%s) does not match" " current user (%s)") % (fowner, user), file, lexer.lineno) if prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO): raise NetrcParseError( "~/.netrc access too permissive: access" " permissions must restrict access to only" " the owner", file, lexer.lineno) password = self._passwd_clean.sub(r'\g<pass>', lexer.get_token()) else: raise NetrcParseError("bad follower token {!r} in {!r} line {}".format( tt, file, lexer.lineno))
[docs] def authenticators(self, host, login=None): """ Return a 3-tuple ``(login, account, password)`` of authenticators for host. If the netrc file did not contain an entry for the given host, return the tuple associated with the 'default' entry. If neither matching host nor default entry is available, return ``None``. When provided, the optional login argument selects a tuple with a matching user name. """ if host in self.hosts: if login is not None: for host_login in self.allhosts[host]: if host_login[0] == login: return host_login return None else: return self.hosts[host] elif 'default' in self.hosts: return self.hosts['default'] else: return None
def __repr__(self): """ Dump the class data as a string in the format of a netrc file. (This discards comments and may reorder the entries.) """ rep = "" for host, attrlist in self.allhosts.items(): for attrs in attrlist: rep = rep + "machine " + host + "\n\tlogin " + repr(attrs[0]) + "\n" if attrs[1]: rep = rep + "account " + repr(attrs[1]) rep = rep + "\tpassword " + repr(attrs[2]) + "\n" for macro in self.macros.keys(): rep = rep + "macdef " + macro + "\n" for line in self.macros[macro]: rep = rep + line rep = rep + "\n" return rep
if __name__ == '__main__': print(netrc())