trigger 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. trigger/__init__.py +7 -0
  2. trigger/acl/__init__.py +32 -0
  3. trigger/acl/autoacl.py +70 -0
  4. trigger/acl/db.py +324 -0
  5. trigger/acl/dicts.py +357 -0
  6. trigger/acl/grammar.py +112 -0
  7. trigger/acl/ios.py +222 -0
  8. trigger/acl/junos.py +422 -0
  9. trigger/acl/models.py +118 -0
  10. trigger/acl/parser.py +168 -0
  11. trigger/acl/queue.py +296 -0
  12. trigger/acl/support.py +1431 -0
  13. trigger/acl/tools.py +746 -0
  14. trigger/bin/__init__.py +0 -0
  15. trigger/bin/acl.py +233 -0
  16. trigger/bin/acl_script.py +574 -0
  17. trigger/bin/aclconv.py +82 -0
  18. trigger/bin/check_access.py +93 -0
  19. trigger/bin/check_syntax.py +66 -0
  20. trigger/bin/fe.py +197 -0
  21. trigger/bin/find_access.py +191 -0
  22. trigger/bin/gnng.py +434 -0
  23. trigger/bin/gong.py +86 -0
  24. trigger/bin/load_acl.py +841 -0
  25. trigger/bin/load_config.py +18 -0
  26. trigger/bin/netdev.py +317 -0
  27. trigger/bin/optimizer.py +638 -0
  28. trigger/bin/run_cmds.py +18 -0
  29. trigger/changemgmt/__init__.py +352 -0
  30. trigger/changemgmt/bounce.py +57 -0
  31. trigger/cmds.py +1217 -0
  32. trigger/conf/__init__.py +94 -0
  33. trigger/conf/global_settings.py +674 -0
  34. trigger/contrib/__init__.py +7 -0
  35. trigger/exceptions.py +307 -0
  36. trigger/gorc.py +172 -0
  37. trigger/netdevices/__init__.py +1288 -0
  38. trigger/netdevices/loader.py +174 -0
  39. trigger/netscreen.py +1030 -0
  40. trigger/packages/__init__.py +6 -0
  41. trigger/packages/peewee.py +8084 -0
  42. trigger/rancid.py +463 -0
  43. trigger/tacacsrc.py +584 -0
  44. trigger/twister.py +2203 -0
  45. trigger/twister2.py +745 -0
  46. trigger/utils/__init__.py +88 -0
  47. trigger/utils/cli.py +349 -0
  48. trigger/utils/importlib.py +77 -0
  49. trigger/utils/network.py +157 -0
  50. trigger/utils/rcs.py +178 -0
  51. trigger/utils/templates.py +81 -0
  52. trigger/utils/url.py +78 -0
  53. trigger/utils/xmltodict.py +298 -0
  54. trigger-2.0.0.dist-info/METADATA +146 -0
  55. trigger-2.0.0.dist-info/RECORD +61 -0
  56. trigger-2.0.0.dist-info/WHEEL +5 -0
  57. trigger-2.0.0.dist-info/entry_points.txt +15 -0
  58. trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
  59. trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
  60. trigger-2.0.0.dist-info/top_level.txt +2 -0
  61. twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/tacacsrc.py ADDED
@@ -0,0 +1,584 @@
1
+ """Abstract interface to .tacacsrc credentials file.
2
+
3
+ Designed to interoperate with the legacy DeviceV2 implementation, but
4
+ provide a reasonable API on top of that. The name and format of the
5
+ .tacacsrc file are not ideal, but compatibility matters.
6
+ """
7
+
8
+ __author__ = "Jathan McCollum, Mark Thomas, Michael Shields"
9
+ __maintainer__ = "Jathan McCollum"
10
+ __email__ = "jmccollum@salesforce.com"
11
+ __copyright__ = "Copyright 2006-2012, AOL Inc.; 2013 Salesforce.com"
12
+
13
+ import getpass
14
+ import os
15
+ import pwd
16
+ import sys
17
+ from base64 import decodebytes as decodestring
18
+ from base64 import encodebytes as encodestring
19
+ from collections import namedtuple
20
+ from time import localtime, strftime
21
+
22
+ from cryptography.hazmat.backends.openssl import backend as openssl_backend
23
+ from cryptography.hazmat.primitives import ciphers
24
+
25
+ # Python 3: distutils deprecated, use packaging instead
26
+ from packaging.version import Version
27
+ from twisted.python import log
28
+
29
+ from trigger.conf import settings
30
+
31
+ # Python 3 / cryptography 48+: TripleDES moved to decrepit module
32
+ try:
33
+ from cryptography.hazmat.decrepit.ciphers.algorithms import TripleDES
34
+ except ImportError:
35
+ from cryptography.hazmat.primitives.ciphers.algorithms import TripleDES
36
+
37
+ # Exports
38
+ __all__ = (
39
+ "get_device_password",
40
+ "prompt_credentials",
41
+ "convert_tacacsrc",
42
+ "update_credentials",
43
+ "validate_credentials",
44
+ "Credentials",
45
+ "Tacacsrc",
46
+ )
47
+
48
+ # Credential object stored in Tacacsrc.creds
49
+ Credentials = namedtuple("Credentials", "username password realm")
50
+
51
+
52
+ # Exceptions
53
+ class TacacsrcError(Exception):
54
+ pass
55
+
56
+
57
+ class CouldNotParse(TacacsrcError):
58
+ pass
59
+
60
+
61
+ class MissingPassword(TacacsrcError):
62
+ pass
63
+
64
+
65
+ class MissingRealmName(TacacsrcError):
66
+ pass
67
+
68
+
69
+ class VersionMismatch(TacacsrcError):
70
+ pass
71
+
72
+
73
+ # Functions
74
+ def get_device_password(device=None, tcrc=None):
75
+ """
76
+ Fetch the password for a device/realm or create a new entry for it.
77
+ If device is not passed, ``settings.DEFAULT_REALM`` is used, which is default
78
+ realm for most devices.
79
+
80
+ :param device:
81
+ Realm or device name to updated
82
+
83
+ :param device:
84
+ Optional `~trigger.tacacsrc.Tacacsrc` instance
85
+ """
86
+ if tcrc is None:
87
+ tcrc = Tacacsrc()
88
+
89
+ # If device isn't passed, assume we are initializing the .tacacsrc.
90
+ try:
91
+ creds = tcrc.creds[device]
92
+ except KeyError:
93
+ print(f"\nCredentials not found for device/realm {device!r}, prompting...")
94
+ creds = prompt_credentials(device)
95
+ tcrc.creds[device] = creds
96
+ tcrc.write()
97
+
98
+ return creds
99
+
100
+
101
+ def prompt_credentials(device, user=None):
102
+ """
103
+ Prompt for username, password and return them as Credentials namedtuple.
104
+
105
+ :param device: Device or realm name to store
106
+ :param user: (Optional) If set, use as default username
107
+ """
108
+ if not device:
109
+ raise MissingRealmName("You must specify a device/realm name.")
110
+
111
+ creds = ()
112
+ # Make sure we can even get tty i/o!
113
+ if sys.stdin.isatty() and sys.stdout.isatty():
114
+ print(f"\nUpdating credentials for device/realm {device!r}")
115
+
116
+ user_default = ""
117
+ if user:
118
+ user_default = f" [{user}]"
119
+
120
+ username = input(f"Username{user_default}: ") or user
121
+ if username == "":
122
+ print("\nYou must specify a username, try again!")
123
+ return prompt_credentials(device, user=user)
124
+
125
+ passwd = getpass.getpass("Password: ")
126
+ passwd2 = getpass.getpass("Password (again): ")
127
+ if not passwd:
128
+ print("\nPassword cannot be blank, try again!")
129
+ return prompt_credentials(device, user=username)
130
+
131
+ if passwd != passwd2:
132
+ print("\nPasswords did not match, try again!")
133
+ return prompt_credentials(device, user=username)
134
+
135
+ creds = Credentials(username, passwd, device)
136
+
137
+ return creds
138
+
139
+
140
+ def update_credentials(device, username=None):
141
+ """
142
+ Update the credentials for a given device/realm. Assumes the same username
143
+ that is already cached unless it is passed.
144
+
145
+ This may seem redundant at first compared to Tacacsrc.update_creds() but we
146
+ need this factored out so that we don't end up with a race condition when
147
+ credentials are messed up.
148
+
149
+ Returns True if it actually updated something or None if it didn't.
150
+
151
+ :param device: Device or realm name to update
152
+ :param username: Username for credentials
153
+ """
154
+ tcrc = Tacacsrc()
155
+ if tcrc.creds_updated:
156
+ return None
157
+
158
+ mycreds = tcrc.creds.get(device, tcrc.creds[settings.DEFAULT_REALM])
159
+ if username is None:
160
+ username = mycreds.username
161
+
162
+ tcrc.update_creds(tcrc.creds, mycreds.realm, username)
163
+ tcrc.write()
164
+
165
+ return True
166
+
167
+
168
+ def validate_credentials(creds=None):
169
+ """
170
+ Given a set of credentials, try to return a `~trigger.tacacsrc.Credentials`
171
+ object.
172
+
173
+ If ``creds`` is unset it will fetch from ``.tacacsrc``.
174
+
175
+ Expects either a 2-tuple of (username, password) or a 3-tuple of (username,
176
+ password, realm). If only (username, password) are provided, realm will be populated from
177
+ :setting:`DEFAULT_REALM`.
178
+
179
+ :param creds:
180
+ A tuple of credentials.
181
+
182
+ """
183
+ realm = settings.DEFAULT_REALM
184
+
185
+ # If it isn't set or it's a string, or less than 1 or more than 3 items,
186
+ # get from .tacacsrc
187
+ if (not creds) or (type(creds) == str) or (len(creds) not in (2, 3)):
188
+ log.msg("Creds not valid, fetching from .tacacsrc...")
189
+ tcrc = Tacacsrc()
190
+ return tcrc.creds.get(realm, get_device_password(realm, tcrc))
191
+
192
+ # If it's a dict, get the values
193
+ if hasattr(creds, "values"):
194
+ log.msg("Creds is a dict, converting to values...")
195
+ creds = creds.values()
196
+
197
+ # If it's missing realm, add it.
198
+ if len(creds) == 2:
199
+ log.msg("Creds is a 2-tuple, making into namedtuple...")
200
+ username, password = creds
201
+ return Credentials(username, password, realm)
202
+
203
+ # Or just make it go...
204
+ elif len(creds) == 3:
205
+ log.msg("Creds is a 3-tuple, making into namedtuple...")
206
+ return Credentials(*creds)
207
+
208
+ raise RuntimeError("THIS SHOULD NOT HAVE HAPPENED!!")
209
+
210
+
211
+ def convert_tacacsrc():
212
+ """Converts old .tacacsrc to new .tacacsrc.gpg."""
213
+ print("Converting old tacacsrc to new kind :)")
214
+ tco = Tacacsrc(old=True)
215
+ tcn = Tacacsrc(old=False, gen=True)
216
+ tcn.creds = tco.creds
217
+ tcn.write()
218
+
219
+
220
+ def _perl_unhex_old(c):
221
+ """
222
+ Emulate Crypt::TripleDES's bizarre handling of keys, which relies on
223
+ the fact that you can pass Perl's pack('H*') a string that contains
224
+ anything, not just hex digits. "The result for bytes "g".."z" and
225
+ "G".."Z" is not well-defined", says perlfunc(1). Smash!
226
+
227
+ This function can be safely removed once GPG is fully supported.
228
+ """
229
+ if "a" <= c <= "z":
230
+ return (ord(c) - ord("a") + 10) & 0xF
231
+ if "A" <= c <= "Z":
232
+ return (ord(c) - ord("A") + 10) & 0xF
233
+ return ord(c) & 0xF
234
+
235
+
236
+ def _perl_pack_Hstar_old(s):
237
+ """
238
+ Used with _perl_unhex_old(). Ghetto hack.
239
+
240
+ This function can be safely removed once GPG is fully supported.
241
+ """
242
+ r = ""
243
+ while len(s) > 1:
244
+ r += chr((_perl_unhex_old(s[0]) << 4) | _perl_unhex_old(s[1]))
245
+ s = s[2:]
246
+ if len(s) == 1:
247
+ r += _perl_unhex_old(s[0])
248
+ return r
249
+
250
+
251
+ # Classes
252
+ class Tacacsrc:
253
+ """
254
+ Encrypts, decrypts and returns credentials for use by network devices and
255
+ other tools.
256
+
257
+ Pass use_gpg=True to force GPG, otherwise it relies on
258
+ settings.USE_GPG_AUTH
259
+
260
+ `*_old` functions should be removed after everyone is moved to the new
261
+ system.
262
+ """
263
+
264
+ def __init__(
265
+ self, tacacsrc_file=None, use_gpg=settings.USE_GPG_AUTH, generate_new=False
266
+ ):
267
+ """
268
+ Open .tacacsrc (tacacsrc_file or $TACACSRC or ~/.tacacsrc), or create
269
+ a new file if one cannot be found on disk.
270
+
271
+ If settings.USE_GPG_AUTH is enabled, tries to use GPG (.tacacsrc.gpg).
272
+ """
273
+ self.file_name = tacacsrc_file
274
+ self.use_gpg = use_gpg
275
+ self.generate_new = generate_new
276
+ self.userinfo = pwd.getpwuid(os.getuid())
277
+ self.username = self.userinfo.pw_name
278
+ self.user_home = self.userinfo.pw_dir
279
+ self.data = []
280
+ self.creds = {}
281
+ self.creds_updated = False
282
+ self.version = Version("2.0")
283
+
284
+ # If we're not generating a new file and gpg is enabled, turn it off if
285
+ # the right files can't be found.
286
+ if not self.generate_new:
287
+ if self.use_gpg and not self.user_has_gpg():
288
+ log.msg(".tacacsrc.gpg not setup, disabling GPG", debug=True)
289
+ self.use_gpg = False
290
+
291
+ log.msg(f"Using GPG method: {self.use_gpg!r}", debug=True)
292
+ log.msg(f"Got username: {self.username!r}", debug=True)
293
+
294
+ # Set the .tacacsrc file location
295
+ if self.file_name is None:
296
+ self.file_name = settings.TACACSRC
297
+
298
+ # GPG uses '.tacacsrc.gpg'
299
+ if self.use_gpg:
300
+ self.file_name += ".gpg"
301
+
302
+ # Check if the file exists
303
+ if not os.path.exists(self.file_name):
304
+ print(f"{self.file_name} not found, generating a new one!")
305
+ self.generate_new = True
306
+
307
+ if self.use_gpg:
308
+ if not self.generate_new:
309
+ self.rawdata = self._decrypt_and_read()
310
+ self.creds = self._parse()
311
+ else:
312
+ self.creds[settings.DEFAULT_REALM] = prompt_credentials(
313
+ device="tacacsrc"
314
+ )
315
+ self.write()
316
+ else:
317
+ # If passphrase is enable, use that
318
+ if settings.TACACSRC_USE_PASSPHRASE:
319
+ passphrase = settings.TACACSRC_PASSPHRASE
320
+ import hashlib
321
+
322
+ # Python 3 requires encoding string to bytes before hashing
323
+ if isinstance(passphrase, str):
324
+ passphrase = passphrase.encode("utf-8")
325
+ key = hashlib.md5(passphrase).hexdigest()[:24] # 24 bytes
326
+ self.key = key
327
+ # Otherwise read from keyfile.
328
+ else:
329
+ self.key = self._get_key_old(settings.TACACSRC_KEYFILE)
330
+
331
+ if not self.generate_new:
332
+ self.rawdata = self._read_file_old()
333
+ self.creds = self._parse_old()
334
+ if self.creds_updated: # _parse_old() might set this flag
335
+ log.msg("creds updated, writing to file", debug=True)
336
+ self.write()
337
+ else:
338
+ self.creds[settings.DEFAULT_REALM] = prompt_credentials(
339
+ device="tacacsrc"
340
+ )
341
+ self.write()
342
+
343
+ def _get_key_nonce_old(self):
344
+ """Yes, the key nonce is the userid. Awesome, right?"""
345
+ return pwd.getpwuid(os.getuid())[0] + "\n"
346
+
347
+ def _get_key_old(self, keyfile):
348
+ """Of course, encrypting something in the filesystem using a key
349
+ in the filesystem really doesn't buy much. This is best referred
350
+ to as obfuscation of the .tacacsrc."""
351
+ try:
352
+ with open(keyfile) as kf:
353
+ key = kf.readline().strip()
354
+ except OSError:
355
+ msg = f"Keyfile at {keyfile} not found. Please create it."
356
+ raise CouldNotParse(msg)
357
+
358
+ if not key:
359
+ msg = f"Keyfile at {keyfile} must contain a passphrase."
360
+ raise CouldNotParse(msg)
361
+
362
+ key += self._get_key_nonce_old()
363
+ key = _perl_pack_Hstar_old((key + (" " * 48))[:48])
364
+ assert len(key) == 24
365
+
366
+ return key
367
+
368
+ def _parse_old(self):
369
+ """Parses .tacacsrc and returns dictionary of credentials."""
370
+ data = {}
371
+ creds = {}
372
+
373
+ # Cleanup the rawdata
374
+ for idx, line in enumerate(self.rawdata):
375
+ line = line.strip() # eat \n
376
+ lineno = idx + 1 # increment index for actual lineno
377
+
378
+ # Skip blank lines and comments
379
+ if any((line.startswith("#"), line == "")):
380
+ log.msg(f"skipping {line!r}", debug=True)
381
+ continue
382
+ # log.msg('parsing %r' % line, debug=True)
383
+
384
+ if line.count(" = ") > 1:
385
+ raise CouldNotParse(f"Malformed line {line!r} at line {lineno}")
386
+
387
+ key, sep, val = line.partition(" = ")
388
+ if val == "":
389
+ continue # Don't add a key with a missing value
390
+ raise CouldNotParse(f"Missing value for key {key!r} at line {lineno}")
391
+
392
+ # Check for version
393
+ if key == "version":
394
+ if val != self.version:
395
+ raise VersionMismatch(f"Bad .tacacsrc version ({v})")
396
+ continue
397
+
398
+ # Make sure tokens can be parsed
399
+ realm, token, end = key.split("_")
400
+ if end != "" or (realm, token) in data:
401
+ raise CouldNotParse(f"Could not parse {line!r} at line {lineno}")
402
+
403
+ data[(realm, token)] = self._decrypt_old(val)
404
+ del key, val, line
405
+
406
+ # Store the creds, if a password is empty, try to prompt for it.
407
+ for (realm, key), val in data.items():
408
+ if key == "uname":
409
+ try:
410
+ # creds[realm] = Credentials(val, data[(realm, 'pwd')])
411
+ creds[realm] = Credentials(val, data[(realm, "pwd")], realm)
412
+ except KeyError:
413
+ print(f"\nMissing password for {realm!r}, initializing...")
414
+ self.update_creds(creds=creds, realm=realm, user=val)
415
+ # raise MissingPassword('Missing password for %r' % realm)
416
+ elif key == "pwd":
417
+ pass
418
+ else:
419
+ raise CouldNotParse(f"Unknown .tacacsrc entry ({realm}_{val})")
420
+
421
+ self.data = data
422
+ return creds
423
+
424
+ def update_creds(self, creds, realm, user=None):
425
+ """
426
+ Update username/password for a realm/device and set self.creds_updated
427
+ bit to trigger .write().
428
+
429
+ :param creds: Dictionary of credentials keyed by realm
430
+ :param realm: The realm to update within the creds dict
431
+ :param user: (Optional) Username passed to prompt_credentials()
432
+ """
433
+ creds[realm] = prompt_credentials(realm, user)
434
+ log.msg("setting self.creds_updated flag", debug=True)
435
+ self.creds_updated = True
436
+ new_user = creds[realm].username
437
+ print(f"\nCredentials updated for user: {new_user!r}, device/realm: {realm!r}.")
438
+
439
+ def _encrypt_old(self, s):
440
+ """Encodes using the old method. Adds a newline for you."""
441
+ # Ensure key and plaintext are bytes for cryptography library
442
+ key = self.key if isinstance(self.key, bytes) else self.key.encode("latin-1")
443
+ plaintext = s if isinstance(s, bytes) else s.encode("latin-1")
444
+
445
+ des = TripleDES(key)
446
+ cipher = ciphers.Cipher(des, ciphers.modes.ECB(), backend=openssl_backend)
447
+ encryptor = cipher.encryptor()
448
+
449
+ # Crypt::TripleDES pads with *spaces*! How 1960. Pad it so the
450
+ # length is a multiple of 8.
451
+ padding_len = (8 - len(plaintext) % 8) % 8
452
+ padding = b" " * padding_len
453
+
454
+ cipher_text = encryptor.update(plaintext + padding) + encryptor.finalize()
455
+
456
+ # We need to return a newline if a field is empty so as not to break
457
+ # .tacacsrc parsing (trust me, this is easier)
458
+ return (
459
+ encodestring(cipher_text).decode("ascii").replace("\n", "") or ""
460
+ ) + "\n"
461
+
462
+ def _decrypt_old(self, s):
463
+ """Decodes using the old method. Strips newline for you."""
464
+ # Ensure key is bytes for cryptography library
465
+ key = self.key if isinstance(self.key, bytes) else self.key.encode("latin-1")
466
+ des = TripleDES(key)
467
+ cipher = ciphers.Cipher(des, ciphers.modes.ECB(), backend=openssl_backend)
468
+ decryptor = cipher.decryptor()
469
+ # Ensure s is bytes for base64.decodebytes
470
+ s_bytes = s if isinstance(s, bytes) else s.encode("ascii")
471
+ # rstrip() to undo space-padding; unfortunately this means that
472
+ # passwords cannot end in spaces.
473
+ plaintext = decryptor.update(decodestring(s_bytes)) + decryptor.finalize()
474
+ return plaintext.rstrip(b" ").decode("latin-1")
475
+
476
+ def _read_file_old(self):
477
+ """Read old style file and return the raw data."""
478
+ self._update_perms()
479
+ with open(self.file_name) as f:
480
+ return f.readlines()
481
+
482
+ def _write_old(self):
483
+ """Write old style to disk. Newlines provided by _encrypt_old(), so don't fret!"""
484
+ out = [
485
+ "# Saved by {} at {}\n\n".format(
486
+ self.__module__, strftime("%Y-%m-%d %H:%M:%S %Z", localtime())
487
+ )
488
+ ]
489
+
490
+ for realm, (uname, pwd, _) in self.creds.items():
491
+ # log.msg('encrypting %r' % ((uname, pwd),), debug=True)
492
+ out.append(f"{realm}_uname_ = {self._encrypt_old(uname)}")
493
+ out.append(f"{realm}_pwd_ = {self._encrypt_old(pwd)}")
494
+
495
+ with open(self.file_name, "w+") as fd:
496
+ fd.writelines(out)
497
+
498
+ self._update_perms()
499
+
500
+ def _decrypt_and_read(self):
501
+ """Decrypt file using GPG and return the raw data."""
502
+ ret = []
503
+ for x in os.popen(f"gpg2 --no-tty --quiet -d {self.file_name}"):
504
+ x = x.rstrip()
505
+ ret.append(x)
506
+
507
+ return ret
508
+
509
+ def _encrypt_and_write(self):
510
+ """Encrypt using GPG and dump password data to disk."""
511
+ fin, fout = os.popen2(
512
+ f"gpg2 --yes --quiet -r {self.username} -e -o {self.file_name}"
513
+ )
514
+ for line in self.rawdata:
515
+ print(line, file=fin)
516
+
517
+ def _write_new(self):
518
+ """Replace self.rawdata with current password details."""
519
+ out = [
520
+ "# Saved by {} at {}\n\n".format(
521
+ self.__module__, strftime("%Y-%m-%d %H:%M:%S %Z", localtime())
522
+ )
523
+ ]
524
+
525
+ for realm, (uname, pwd, _) in self.creds.items():
526
+ out.append(f"{realm}_uname_ = {uname}")
527
+ out.append(f"{realm}_pwd_ = {pwd}")
528
+
529
+ self.rawdata = out
530
+ self._encrypt_and_write()
531
+ self._update_perms()
532
+
533
+ def write(self):
534
+ """Writes .tacacsrc(.gpg) using the accurate method (old vs. new)."""
535
+ if self.use_gpg:
536
+ return self._write_new()
537
+
538
+ return self._write_old()
539
+
540
+ def _update_perms(self):
541
+ """Enforce -rw------- on the creds file"""
542
+ os.chmod(self.file_name, 0o600)
543
+
544
+ def _parse(self):
545
+ """Parses .tacacsrc.gpg and returns dictionary of credentials."""
546
+ data = {}
547
+ creds = {}
548
+ for line in self.rawdata:
549
+ if line.find("#") != -1:
550
+ line = line[: line.find("#")]
551
+ line = line.strip()
552
+ if len(line):
553
+ k, v = line.split(" = ")
554
+ if k == "version":
555
+ if v != self.version:
556
+ raise VersionMismatch(f"Bad .tacacsrc version ({v})")
557
+ else:
558
+ realm, s, junk = k.split("_")
559
+ # assert(junk == '')
560
+ assert (realm, s) not in data
561
+ data[(realm, s)] = v # self._decrypt(v)
562
+
563
+ for (realm, k), v in data.items():
564
+ if k == "uname":
565
+ # creds[realm] = (v, data[(realm, 'pwd')])
566
+ # creds[realm] = Credentials(v, data[(realm, 'pwd')])
567
+ creds[realm] = Credentials(v, data[(realm, "pwd")], realm)
568
+ elif k == "pwd":
569
+ pass
570
+ else:
571
+ raise CouldNotParse(f"Unknown .tacacsrc entry ({realm}_{v})")
572
+
573
+ return creds
574
+
575
+ def user_has_gpg(self):
576
+ """Checks if user has .gnupg directory and .tacacsrc.gpg file."""
577
+ gpg_dir = os.path.join(self.user_home, ".gnupg")
578
+ tacacsrc_gpg = os.path.join(self.user_home, ".tacacsrc.gpg")
579
+
580
+ # If not generating new .tacacsrc.gpg, we want both to be True
581
+ if os.path.isdir(gpg_dir) and os.path.isfile(tacacsrc_gpg):
582
+ return True
583
+
584
+ return False