6 Commits

10 changed files with 34 additions and 149 deletions

38
debian/changelog vendored
View File

@ -1,38 +0,0 @@
ldaptool (0.4-1) unstable; urgency=medium
* move argument/column handling to decoder (prepare for more post-processing in decoder)
* move json output format handling to main tool from decoder
* support attribute post-processing; :<len>, and DN :domain, :path, :fullpath
* use Enum instead of StrEnum for python3.10
-- Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de> Tue, 02 May 2023 16:54:00 +0200
ldaptool (0.3-1) unstable; urgency=medium
* ldaptool: move output arguments from search to main
* run sort internally, refactor table output into separate method
* refactor table variant handling
* add html output format
* README.md: document csvkit dependency
* debian: require csvkit (markdown table is an essential feature)
-- Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de> Fri, 28 Apr 2023 19:31:37 +0200
ldaptool (0.2-1) unstable; urgency=medium
* README.md: fix typo
* enable tls unless kerberos is used (SASL GSS-API doesn't seem to work over TLS)
-- Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de> Fri, 28 Apr 2023 17:21:35 +0200
ldaptool (0.1-1) unstable; urgency=medium
* Initial release.
-- Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de> Fri, 28 Apr 2023 12:09:30 +0200
ldaptool (0.1-0) unstable; urgency=medium
* Stub ITP lintian.
-- Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de> Fri, 28 Apr 2023 12:09:29 +0200

43
debian/control vendored
View File

@ -1,43 +0,0 @@
Source: ldaptool
Section: net
Priority: optional
Maintainer: Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de>
Rules-Requires-Root: no
Build-Depends:
debhelper-compat (= 13),
pybuild-plugin-pyproject,
flit,
dh-sequence-python3,
python3,
python3-ldap,
python3-yaml,
python3-pykeepass,
#Testsuite: autopkgtest-pkg-python
Standards-Version: 4.6.2
Homepage: https://git-nks-public.tik.uni-stuttgart.de/net/ldaptool
Package: python3-ldaptool
Architecture: all
Depends:
${python3:Depends},
${misc:Depends},
Recommends:
python3-pykeepass,
Description: CLI tool to run ldap queries
CLI tool to query LDAP/AD servers, featuring various output formats
and a configuration for different realms.
.
This package installs the library for Python 3.
Package: ldaptool
Architecture: all
Depends:
python3-ldaptool (=${binary:Version}),
${python3:Depends},
${misc:Depends},
csvkit,
Description: CLI tool to run ldap queries
CLI tool to query LDAP/AD servers, featuring various output formats
and a configuration for different realms.
.
This package installs the script.

27
debian/copyright vendored
View File

@ -1,27 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Source: <https://git-nks-public.tik.uni-stuttgart.de/net/ldaptool>
Upstream-Name: ldaptool
Files:
*
Copyright:
2023 Stefan Bühler <stefan.buehler@tik.uni-stuttgart.de>
2023 Daniel Dizdarevic <daniel.dizdarevic@tik.uni-stuttgart.de>
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

5
debian/gbp.conf vendored
View File

@ -1,5 +0,0 @@
[DEFAULT]
pristine-tar = False
upstream-branch = main
debian-branch = debian
upstream-tag = ldaptool-%(version)s

13
debian/rules vendored
View File

@ -1,13 +0,0 @@
#!/usr/bin/make -f
export PYBUILD_NAME=ldaptool
%:
dh $@ --buildsystem=pybuild
# we want /usr/bin/ldaptool in a separate package
override_dh_auto_install:
dh_auto_install
mkdir -p debian/ldaptool/usr
mv debian/python3-ldaptool/usr/bin debian/ldaptool/usr/

View File

@ -1 +0,0 @@
3.0 (quilt)

View File

@ -1 +0,0 @@
extend-diff-ignore = "^[^/]*[.]egg-info/|^[.]vscode|/__pycache__/|^venv/|^.mypy_cache/"

View File

@ -16,7 +16,7 @@ classifiers = [
] ]
dynamic = ["version", "description"] dynamic = ["version", "description"]
requires-python = "~=3.11" requires-python = "~=3.10"
dependencies = [ dependencies = [
"python-ldap", "python-ldap",
"PyYAML", "PyYAML",

View File

@ -105,7 +105,7 @@ class _Context:
try: try:
self.config = search.Config.load() self.config = search.Config.load()
except Exception as e: except Exception as e:
raise SystemExit(f"config error: {e}") raise SystemExit(f"config error: {e!r}")
try: try:
self.arguments = arguments_p.from_args(args) self.arguments = arguments_p.from_args(args)
except decode.InvalidStep as e: except decode.InvalidStep as e:

View File

@ -7,6 +7,7 @@ import os
import os.path import os.path
import shlex import shlex
import subprocess import subprocess
import sys
import typing import typing
import yaml import yaml
@ -28,13 +29,13 @@ class Realm:
@staticmethod @staticmethod
def load(name: str, data: typing.Any) -> Realm: def load(name: str, data: typing.Any) -> Realm:
assert isinstance(data, dict) assert isinstance(data, dict), f"Realm section isn't a dictionary: {data!r}"
domain = data.pop("domain") domain = data["domain"]
servers = data.pop("servers").split() servers = data["servers"].split()
forest_root_domain = data.pop("forest_root_domain", domain) forest_root_domain = data.get("forest_root_domain", domain)
account = data.pop("account", None) account = data.get("account", None)
password_file = data.pop("password_file", None) password_file = data.get("password_file", None)
password_folder = data.pop("password_folder", None) password_folder = data.get("password_folder", None)
return Realm( return Realm(
name=name, name=name,
domain=domain, domain=domain,
@ -101,8 +102,8 @@ class Keyringer(PasswordManager):
@staticmethod @staticmethod
def load(data: typing.Any) -> Keyringer: def load(data: typing.Any) -> Keyringer:
assert isinstance(data, dict) assert isinstance(data, dict)
keyring = data.pop("keyring") keyring = data["keyring"]
folder = data.pop("folder") folder = data.get("folder", "")
return Keyringer(keyring=keyring, folder=folder) return Keyringer(keyring=keyring, folder=folder)
def get_password(self, password_name: str) -> str: def get_password(self, password_name: str) -> str:
@ -145,9 +146,17 @@ class Keepass(PasswordManager):
def get_password(self, password_name: str) -> str: def get_password(self, password_name: str) -> str:
import pykeepass # already made sure it is avaiable above import pykeepass # already made sure it is avaiable above
password = getpass.getpass(f"KeePass password for database {self.database}: ") while True:
kp = pykeepass.PyKeePass(self.database, password=password) try:
password = getpass.getpass(f"KeePass password for database {self.database}: ")
kp = pykeepass.PyKeePass(self.database, password=password)
break
except pykeepass.exceptions.CredentialsError:
print("Invalid password", file=sys.stderr)
entry = kp.find_entries(username=password_name, first=True) entry = kp.find_entries(username=password_name, first=True)
if not entry:
raise SystemExit(f"no KeePass entry for {password_name!r} found")
return entry.password # type: ignore return entry.password # type: ignore
@ -190,8 +199,8 @@ class Config:
with open(conf_path) as f: with open(conf_path) as f:
data = yaml.safe_load(f) data = yaml.safe_load(f)
assert isinstance(data, dict) assert isinstance(data, dict)
assert "realms" in data assert "realms" in data, "Missing realms section in config"
realms_data = data.pop("realms") realms_data = data["realms"]
assert isinstance(realms_data, dict) assert isinstance(realms_data, dict)
realms = {} realms = {}
for name, realm_data in realms_data.items(): for name, realm_data in realms_data.items():
@ -201,15 +210,15 @@ class Config:
if "keyringer" in data: if "keyringer" in data:
if password_manager: if password_manager:
raise ValueError("Can only set a single password manager") raise ValueError("Can only set a single password manager")
password_manager = Keyringer.load(data.pop("keyringer")) password_manager = Keyringer.load(data["keyringer"])
if "keepass" in data: if "keepass" in data:
if password_manager: if password_manager:
raise ValueError("Can only set a single password manager") raise ValueError("Can only set a single password manager")
password_manager = Keepass.load(data.pop("keepass")) password_manager = Keepass.load(data["keepass"])
if "password-script" in data: if "password-script" in data:
if password_manager: if password_manager:
raise ValueError("Can only set a single password manager") raise ValueError("Can only set a single password manager")
password_manager = PasswordScript.load(data.pop("password-script")) password_manager = PasswordScript.load(data["password-script"])
return Config(realms=realms, password_manager=password_manager) return Config(realms=realms, password_manager=password_manager)
@ -220,7 +229,11 @@ class Config:
""" """
if realm.account is None: if realm.account is None:
raise RuntimeError("Can't get password without acccount - should use kerberos instead") raise RuntimeError("Can't get password without acccount - should use kerberos instead")
if self.password_manager:
return self.password_manager.get_password(realm.password_name)
return getpass.getpass(f"Enter password for {realm.password_name}: ") try:
if self.password_manager:
return self.password_manager.get_password(realm.password_name)
return getpass.getpass(f"Enter password for {realm.password_name}: ")
except (KeyboardInterrupt, EOFError):
raise SystemExit("Password prompt / retrieval aborted")