@@ -19,6 +19,50 @@ def __str__(self):
19
19
return "%s (%s, line %s)" % (self .msg , self .filename , self .lineno )
20
20
21
21
22
+ class _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
+
22
66
class netrc :
23
67
def __init__ (self , file = None ):
24
68
default_netrc = file is None
@@ -34,9 +78,7 @@ def __init__(self, file=None):
34
78
self ._parse (file , fp , default_netrc )
35
79
36
80
def _parse (self , file , fp , default_netrc ):
37
- lexer = shlex .shlex (fp )
38
- lexer .wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
39
- lexer .commenters = lexer .commenters .replace ('#' , '' )
81
+ lexer = _netrclex (fp )
40
82
while 1 :
41
83
# Look for a machine, default, or macdef top-level keyword
42
84
saved_lineno = lexer .lineno
@@ -51,68 +93,75 @@ def _parse(self, file, fp, default_netrc):
51
93
entryname = lexer .get_token ()
52
94
elif tt == 'default' :
53
95
entryname = 'default'
54
- elif tt == 'macdef' : # Just skip to end of macdefs
96
+ elif tt == 'macdef' :
55
97
entryname = lexer .get_token ()
56
98
self .macros [entryname ] = []
57
- lexer .whitespace = ' \t '
58
99
while 1 :
59
100
line = lexer .instream .readline ()
60
- if not line or line == '\012 ' :
61
- lexer .whitespace = ' \t \r \n '
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.
62
109
break
63
110
self .macros [entryname ].append (line )
64
111
continue
65
112
else :
66
113
raise NetrcParseError (
67
114
"bad toplevel token %r" % tt , file , lexer .lineno )
68
115
116
+ if not entryname :
117
+ raise NetrcParseError ("missing %r name" % tt , file , lexer .lineno )
118
+
69
119
# We're looking at start of an entry for a named machine or default.
70
- login = ''
71
- account = password = None
120
+ login = account = password = ''
72
121
self .hosts [entryname ] = {}
73
122
while 1 :
123
+ prev_lineno = lexer .lineno
74
124
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 )
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
86
133
elif tt == 'login' or tt == 'user' :
87
134
login = lexer .get_token ()
88
135
elif tt == 'account' :
89
136
account = lexer .get_token ()
90
137
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
138
password = lexer .get_token ()
113
139
else :
114
140
raise NetrcParseError ("bad follower token %r" % tt ,
115
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" )
116
165
117
166
def authenticators (self , host ):
118
167
"""Return a (user, account, password) tuple for given host."""
0 commit comments