├── MANIFEST.in ├── luckyLUKS ├── __init__.py ├── main.py ├── utilsUI.py ├── unlockUI.py ├── utils.py ├── mainUI.py ├── locale │ ├── luckyLUKS.pot │ └── de │ │ └── LC_MESSAGES │ │ └── luckyLUKS.po └── worker.py ├── luckyluks.desktop ├── setup.cfg ├── LICENSE ├── .gitignore ├── luckyluks ├── __main__.py ├── setup.py ├── signing-key.asc ├── Makefile ├── CHANGELOG └── README.rst /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include luckyluks.desktop 2 | include luckyluks.1.gz 3 | include luckyluks 4 | include LICENSE 5 | include README -------------------------------------------------------------------------------- /luckyLUKS/__init__.py: -------------------------------------------------------------------------------- 1 | """ luckyLUKS """ 2 | 3 | VERSION_STRING = '2.1.0' 4 | PROJECT_URL = 'https://github.com/jas-per/luckyLUKS' 5 | -------------------------------------------------------------------------------- /luckyluks.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=luckyLUKS 3 | Name[de]=luckyLUKS 4 | Comment=GUI for creating and unlocking LUKS/TrueCrypt volumes from container files 5 | Comment[de]=GUI zum Erstellen und Öffnen von verschlüsselten Containern (LUKS/TrueCrypt) 6 | GenericName=Encrypted Container Tool 7 | GenericName[de]=Verschlüsselte Container 8 | Exec=luckyluks 9 | Categories=Utility; 10 | Icon=dialog-password 11 | NoDisplay=false 12 | StartupNotify=false 13 | Terminal=false 14 | Type=Application 15 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [extract_messages] 2 | add-comments = L10n: 3 | copyright-holder = Jasper van Hoorn (muzius@gmail.com) 4 | msgid-bugs-address = Jasper van Hoorn (muzius@gmail.com) 5 | 6 | [luckyLUKS] 7 | section: utils 8 | mime-desktop-files: luckyluks.desktop 9 | package: luckyluks 10 | package3: luckyluks 11 | Build-Depends: python3 (>= 3.5), dh-python 12 | depends3: sudo (>= 1.8), cryptsetup (>= 2:2.2), cryptsetup-bin (>= 2:2.2), python3-pyqt5 (>= 5.11) 13 | suggests3: tcplay 14 | copyright-file: LICENSE 15 | maintainer: Jasper van Hoorn 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: * 4 | Copyright: (c) 2014,2015,2022 Jasper van Hoorn 5 | License: GPL-3+ 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | . 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | . 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | . 19 | On Debian systems, the full text of the GNU General Public License 20 | version 3 can be found in the file `/usr/share/common-licenses/GPL-3`. -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | dist_deb/ 15 | deb_dist/ 16 | downloads/ 17 | eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | README 24 | luckyluks.1.gz 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | *.eggs 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | 48 | # Translations 49 | *.mo 50 | #*.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Pydev/Eclipse 62 | .project 63 | .pydevproject 64 | .settings 65 | 66 | -------------------------------------------------------------------------------- /luckyluks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. 4 | For more information visit: http://github.com/jas-per/luckyLUKS 5 | 6 | Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | """ 18 | 19 | import locale 20 | import gettext 21 | import os.path 22 | import inspect 23 | from luckyLUKS import main 24 | 25 | 26 | locale.setlocale(locale.LC_ALL, '') 27 | 28 | locale_dir = os.path.join(os.path.dirname(inspect.getsourcefile(main)), 'locale') 29 | translation = gettext.translation('luckyLUKS', locale_dir, fallback=True) 30 | 31 | main.luckyLUKS(translation) 32 | -------------------------------------------------------------------------------- /__main__.py: -------------------------------------------------------------------------------- 1 | """ 2 | luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. 3 | For more information visit: http://github.com/jas-per/luckyLUKS 4 | 5 | Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | """ 17 | 18 | import locale 19 | import pkgutil 20 | import io 21 | from gettext import GNUTranslations, NullTranslations 22 | 23 | from luckyLUKS import main 24 | 25 | # This is the entry point, when run from the zip-file package. 26 | # To access resources inside the zip file, pkgutil.get_data() has to be used. 27 | # Because of this, gettext will be initialized here, 28 | # to search for a .mo file for the users locale inside the zip 29 | if __name__ == '__main__': 30 | 31 | locale.setlocale(locale.LC_ALL, '') 32 | loc, enc = locale.getlocale(locale.LC_MESSAGES) 33 | l10n_resource = None 34 | 35 | # try to find the corresponding gettext file (*.mo) for the users locale in the zip file 36 | if loc is not None and loc != 'C': 37 | try: 38 | l10n_resource = pkgutil.get_data( 39 | 'luckyLUKS', 40 | 'locale/{0}/LC_MESSAGES/luckyLUKS.mo'.format(loc) 41 | ) 42 | except IOError: 43 | if '_' in loc: 44 | try: 45 | l10n_resource = pkgutil.get_data( 46 | 'luckyLUKS', 47 | 'locale/{0}/LC_MESSAGES/luckyLUKS.mo'.format(loc.split('_')[0]) 48 | ) 49 | except IOError: 50 | pass 51 | 52 | if l10n_resource is None: 53 | translation = NullTranslations() 54 | else: 55 | translation = GNUTranslations(io.BytesIO(l10n_resource)) 56 | 57 | main.luckyLUKS(translation) 58 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ config for setuptools/stdeb """ 2 | 3 | from setuptools import setup 4 | from luckyLUKS import VERSION_STRING 5 | 6 | long_desc = """luckyLUKS is a Linux GUI for creating and (un-)locking 7 | encrypted volumes from container files. Unlocked containers leave 8 | an icon in the systray as a reminder to close them eventually ;) 9 | Supports cryptsetup/LUKS and Truecrypt container files. 10 | 11 | The container is basically a large file that encapsulates an 12 | encrypted partition. This simplifies handling and backup of 13 | encrypted data for casual users. 14 | luckyLUKS follows a keep-it-simple philosophy for creating and using 15 | encrypted containers, that aims to keep users from shooting themselves 16 | in the foot. For quick access the GUI offers to add a shortcut for 17 | unlocking a specific container to the start menu or on the desktop. 18 | 19 | For more information and a complete FAQ see 20 | https://github.com/jas-per/luckyLUKS""" 21 | 22 | 23 | setup(name='luckyLUKS', 24 | version=VERSION_STRING, 25 | author='Jasper van Hoorn', 26 | author_email='muzius@gmail.com', 27 | url='https://github.com/jas-per/luckyLUKS', 28 | download_url='https://github.com/jas-per/luckyLUKS', 29 | description='GUI for encrypted LUKS or TrueCrypt containers', 30 | long_description=long_desc, 31 | platforms=['Linux'], 32 | packages=['luckyLUKS'], 33 | package_data={'luckyLUKS': ['locale/*/LC_MESSAGES/*']}, 34 | scripts=['luckyluks'], 35 | keywords='python tools utils cryptsetup LUKS TrueCrypt encryption container block device mapper GUI tcplay', 36 | license='GPL', 37 | classifiers=['Development Status :: 5 - Production/Stable', 38 | 'Environment :: X11 Applications :: Qt', 39 | 'Intended Audience :: End Users/Desktop', 40 | 'Natural Language :: English', 41 | 'Natural Language :: German', 42 | 'Operating System :: POSIX :: Linux', 43 | 'Programming Language :: Python :: 3', 44 | 'Programming Language :: Python :: 3.5', 45 | 'Programming Language :: Python :: 3.6', 46 | 'Programming Language :: Python :: 3.7', 47 | 'Programming Language :: Python :: 3.8', 48 | 'Programming Language :: Python :: 3.9', 49 | 'Programming Language :: Python :: 3.10', 50 | 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', 51 | 'Topic :: Utilities', 52 | 'Topic :: Security :: Cryptography', 53 | ], 54 | ) 55 | -------------------------------------------------------------------------------- /signing-key.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | Version: GnuPG v1 3 | 4 | mQINBFSYePwBEADj2AJIzIx9S1WNGzPRFutQC9Ue663hiWjru6HvZ6gSPm8wbgzD 5 | jEZ0LQcsgAJsl8c1Fc/pKcH03TO0yULKnCas8K4lFg81OmayzcxB5BrerbK0grmG 6 | ZD7lROyExm1Y0t8r5Un81PYeR52tSI7c0OuoE6cCUtbl7FMkXRvTJkIRbaqI6cPo 7 | tUPQWoXhqbnbHeO8HHMIQQ8Ngnst1XOrVXyprVXHk5HuLEvAJ/+96iDrWODTYUXN 8 | sfCSq7iw1URvPljozaLrtwuBShlRLZRDF2T2ydZZ0FPj2r32yZIUvynOkV8V+U9O 9 | pZKzeepW5IDYXCHRHXxLMK/ydtxuzP/LKT7NCcToCppehtkDFTg/OZQXARu5YBDn 10 | IgwFGmGeq33UVzZlJfPu4C42GiVNfgy6qV3Qd1BOUVVIZnpr52ErZtuderISgsQy 11 | JwTVJ3aVU6krak5pXYg4e8jMOwRIkOBOb11gBcs2ObFwvf6rWkaRotvKm4+14tb3 12 | 4RsEbzWyCOptpkCV+fU+REaqDPjBwqmE9s+UXPKoPVkFzALTCZPoYZ4laaM7VYGN 13 | dj0uAvuHM/TlHnj/Bs9XI2UskoDANCdqc4h0Y2DACNd4Nj70CTtw9VmDvRLh2FGL 14 | O6Par7HRc5QYLDrVUhjdKOkRwJsfpR6lSNXgxRWrxGRh4g5DgctqmNcxDwARAQAB 15 | tCNKYXNwZXIgdmFuIEhvb3JuIDxtdXppdXNAZ21haWwuY29tPokCOAQTAQIAIgUC 16 | VJh4/AIbLwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQD5DVEqt95z1CIxAA 17 | xLy9SPwFj2DsVt9/K8lqvfpQ82Fi57Kf+35JTh55zFLW+5bxioZRv2AFCQSQm9Xy 18 | aoCoguXLUHlzDi2xyCzk255JFYyAw5oznSWf6YBwjldVxFV/lqI4Wkty8n6v1TaD 19 | N3mdnwwW37v5EjrZO1dro1+M+Fqclhs6yUZ7vDs0bPu5E3AOmbCvRiTDCF4m4aol 20 | 35ul8MGCWJjFzH+LgPjVCdxST+NCqssbr+oYNTXA0pmtvtpbzX6xaRU/ToQzLukm 21 | ktuNiQ83d0606j+FnXEUGA9zT6i7nNK9Sz28hUH03kJnRpTSdKEAtZPTL7p+Cku6 22 | PR2Djns5tx4MOes1TUnLUhTxzLMjYk2BkmSrklujpHV/DD7fPiYTlAigO5GfauXY 23 | f4yyxluIX2qCS9zx7EVfBTPwZ4aITVHzf1e+iov+KgqI5CHiYJSZ3wlQvSFKGGkt 24 | bQcvfx0USsvHSwEVWhTMLoA0mbmkIDiKXXOM+ptp9eID1cIRxNvTHlNgcQEchPs4 25 | oKkQeckTrkYh3Fir8NarRVNyts095kujYorPrd+VbH2F8Vuvu64IPH2fPcC+ArV/ 26 | mBEeArPCWsmmoT3m/TKr7HchtgaQugC1VKZJjRoVHCtqYz4UAJLnXs7Wf8F2ao1G 27 | fAaA+xLW532OmCZbPSUo6otqwa1pTLv2mQnay5eX0eS5Ag0EVJh4/AEQAL4Flr3/ 28 | cNeJPqSgwDndyoMlQ9q1ncbawO+scMS14NK0LtawZXQ6VwgYnErl4DaztQsp11Zm 29 | 2llC6gWnIbhNyEzXF94QHMji9M2m6J9ZbSStV9IVkRefHw947HcdpLYsS4PT8pS/ 30 | k6gRtTv8x2zsIq2z8L3oOHPZQKlTEp/YTlT3JDZns6ZLDt5RXCvRF+Ag4lLqM8qR 31 | b17nFhNkk88EHhwXnTQpdOCnu8Wdc9jpvf+itA3NG+qy2Ss7GM3boZpYG8SosSWp 32 | lqtKFXSq1EOmeW1H3fW8cKqdu8iqFPGBlkzY9ymSgtjFVHUIivq3bcoQEPFoTohM 33 | 46UnXveTT3TmUedu927AJfHN0dg4r/+jRzt5vCwWk85AxgDfTrF27jBNfBTLY1ri 34 | Fp5WwmRRTXlRAgql6Hv22wHHxXBfwvO5bU8Q9qbJ8ZnjEVWY1tBMkYqNQ/pBII2u 35 | yewR8gqTrNXXBu1Yp0yx6LFlsp43OjiHGG0wqm8v+FiwUceBzFInrjUiX1vi068U 36 | ZJ32WHtejKM/sV5OTjpX1WU/A4VZNhwTaYSIgZ46b1xZo0PP1YBOUBPV8MGCubHm 37 | JV9ClqD8rAX+Zwl4rNY6x+fHCpYTGPjrfrSM5Csz88dnZK6PnGQndjwdRSjqnOce 38 | ohqG8dYN6mDqbSvDNsyHayCRwLk2jXMixz+ZABEBAAGJBD4EGAECAAkFAlSYePwC 39 | Gy4CKQkQD5DVEqt95z3BXSAEGQECAAYFAlSYePwACgkQSr/2ghc2Ns5OzhAAjDsj 40 | jC1QpPTWPaCWE/MsG2O0df8XMNmHkOjel3u1S1f9NmGT0nekznrAe2wCVdgCEBPh 41 | LjsZlsaemc0Mj4TtqfjGCnd2CoheBzjg2OxPxOXRYUozsLnfqxlePVxzjzaB8hnE 42 | xax+1tAOHYPVYU8UHIXCxsDpWfXksuY4D9rLvUDzlPw9ztecxigBbWRn7OfAUJoK 43 | +czJn5Ty6k4IqEYSfeK1Z1orM4vqdzZy50Op/z0bVA9pqzCzHO1YTGX8UOm5f5ZG 44 | wCmBSC+IfnsJpziyENt0Go7CPRtYqYp+JKfo/tLZtG1fWXWUFzc6tn9rR3xAklg9 45 | TaRRX7IIjYlBeilDlS9E+u8XJSXT8BUZZof8afO0YbN3NBwgnJm0yO6cT037LY0r 46 | dUd2Dvtc4YVt3e3Tul+WZ3Y9vS2grOgt6mWRNVZAZr7uZeIeDbiwB6+W6XEA/g7Z 47 | Kk/UjvSTozUHsit7vRoHbXbVSLXtzlid4V0wZPMTtZxlc5L3lkvUyYoPyIQh/WB8 48 | KIqT7bELR2+khdkSZz90dz41FUieIR9swdUfsqHN6HYRiJOf3NfdloJRW8/4+bTg 49 | NgEjOFAeNTK6eyve7FGL7CZAhKftQvvNxgh77fsxCUJDqDIeug8VDhEAB7BCQL4G 50 | 5xfHoEMCwrcjEdEXzfEeoKqFCXFvebB4xLyAKQUM6w//aXTfrPE7jVX5ww4Omb48 51 | cARLzj0FrocrTbqgNsFZPhZqc7mkG+uD8wAGejmVeHLgrbVZhamzeTFvYaJyYG6W 52 | 5nqEXZ0hvg1IUO4s9yW5BZE77iIu7ZTi9skPSWqhynvqLW98yT/g22FUBBlgH7tf 53 | 41pJITDPutIAP5f0e3vQBZWHRNTFe00bGmMRneAsS+WOkOIqIqtVMBL+8OyQBcKJ 54 | 0+X8RqWnL/m46UQGJIbrh4TEfA/3nyi0cDOZvLz4mkiAlwk+BhbcxCvdXDLi3qNM 55 | iSIUYZdtDbOKqsvcfeW+szz8GTlsIdh9WDC5+GXudj4kqt/mu9bocjrLlroRaaH/ 56 | glQVO3psjZOuAFeN01hRp6cL12zd/cesErI/DTif6YyTNlMchfPqZPZ9ZM6APEjE 57 | VNnlG+1ah9okCzXSyseokoNDZivV11qgKz+RwfU40M97sSJiGrqfqHMM5e60tIXQ 58 | 6MIj2eqV5xCy6ug70uTU6ZStejmaW0e2IM0BB7k78oVVnaxslYFylKdIZ+6GEZlV 59 | gogLnoMSK0bDlK+rLrjhQCHAOz36aoMDw2hHE4FQDJVkfrgPfPDM/NkOyxK+qc6p 60 | c0MEFiyr5FFgwv4sefOXHiiBM2GgIXeXlJaDUAPQtDfbuVNBcMjXMX+xdDk66MQp 61 | HM8I4t9M3JVKvqUwnTlWB4w= 62 | =1LVQ 63 | -----END PGP PUBLIC KEY BLOCK----- 64 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON=python3 2 | 3 | NAME=$(shell ${PYTHON} setup.py --name) 4 | LOWER_NAME = $(shell echo $(NAME) | tr A-Z a-z) 5 | VERSION=$(shell ${PYTHON} setup.py --version) 6 | SDIST=dist/${NAME}-${VERSION}.tar.gz 7 | VENV=/tmp/venv 8 | 9 | .PHONY: dist_zip dist_deb 10 | 11 | # requires: python3-setuptools, python3-stdeb, python3-babel, help2man, pandoc -> TODO: move into setup.cfg/build-dep 12 | dist: clean compile_locales manpage readme 13 | ${PYTHON} setup.py sdist 14 | @echo "Signing tgz file" 15 | gpg -ba -o dist/${NAME}-${VERSION}.tar.gz.asc dist/${NAME}-${VERSION}.tar.gz 16 | 17 | # requires stdeb >= 0.8.5 18 | dist_deb: dist 19 | mkdir dist_deb 20 | cp dist/${NAME}-${VERSION}.tar.gz dist_deb/${LOWER_NAME}_${VERSION}.orig.tar.gz 21 | cp dist/${NAME}-${VERSION}.tar.gz.asc dist_deb/${LOWER_NAME}_${VERSION}.orig.tar.gz.asc 22 | cd dist_deb && tar -xvzf ${LOWER_NAME}_${VERSION}.orig.tar.gz 23 | cd dist_deb/${NAME}-${VERSION} && ${PYTHON} setup.py --command-packages=stdeb.command debianize --extra-cfg-file setup.cfg 24 | cp CHANGELOG dist_deb/${NAME}-${VERSION}/debian/changelog 25 | echo 'README' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.docs 26 | echo 'luckyluks.1.gz' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.manpages 27 | echo 'luckyluks usr/bin' >> dist_deb/${NAME}-${VERSION}/debian/${LOWER_NAME}.install 28 | mkdir dist_deb/${NAME}-${VERSION}/debian/upstream/ 29 | cp signing-key.asc dist_deb/${NAME}-${VERSION}/debian/upstream/ 30 | echo 'version=3\nopts=filenamemangle=s/.+\/v?(\d?\S*)\.tar\.gz/luckyluks_$$1.tar.gz/,pgpsigurlmangle=s/archive\/v?(\d\S*)\.tar\.gz/releases\/download\/v$$1\/v$$1.tar.gz.asc/ https://github.com/jas-per/luckyLUKS/releases .*/archive/v(\d?\S*)\.tar\.gz' >> dist_deb/${NAME}-${VERSION}/debian/watch 31 | echo 'override_dh_install:' >> dist_deb/${NAME}-${VERSION}/debian/rules 32 | echo '\tdh_install --sourcedir=./' >> dist_deb/${NAME}-${VERSION}/debian/rules 33 | sed -e "s/7/9/g" -i dist_deb/${NAME}-${VERSION}/debian/compat 34 | sed -e "s,Standards-Version: 3.9.1,Standards-Version: 4.5.0\nVcs-Git: git://github.com/jas-per/luckyLUKS.git\nVcs-Browser: https://github.com/jas-per/luckyLUKS,g" -i dist_deb/${NAME}-${VERSION}/debian/control 35 | cd dist_deb/${NAME}-${VERSION} && debuild -S -sa 36 | 37 | dist_zip: 38 | mkdir -p dist_zip 39 | rm -f dist_zip/${NAME}-${VERSION} 40 | zip -r ${NAME}-${VERSION} ${NAME}/ __main__.py -i \*.py \*.mo 41 | echo '#!/usr/bin/env ${PYTHON}' | cat - ${NAME}-${VERSION} > temp && mv temp ${NAME}-${VERSION} 42 | chmod +x ${NAME}-${VERSION} 43 | mv ${NAME}-${VERSION} dist_zip/ 44 | 45 | # these would work if stdeb could handle additional files (manpage etc) && custom changelog 46 | # use dist_deb target instead and build binary package manually if needed 47 | #deb_src: clean manpage 48 | # ${PYTHON} setup.py --command-packages=stdeb.command sdist_dsc --extra-cfg-file setup.cfg 49 | # debsign dist_deb/${LOWER_NAME}_${VERSION}*_source.changes 50 | 51 | #deb_bin: deb_src 52 | # cd dist_deb/${NAME}-${VERSION} && debuild -us -uc 53 | 54 | update_locales: 55 | ${PYTHON} setup.py extract_messages --output-file ${NAME}/locale/${NAME}.pot 56 | ${PYTHON} setup.py update_catalog --domain ${NAME} --input-file ${NAME}/locale/${NAME}.pot --output-dir ${NAME}/locale 57 | 58 | compile_locales: 59 | ${PYTHON} setup.py compile_catalog --domain ${NAME} --directory ${NAME}/locale 60 | 61 | init_locale: 62 | if test -z "$$NEW_LANG";\ 63 | then echo 'please provide a language eg. `make init_locale NEW_LANG="LANGCODE"`';\ 64 | else ${PYTHON} setup.py init_catalog -l ${NEW_LANG} -i ${NAME}/locale/${NAME}.pot -d ${NAME}/locale; fi; 65 | 66 | manpage: 67 | help2man -n 'GUI for creating and unlocking LUKS/TrueCrypt volumes from container files' -N --no-discard-stderr ./luckyluks | gzip -9 > luckyluks.1.gz 68 | 69 | readme: 70 | sed '/Installation/,/repository tools./d' README.rst | pandoc -r rst -w plain -o README 71 | 72 | install: 73 | ${PYTHON} setup.py install --install-layout=deb 74 | 75 | check: 76 | @echo '### pylint check ###' 77 | find . -name \*.py | grep -v "^test_" | xargs pylint --max-line-length=120 --max-args=7 --disable=invalid-name,unused-argument,fixme,import-outside-toplevel,no-self-use --errors-only --additional-builtins=_ --extension-pkg-whitelist=PyQt5 --reports=n 78 | @echo '### pep8 check ###' 79 | pep8 *.py ./luckyLUKS --max-line-length=120 --ignore=E731,W503,W504 80 | # autopep8 ./luckyLUKS/*.py --in-place --verbose --ignore=E501,E731,W503,W504 81 | 82 | #deploy: 83 | # # make sdist 84 | # rm -rf dist 85 | # python setup.py sdist 86 | # 87 | # # setup venv 88 | # rm -rf $(VENV) 89 | # virtualenv --no-site-packages $(VENV) 90 | # $(VENV)/bin/pip install $(SDIST) 91 | 92 | #upload: 93 | # ${PYTHON} setup.py sdist register upload 94 | 95 | clean: 96 | ${PYTHON} setup.py clean 97 | rm -rf build/ dist build ${NAME}-${VERSION} ${NAME}.egg-info deb_dist dist_zip dist_deb debian luckyluks.1.gz README 98 | find . -name '*.pyc' -delete 99 | -------------------------------------------------------------------------------- /luckyLUKS/main.py: -------------------------------------------------------------------------------- 1 | """ 2 | luckyLUKS is a GUI for creating and (un-)locking LUKS volumes from container files. 3 | For more information visit: http://github.com/jas-per/luckyLUKS 4 | 5 | Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | """ 17 | 18 | import os.path 19 | import sys 20 | import argparse 21 | import types 22 | import builtins 23 | from luckyLUKS import VERSION_STRING, PROJECT_URL 24 | 25 | 26 | def luckyLUKS(translation, *args, **kwargs): 27 | """ main entry point: initialize gettext, parse arguments and start either GUI or worker """ 28 | 29 | translation.gettext_qt = types.MethodType(lambda self, msg: self.gettext(msg).replace('\n', '
'), translation) 30 | argparse._ = _ = translation.gettext # gettext for argsparse 31 | 32 | parser = argparse.ArgumentParser( 33 | description=_('GUI for creating and unlocking LUKS/TrueCrypt volumes from container files'), 34 | epilog=_('When called without any arguments a setup dialog will be shown before unlocking,\n' 35 | 'where you can select containerfile and name, or create a new encrypted container.\n' 36 | 'If both arguments are supplied, the unlock dialog will be shown directly.\n\n' 37 | 'Example:\n' 38 | ' {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n\n' 39 | 'If automatic mounting (eg udisks/polkit) is configured on your system,\n' 40 | 'explicitly setting a mountpoint is usually not needed (but still possible)\n\n' 41 | 'Homepage: {project_url}').format(executable=os.path.basename(sys.argv[0]), 42 | project_url=PROJECT_URL), 43 | formatter_class=argparse.RawDescriptionHelpFormatter 44 | ) 45 | 46 | # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) 47 | ap_usage = _('usage: ') 48 | # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) 49 | ap_optargs = _('optional arguments') 50 | # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) 51 | ap_helpmsg = _('show this help message and exit') 52 | # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) 53 | ap_errmsg = _('%(prog)s: error: %(message)s\n') 54 | # L10n: used by argsparse to generate help output on the console (luckyLUKS --help) 55 | ap_unknown = _('unrecognized arguments: %s') 56 | 57 | # if argument not specified or empty set value to None 58 | # error messages will be shown by the GUI, not on the command line 59 | parser.add_argument('-c', dest='container', nargs='?', metavar=_('PATH'), 60 | help=_('Path to the encrypted container file')) 61 | parser.add_argument('-n', dest='name', nargs='?', metavar=_('NAME'), 62 | help=_('Choose a device name to identify the unlocked container')) 63 | parser.add_argument('-m', dest='mountpoint', nargs='?', metavar=_('PATH'), 64 | help=_('Where to mount the encrypted filesystem')) 65 | parser.add_argument('-k', dest='keyfile', nargs='?', metavar=_('PATH'), 66 | help=_('Path to an optional key file')) 67 | parser.add_argument('-v', '--version', action='version', version="luckyLUKS " + VERSION_STRING, 68 | help=_("show program's version number and exit")) 69 | parser.add_argument('--ishelperprocess', action='store_true', help=argparse.SUPPRESS) 70 | parser.add_argument('--sudouser', type=int, help=argparse.SUPPRESS) 71 | 72 | parsed_args = parser.parse_args() 73 | 74 | # worker will be created by calling the script again (but this time with su privileges) 75 | builtins._ = translation.gettext_qt 76 | if parsed_args.ishelperprocess: 77 | startWorker(parsed_args.sudouser) 78 | else: 79 | startUI(parsed_args) 80 | 81 | 82 | def startUI(parsed_args): 83 | """ Import the required GUI elements and create main window """ 84 | from PyQt5.QtWidgets import QApplication 85 | from PyQt5.QtCore import QLocale, QTranslator, QLibraryInfo 86 | from luckyLUKS.mainUI import MainWindow 87 | 88 | # l10n qt-gui elements 89 | qt_translator = QTranslator() 90 | qt_translator.load('qt_' + QLocale.system().name(), QLibraryInfo.location(QLibraryInfo.TranslationsPath)) 91 | application = QApplication(sys.argv) 92 | application.installTranslator(qt_translator) 93 | 94 | # start application 95 | main_win = MainWindow(parsed_args.name, parsed_args.container, parsed_args.keyfile, parsed_args.mountpoint) 96 | # setup OK -> run event loop 97 | if main_win.is_initialized: 98 | sys.exit(application.exec_()) 99 | else: 100 | sys.exit(0) 101 | 102 | 103 | def startWorker(sudouser=None): 104 | """ Initialize worker process """ 105 | from luckyLUKS import worker 106 | if sudouser is not None: 107 | # helper called with su to configure sudo 108 | if os.getuid() == 0 and os.getenv("SUDO_UID") is None: 109 | try: 110 | worker.WorkerHelper().modify_sudoers(sudouser) 111 | sys.exit(0) 112 | except worker.WorkerException as we: 113 | sys.stdout.write(str(we)) 114 | sys.exit(2) 115 | else: 116 | # deny giving other user userids sudo access to luckyLUKS if not called with su 117 | sys.exit(2) 118 | else: 119 | worker.run() 120 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | luckyluks (2.1.0) unstable; urgency=low 2 | 3 | * Add quickformat to initialize container with fallocate 4 | instead of filling with random data 5 | 6 | -- Jasper van Hoorn Wed, 16 Feb 2022 23:48:23 +0100 7 | 8 | luckyluks (2.0.1) unstable; urgency=low 9 | 10 | * Update readme/manpages 11 | 12 | -- Jasper van Hoorn Tue, 25 Jan 2022 01:03:37 +0100 13 | 14 | 15 | luckyluks (2.0.0) unstable; urgency=low 16 | 17 | * bugfix tcplay keyfile only 18 | 19 | * testing: kde, (ubuntu-) gnome, xfce, lxqt, cinnamon, mate 20 | 21 | -- Jasper van Hoorn Tue, 25 Jan 2022 00:23:45 +0100 22 | 23 | 24 | luckyluks (1.99.0) unstable; urgency=low 25 | 26 | * minor adjustments for LUKS2 support 27 | 28 | * cleanup: remove legacy code for cryptsetup < 2.0 29 | 30 | * cleanup: remove legacy support for qt4/python2 31 | 32 | * update build scripts 33 | 34 | * update/cleanup readme 35 | 36 | -- Jasper van Hoorn Mon, 17 Jan 2022 16:42:21 +0100 37 | 38 | 39 | luckyluks (1.2.0) unstable; urgency=low 40 | 41 | * minor adjustments for gtk2/3 version 42 | (feature complete alternative to this qt based version) 43 | 44 | * bugfixes error handling 45 | 46 | -- Jasper van Hoorn Tue, 04 May 2015 16:32:28 +0100 47 | 48 | 49 | luckyluks (1.1.0) unstable; urgency=low 50 | 51 | * keyfile support (open/create for LUKS and TrueCrypt containers) 52 | 53 | * generate safe keyfile from ui 54 | 55 | * improved help dialogs 56 | 57 | -- Jasper van Hoorn Sun, 19 Apr 2015 22:16:24 +0100 58 | 59 | 60 | luckyluks (1.0.5) unstable; urgency=low 61 | 62 | * fix locale transfer to worker process 63 | 64 | -- Jasper van Hoorn Tue, 31 Mar 2015 19:18:46 +0100 65 | 66 | 67 | luckyluks (1.0.4) unstable; urgency=low 68 | 69 | * degrade gracefully if no system tray available 70 | 71 | -- Jasper van Hoorn Mon, 30 Mar 2015 18:26:39 +0100 72 | 73 | 74 | luckyluks (1.0.3) unstable; urgency=low 75 | 76 | * improved debian packaging 77 | 78 | -- Jasper van Hoorn Fri, 27 Mar 2015 13:49:53 +0100 79 | 80 | 81 | luckyluks (1.0.2) unstable; urgency=low 82 | 83 | * removed dependency on pkg_resources 84 | 85 | * improved error passing from worker to UI 86 | 87 | -- Jasper van Hoorn Thu, 26 Mar 2015 23:11:28 +0100 88 | 89 | 90 | luckyluks (1.0.1) unstable; urgency=low 91 | 92 | * bugfixes sudo setup & losetup 93 | (closes https://github.com/jas-per/luckyLUKS/issues/4) 94 | 95 | * use nosuid/nodev for mount 96 | (thanks to https://github.com/mhogomchungu) 97 | 98 | * modified access rights checks 99 | 100 | -- Jasper van Hoorn Fri, 13 Mar 2015 19:16:52 +0100 101 | 102 | 103 | luckyluks (1.0.0) unstable; urgency=low 104 | 105 | * don't require tcplay to just open TrueCrypt containers 106 | 107 | * fallback icon for dialog-password 108 | 109 | * workaround for .desktop file creation 110 | 111 | * wait before detaching loopback device (workaround udisks-daemon crashes) 112 | 113 | * bugfixes python3 114 | 115 | * fully tested on Ubuntu 14.04 & 12.04 (Unity, KDE, XFCE, LXDE) / Kubuntu 15.04-beta1 / Debian wheezy & jessie & unstable / Tails 116 | 117 | -- Jasper van Hoorn Sun, 08 Mar 2015 23:56:05 +0100 118 | 119 | 120 | luckyluks (0.9.10) unstable; urgency=low 121 | 122 | * full qt5 support (python3 version uses pyqt5 if installed) 123 | 124 | * packaging fixes 125 | 126 | -- Jasper van Hoorn Sat, 07 Mar 2015 08:44:23 +0100 127 | 128 | 129 | luckyluks (0.9.9) unstable; urgency=low 130 | 131 | * Create TrueCrypt containers 132 | 133 | * Simplyfy interface / expandable advanced settings 134 | 135 | * workaround udisks-daemon crash (manual loopback device handling) 136 | 137 | * Restructured "Help" dialogs 138 | 139 | * Toggle hidden passphrase input when creating container 140 | 141 | * FAQ & Translation notes added to Readme 142 | 143 | -- Jasper van Hoorn Tue, 03 Mar 2015 18:41:30 +0100 144 | 145 | 146 | luckyluks (0.9.8) unstable; urgency=low 147 | 148 | * python3 packaging (make needs patched stdeb: 149 | https://github.com/astraw/stdeb/pull/93) 150 | 151 | * minor qt5 compatibility changes 152 | 153 | -- Jasper van Hoorn Wed, 11 Feb 2015 19:15:53 +0100 154 | 155 | 156 | luckyluks (0.9.7) unstable; urgency=low 157 | 158 | * better cleanup: detect input pipe close to exit worker process 159 | 160 | -- Jasper van Hoorn Fri, 06 Feb 2015 21:53:41 +0100 161 | 162 | 163 | luckyluks (0.9.6) unstable; urgency=low 164 | 165 | * add show/hide to systray context menu (required for unity) 166 | 167 | * setup sudo if not configured via su 168 | 169 | * add *sbin/ to search path for tool checks 170 | 171 | * refactor util methods 172 | 173 | * Makefile and setup.py for debian packaging 174 | 175 | * tested on debian sid, ubuntu 14.04 and kubuntu 12.04 & 14.04 176 | 177 | -- Jasper van Hoorn Fri, 30 Jan 2015 15:02:11 +0100 178 | 179 | 180 | luckyluks (0.9.5) unstable; urgency=low 181 | 182 | * using poll instead of epoll to connect with sudo process to avoid problems on python<2.7.4 183 | 184 | * check dmsetup version for unicode/special character support 185 | 186 | * fixes unicode output on exception display 187 | 188 | -- Jasper van Hoorn Thu, 22 Jan 2015 17:48:23 +0100 189 | 190 | 191 | luckyluks (0.9.4) unstable; urgency=low 192 | 193 | * fixes build failure ubuntu ppa because of setup_requires 194 | 195 | -- Jasper van Hoorn Tue, 20 Jan 2015 17:32:05 +0100 196 | 197 | 198 | luckyluks (0.9.3) unstable; urgency=low 199 | 200 | * modified gettext & qt application init 201 | 202 | * more reliable parsing of sudo output 203 | 204 | * use xdg-desktop-menu to add menuentry when available 205 | 206 | * use python to set uid/gui of partition root on create (instead of 207 | using mkfs which fails in older versions of mkfs) 208 | 209 | -- Jasper van Hoorn Thu, 15 Jan 2015 22:51:38 +0100 210 | 211 | 212 | luckyluks (0.9.2) unstable; urgency=low 213 | 214 | * first try debian packaging with ubuntu ppa 215 | 216 | -- Jasper van Hoorn Sat, 27 Dec 2014 00:54:45 +0100 217 | 218 | 219 | luckyluks (0.9.1) unstable; urgency=low 220 | 221 | * Support for internationalization 222 | 223 | * German localization 224 | 225 | * zip-'packaging' 226 | 227 | -- Jasper van Hoorn Sat, 20 Dec 2014 23:53:49 +0100 228 | 229 | 230 | luckyluks (0.9.0) unstable; urgency=low 231 | 232 | * Initial version, finally getting things in shape for a public release 233 | Been using the program for almost half a year and shared it 234 | with some friends as well, so major bugs should hopefully be 235 | squashed by now. 236 | 237 | -- Jasper van Hoorn Fri, 05 Dec 2014 11:37:23 +0100 238 | -------------------------------------------------------------------------------- /luckyLUKS/utilsUI.py: -------------------------------------------------------------------------------- 1 | """ 2 | UI helper/classes for luckyLUKS 3 | 4 | luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 5 | QExpander Copyright (c) 2012 Canonical Ltd. 6 | modified, originally from https://launchpad.net/ubuntu-sso-client (GPL v3+) 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation, either version 3 of the License, or 11 | (at your option) any later version. 12 | 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | """ 18 | 19 | from PyQt5.QtCore import pyqtSignal, Qt 20 | from PyQt5.QtWidgets import QApplication, QMessageBox, QDialog, QVBoxLayout, QHBoxLayout,\ 21 | QWidget, QDialogButtonBox, QLabel, QStyle, QStyleOption, QSizePolicy, QFrame 22 | from PyQt5.QtGui import QPainter 23 | 24 | from luckyLUKS import VERSION_STRING, PROJECT_URL 25 | 26 | 27 | class HelpDialog(QDialog): 28 | 29 | """ Displays a help dialog that consists of 30 | a help icon and a header/title 31 | a main text 32 | an iniatially hidden secondary help text that can be expanded 33 | followed by a footer 34 | """ 35 | 36 | def __init__(self, parent, header_text, basic_text, advanced_topics): 37 | """ Create a new instance 38 | :param parent: The parent window/dialog used to enable modal behaviour 39 | :type parent: :class:`PyQt5.QtGui.QWidget` 40 | :param header_text: Displayed in the top of the dialog next to the help icon 41 | :type header_text: str 42 | :param basic_text: Displayed in the middle of the help dialog 43 | :type basic_text: str 44 | :param advanced_topics: Displayed below the basic text, initially only header is shown and content hidden 45 | :type advanced_topics: Array of dicts with str head and text properties 46 | """ 47 | super().__init__(parent, Qt.WindowCloseButtonHint | Qt.WindowTitleHint) 48 | self.setWindowTitle(_('Help')) 49 | layout = QVBoxLayout() 50 | layout.setContentsMargins(15, 5, 15, 5) 51 | layout.setSpacing(5) 52 | # icon and header 53 | header = QHBoxLayout() 54 | header.setSpacing(80) 55 | header.setAlignment(Qt.AlignLeft) 56 | icon = QLabel() 57 | icon.setPixmap(QApplication.style().standardIcon(QStyle.SP_DialogHelpButton).pixmap(48)) 58 | header.addWidget(icon) 59 | header.addWidget(QLabel(header_text)) 60 | layout.addLayout(header) 61 | # main help text 62 | basic_text = QLabel(basic_text) 63 | basic_text.setWordWrap(True) 64 | basic_text.setAlignment(Qt.AlignJustify) 65 | basic_text.setFixedWidth(470) # Qt produces unreliable layout when using wordwrap and non-fixed width 66 | basic_text.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 67 | layout.addWidget(basic_text) 68 | # advanced help 69 | advanced = QLabel('' + _('Advanced Topics:') + '') 70 | layout.addWidget(advanced) 71 | self._advanced_topics = [] 72 | 73 | for topic in advanced_topics: 74 | head = QExpander(topic['head'], self, False) 75 | layout.addWidget(head) 76 | text = QLabel(topic['text']) 77 | text.setWordWrap(True) 78 | text.setAlignment(Qt.AlignJustify) 79 | text.setFixedWidth(470) 80 | text.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 81 | layout.addWidget(text) 82 | head.addWidgets([text]) 83 | self._advanced_topics += [head] 84 | head.clicked.connect(self._on_topic_clicked) 85 | 86 | # footer 87 | layout.addStretch() 88 | hl = QFrame() 89 | hl.setFrameShape(QFrame.HLine) 90 | hl.setFrameShadow(QFrame.Sunken) 91 | layout.addWidget(hl) 92 | 93 | footer = QLabel(_('luckyLUKS version {version}\n' 94 | 'For more information, visit\n' 95 | '{project_url}').format(version=VERSION_STRING, 96 | project_url=PROJECT_URL)) 97 | footer.setContentsMargins(0, 10, 0, 10) 98 | layout.addWidget(footer) 99 | # button 100 | ok_button = QDialogButtonBox(QDialogButtonBox.Ok, parent=self) 101 | ok_button.accepted.connect(self.accept) 102 | layout.addWidget(ok_button) 103 | 104 | self.setLayout(layout) 105 | 106 | def _on_topic_clicked(self, clicked_topic): 107 | """An expandable topic was clicked. Closes previously opened topic if necessary""" 108 | if clicked_topic.is_expanded: 109 | for topic in self._advanced_topics: 110 | if not topic == clicked_topic: 111 | topic.setExpanded(False) 112 | 113 | 114 | def show_info(parent, message, title=''): 115 | """ Helper to show info message 116 | :param parent: The parent widget to be passed to the modal dialog 117 | :type parent: :class:`PyQt5.QtGui.QWidget` 118 | :param message: The message that gets displayed in a modal dialog 119 | :type message: str 120 | :param title: Displayed in the dialogs titlebar 121 | :type title: str 122 | """ 123 | show_message(parent, message, title, QMessageBox.Information) 124 | 125 | 126 | def show_alert(parent, message, critical=False): 127 | """ Helper to show error message 128 | :param parent: The parent widget to be passed to the modal dialog 129 | :type parent: :class:`PyQt5.QtGui.QWidget` 130 | :param message: The message that gets displayed in a modal dialog 131 | :type message: str 132 | :param critical: If critical, quit application (default=False) 133 | :type critical: bool 134 | """ 135 | show_message(parent, message, _('Error'), QMessageBox.Critical if critical else QMessageBox.Warning) 136 | if critical: 137 | QApplication.instance().quit() 138 | 139 | 140 | def show_message(parent, message, title, message_type): 141 | """ Generic helper to show message 142 | :param parent: The parent widget to be passed to the modal dialog 143 | :type parent: :class:`PyQt5.QtGui.QWidget` 144 | :param message: The message that gets displayed in a modal dialog 145 | :type message: str 146 | :param title: Displayed in the dialogs titlebar 147 | :type title: str 148 | :param message_type: Type of message box to be used 149 | :type message_type: :class:`QMessageBox.Icon` 150 | """ 151 | if message != '': 152 | mb = QMessageBox(message_type, title, message, QMessageBox.Ok, parent) 153 | # make QMessageBox better adaptable to long messages (eg stacktraces) 154 | mb.findChildren(QLabel)[1].setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) 155 | mb.exec_() 156 | 157 | 158 | class QExpander(QWidget): 159 | 160 | """A Qt implementation similar to GtkExpander.""" 161 | 162 | clicked = pyqtSignal(QWidget) 163 | 164 | def __init__(self, label, parent, expanded=False): 165 | """Create a new instance.""" 166 | super().__init__(parent) 167 | self.parent = parent 168 | self.label = QExpanderLabel(label, self) 169 | self.layout = QVBoxLayout() 170 | self.setLayout(self.layout) 171 | self.layout.addWidget(self.label) 172 | self._widgets = [] 173 | self._expanded = False 174 | self.label.clicked.connect(self._on_label_clicked) 175 | self.setExpanded(expanded) 176 | 177 | def _on_label_clicked(self): 178 | """The expander widget was clicked.""" 179 | self._expanded = not self._expanded 180 | self.setExpanded(self._expanded) 181 | self.clicked.emit(self) 182 | 183 | def addWidgets(self, widgets): 184 | """Add widgets to the expander. 185 | """ 186 | self._widgets += widgets 187 | self.setExpanded(self._expanded) 188 | 189 | def is_expanded(self): 190 | """Return if widget is expanded.""" 191 | return self._expanded 192 | 193 | def setExpanded(self, is_expanded): 194 | """Expand the widget or not.""" 195 | self._expanded = is_expanded 196 | if self._expanded: 197 | self.label.arrow.direction = QArrow.DOWN 198 | else: 199 | self.label.arrow.direction = QArrow.RIGHT 200 | for widget in self._widgets: 201 | widget.setVisible(self._expanded) 202 | self.parent.adjustSize() 203 | 204 | 205 | class QExpanderLabel(QWidget): 206 | 207 | """Widget used to show/modify the label of a QExpander.""" 208 | 209 | clicked = pyqtSignal() 210 | 211 | def __init__(self, label, parent): 212 | """Create a new instance.""" 213 | super().__init__(parent) 214 | self.arrow = QArrow(QArrow.RIGHT) 215 | self.label = QLabel(label) 216 | layout = QHBoxLayout() 217 | self.setLayout(layout) 218 | layout.addWidget(self.arrow) 219 | layout.addWidget(self.label) 220 | 221 | def mousePressEvent(self, event): 222 | """Mouse clicked.""" 223 | if self.arrow.direction == QArrow.DOWN: 224 | self.arrow.direction = QArrow.RIGHT 225 | else: 226 | self.arrow.direction = QArrow.DOWN 227 | self.clicked.emit() 228 | 229 | 230 | class QArrow(QWidget): 231 | 232 | """Custom widget, arrow image that can be pointed in 4 different directions""" 233 | 234 | UP = 0 235 | DOWN = 1 236 | LEFT = 2 237 | RIGHT = 3 238 | 239 | def __init__(self, direction, parent=None): 240 | """Create a new instance.""" 241 | super().__init__(parent) 242 | self._set_direction(direction) 243 | self.setFixedWidth(10) 244 | 245 | def paintEvent(self, event): 246 | """Paint the widget.""" 247 | opt = QStyleOption() 248 | opt.initFrom(self) 249 | painter = QPainter(self) 250 | if self._direction == QArrow.UP: 251 | primitive = QStyle.PE_IndicatorArrowUp 252 | elif self._direction == QArrow.DOWN: 253 | primitive = QStyle.PE_IndicatorArrowDown 254 | elif self._direction == QArrow.LEFT: 255 | primitive = QStyle.PE_IndicatorArrowLeft 256 | else: 257 | primitive = QStyle.PE_IndicatorArrowRight 258 | painter.setViewTransformEnabled(True) 259 | self.style().drawPrimitive(primitive, opt, painter, self) 260 | 261 | def _get_direction(self): 262 | """Return the direction used.""" 263 | return self._direction 264 | 265 | def _set_direction(self, direction): 266 | """Set the direction.""" 267 | if direction not in (QArrow.UP, QArrow.DOWN, 268 | QArrow.LEFT, QArrow.RIGHT): 269 | raise ValueError('Wrong arrow direction.') 270 | self._direction = direction 271 | self.repaint() 272 | 273 | direction = property(_get_direction, _set_direction) 274 | -------------------------------------------------------------------------------- /luckyLUKS/unlockUI.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains 3 different graphical modal dialogs to ask a user for password/-phrase. 3 | Each is based on a common password dialog and offers a method to use the dialog 4 | in a synchronous way ie to run itself and return a result or perform an action, 5 | or throw an exception if this fails 6 | 7 | luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 8 | 9 | This program is free software: you can redistribute it and/or modify 10 | it under the terms of the GNU General Public License as published by 11 | the Free Software Foundation, either version 3 of the License, or 12 | (at your option) any later version. 13 | 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | """ 19 | 20 | from PyQt5.QtCore import Qt 21 | from PyQt5.QtWidgets import QDialog, QMessageBox, QDialogButtonBox, QStyle, \ 22 | QLabel, QVBoxLayout, QHBoxLayout, QLineEdit, QCheckBox, QLayout, QApplication 23 | from PyQt5.QtGui import QIcon 24 | 25 | 26 | class UserInputError(Exception): 27 | """ Raised if user cancels a password dialog """ 28 | 29 | 30 | class PasswordDialog(QDialog): 31 | 32 | """ Basic dialog with a textbox input field for the password/-phrase and OK/Cancel buttons """ 33 | 34 | def __init__(self, parent, message, title='Enter Password'): 35 | """ 36 | :param parent: The parent window/dialog used to enable modal behaviour 37 | :type parent: :class:`PyQt5.QtGui.QWidget` 38 | :param message: The message that gets displayed above the textbox 39 | :type message: str 40 | :param title: Displayed in the dialogs titlebar 41 | :type title: str or None 42 | """ 43 | super().__init__(parent, Qt.WindowCloseButtonHint | Qt.WindowTitleHint) 44 | self.setWindowTitle(title) 45 | self.layout = QVBoxLayout() 46 | self.layout.setSizeConstraint(QLayout.SetFixedSize) 47 | self.layout.setSpacing(10) 48 | 49 | # create icon and label 50 | self.header_box = QHBoxLayout() 51 | self.header_box.setSpacing(10) 52 | self.header_box.setAlignment(Qt.AlignLeft) 53 | self.header_text = QLabel(message) 54 | icon = QLabel() 55 | icon.setPixmap( 56 | QIcon.fromTheme( 57 | 'dialog-password', 58 | QApplication.style().standardIcon(QStyle.SP_DriveHDIcon) 59 | ).pixmap(32) 60 | ) 61 | self.header_box.addWidget(icon) 62 | self.header_box.addWidget(self.header_text) 63 | self.layout.addLayout(self.header_box) 64 | 65 | # create the text input field 66 | self.pw_box = QLineEdit() 67 | self.pw_box.setMinimumSize(0, 25) 68 | # password will not be shown on screen 69 | self.pw_box.setEchoMode(QLineEdit.Password) 70 | self.layout.addWidget(self.pw_box) 71 | self.layout.addSpacing(10) 72 | 73 | # OK and Cancel buttons 74 | self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, parent=self) 75 | self.buttons.accepted.connect(self.on_accepted) 76 | self.buttons.rejected.connect(self.reject) 77 | self.layout.addWidget(self.buttons) 78 | 79 | self.setLayout(self.layout) 80 | self.pw_box.setFocus() 81 | 82 | def on_accepted(self): 83 | """ Event handler for accept signal: block when input field empty """ 84 | if self.pw_box.text() != '': 85 | self.accept() 86 | else: 87 | self.pw_box.setFocus() 88 | 89 | def get_password(self): 90 | """ Dialog runs itself and returns the password/-phrase entered 91 | or throws an exception if the user cancelled/closed the dialog 92 | :returns: The entered password/-phrase 93 | :rtype: str 94 | :raises: UserInputError 95 | """ 96 | try: 97 | if self.exec_(): 98 | return str(self.pw_box.text()) 99 | raise UserInputError() 100 | finally: 101 | self.destroy() 102 | 103 | 104 | class SudoDialog(PasswordDialog): 105 | 106 | """ Modified PasswordDialog that adds a checkbox asking for permanent sudo access permission""" 107 | 108 | def __init__(self, parent, message, toggle_function): 109 | """ :param parent: The parent window/dialog used to enable modal behaviour 110 | :type parent: :class:`PyQt5.QtGui.QWidget` 111 | :param message: The message that gets displayed above the textbox 112 | :type message: str 113 | :param toggle_function: A function that accepts a boolean value, propagates current state of the checkbox 114 | :type toggle_function: function(boolean) 115 | """ 116 | super().__init__(parent, message=message, title=_('luckyLUKS')) 117 | 118 | # add checkbox to dialog 119 | self.toggle_function = toggle_function 120 | self.store_cb = QCheckBox('') # QCheckBox supports only string labels .. 121 | # using QLabel because markup is needed for linebreak 122 | cb_label = QLabel(_('Always allow luckyLUKS to be run\nwith administrative privileges')) 123 | # connect clicked on QLabel to fully emulate QCheckbox behaviour 124 | cb_label.mouseReleaseEvent = self.toggle_cb 125 | self.store_cb.stateChanged.connect(self.on_cb_toggled) 126 | self.toggle_function(False) # allowing permanent access has to be confirmed explicitly 127 | 128 | self.sudo_box = QHBoxLayout() 129 | self.sudo_box.setSpacing(5) 130 | self.sudo_box.addWidget(self.store_cb) 131 | self.sudo_box.addWidget(cb_label) 132 | self.sudo_box.addStretch() 133 | self.layout.insertLayout(2, self.sudo_box) 134 | 135 | def toggle_cb(self, event): 136 | """ Slot for QCheckbox behaviour emulation: toggles checkbox """ 137 | self.store_cb.setChecked(not self.store_cb.isChecked()) 138 | 139 | def on_cb_toggled(self, state): 140 | """ Event handler for checkbox toggle: propagate new value to parent """ 141 | self.toggle_function(self.store_cb.isChecked()) 142 | 143 | 144 | class FormatContainerDialog(PasswordDialog): 145 | 146 | """ Modified PasswordDialog that shows the input on screen and requests confirmation for close/cancel """ 147 | 148 | def __init__(self, parent): 149 | """ :param parent: The parent window/dialog used to enable modal behaviour 150 | :type parent: :class:`PyQt5.QtGui.QWidget` 151 | """ 152 | super().__init__(parent, 153 | message=_('Please choose a passphrase\nto encrypt the new container:\n'), 154 | title=_('Enter new Passphrase')) 155 | # display passphrase checkbox and set default to show input 156 | self.show_cb = QCheckBox(_('Display passphrase')) 157 | self.show_cb.stateChanged.connect(self.on_cb_toggled) 158 | self.show_box = QHBoxLayout() 159 | self.show_box.addWidget(self.show_cb) 160 | self.layout.insertLayout(2, self.show_box) 161 | self.show_cb.setChecked(True) 162 | 163 | def on_cb_toggled(self): 164 | """ Event handler for checkbox toggle: show/hide passphrase input on screen """ 165 | if self.show_cb.isChecked(): 166 | # show input on screen 167 | self.pw_box.setEchoMode(QLineEdit.Normal) 168 | else: 169 | # hide input on screen 170 | self.pw_box.setEchoMode(QLineEdit.Password) 171 | 172 | def closeEvent(self, event): 173 | """ Event handler confirm close """ 174 | if not self.confirm_close_cancel(): 175 | event.ignore() 176 | 177 | def reject(self): 178 | """ Event handler confirm cancel """ 179 | if self.confirm_close_cancel(): 180 | super().reject() 181 | else: 182 | self.pw_box.setFocus() 183 | 184 | def confirm_close_cancel(self): 185 | """ Display dialog for close/cancel confirmation 186 | :returns: The users decision 187 | :rtype: bool 188 | """ 189 | message = _('Currently creating new container!\nDo you really want to quit?') 190 | mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) 191 | mb.button(QMessageBox.Ok).setText(_('Quit')) 192 | return mb.exec_() == QMessageBox.Ok 193 | 194 | 195 | class UnlockContainerDialog(PasswordDialog): 196 | 197 | """ Modified PasswordDialog that communicates with the worker process to unlock an encrypted container """ 198 | 199 | def __init__(self, parent, worker, luks_device_name, encrypted_container, key_file=None, mount_point=None): 200 | """ :param parent: The parent window/dialog used to enable modal behaviour 201 | :type parent: :class:`PyQt5.QtGui.QWidget` 202 | :param worker: Communication handler with the worker process 203 | :type worker: :class:`helper.WorkerMonitor` 204 | :param luks_device_name: The device mapper name 205 | :type luks_device_name: str 206 | :param encrypted_container: The path of the container file 207 | :type encrypted_container: str 208 | :param key_file: The path of an optional key file 209 | :type key_file: str or None 210 | :param mount_point: The path of an optional mount point 211 | :type mount_point: str or None 212 | """ 213 | super().__init__(parent, _('Initializing ..'), luks_device_name) 214 | 215 | self.worker = worker 216 | self.error_message = '' 217 | 218 | if key_file is not None: 219 | self.header_text.setText( 220 | _('Using keyfile\n{keyfile}\nto open container.\n\nPlease wait ..') 221 | .format(keyfile=key_file) 222 | ) 223 | self.pw_box.hide() 224 | self.buttons.hide() 225 | self.header_text.setContentsMargins(10, 10, 10, 10) 226 | else: 227 | self.buttons.button(QDialogButtonBox.Ok).setText(_('Unlock')) 228 | # disable input until worker initialized 229 | self.pw_box.setEnabled(False) 230 | self.buttons.setEnabled(False) 231 | 232 | self.waiting_for_response = True 233 | # call worker 234 | self.worker.execute(command={'type': 'request', 235 | 'msg': 'unlock', 236 | 'device_name': luks_device_name, 237 | 'container_path': encrypted_container, 238 | 'mount_point': mount_point, 239 | 'key_file': key_file 240 | }, 241 | success_callback=self.on_worker_reply, 242 | error_callback=self.on_error) 243 | 244 | def on_accepted(self): 245 | """ Event handler send password/-phrase if worker ready """ 246 | # dont send empty password 247 | if self.pw_box.text() == '': 248 | self.pw_box.setFocus() 249 | elif not self.waiting_for_response: 250 | # worker is ready, send request 251 | self.waiting_for_response = True 252 | self.pw_box.setEnabled(False) 253 | self.buttons.setEnabled(False) 254 | self.header_text.setText(_('Checking passphrase ..')) 255 | self.worker.execute(command={'type': 'response', 256 | 'msg': str(self.pw_box.text()) 257 | }, 258 | success_callback=self.on_worker_reply, 259 | error_callback=self.on_error) 260 | 261 | def reject(self): 262 | """ Event handler cancel: 263 | Block while waiting for response or notify worker with abort message 264 | """ 265 | if not self.waiting_for_response: 266 | self.worker.execute({'type': 'abort', 'msg': ''}, None, None) 267 | super().reject() 268 | 269 | def on_error(self, error_message): 270 | """ Error-Callback: set errormessage and trigger Cancel """ 271 | self.error_message = error_message 272 | super().reject() 273 | 274 | def on_worker_reply(self, message): 275 | """ Success-Callback: trigger OK when unlocked, reset dialog if not """ 276 | if message == 'success': 277 | self.accept() 278 | else: 279 | if self.pw_box.text() == '': # init 280 | self.header_text.setText(_('Please enter\ncontainer passphrase:')) 281 | else: # at least one previous pw attempt 282 | self.header_text.setText(_('Wrong passphrase, please retry!\nEnter container passphrase:')) 283 | self.buttons.setEnabled(True) 284 | self.pw_box.setText('') 285 | self.pw_box.setEnabled(True) 286 | self.pw_box.setFocus() 287 | self.waiting_for_response = False 288 | 289 | def closeEvent(self, event): 290 | """ Event handler close: block while waiting for response or notify worker with abort message """ 291 | if self.waiting_for_response: 292 | event.ignore() 293 | else: 294 | self.worker.execute({'type': 'abort', 'msg': ''}, None, None) 295 | 296 | def communicate(self): 297 | """ Dialog runs itself and throws an exception if the container wasn't unlocked 298 | :raises: UserInputError 299 | """ 300 | try: 301 | if not self.exec_(): 302 | raise UserInputError(self.error_message) # empty string if cancel/delete -> won't get displayed 303 | finally: 304 | self.destroy() 305 | -------------------------------------------------------------------------------- /luckyLUKS/utils.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper to establish a root-worker process and ipc handler. 3 | 4 | luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | """ 16 | 17 | import os 18 | import select 19 | import fcntl 20 | import subprocess 21 | import sys 22 | import json 23 | import traceback 24 | 25 | from PyQt5.QtCore import QThread, QEvent 26 | from PyQt5.QtWidgets import QApplication 27 | 28 | from luckyLUKS.unlockUI import PasswordDialog, SudoDialog, UserInputError 29 | from luckyLUKS.utilsUI import show_alert, show_info 30 | 31 | 32 | class SudoException(Exception): 33 | """ Errors while establishing a worker process with elevated privileges using sudo""" 34 | 35 | 36 | class WorkerMonitor(QThread): 37 | 38 | """ Establishes an asynchronous communication channel with the worker process: 39 | Since the worker executes only one task at a time queueing/sophisticated ipc are not needed. 40 | After execute is called with command and callbacks, further commands will get blocked 41 | until an answer from the worker arrives. The answer will get injected into the UI-loop 42 | -> the UI stays responsive, and thus has to disable buttons etc to prevent the user from 43 | sending additional commands 44 | """ 45 | 46 | def __init__(self, parent): 47 | """ :param parent: The parent widget to be passed to modal dialogs 48 | :type parent: :class:`PyQt5.QtGui.QWidget` 49 | :raises: SudoException 50 | """ 51 | super().__init__() 52 | self.parent = parent 53 | self.success_callback, self.error_callback = None, None 54 | self.modify_sudoers = False 55 | self.worker = None 56 | self._spawn_worker() 57 | 58 | if self.modify_sudoers: # adding user/program to /etc/sudoers.d/ requested 59 | self.execute({'type': 'request', 'msg': 'authorize'}, None, None) 60 | response = json.loads(self.worker.stdout.readline().strip()) # blocks 61 | if response['type'] == 'error': 62 | show_alert(self.parent, response['msg']) 63 | else: 64 | message = _('Permanent `sudo` authorization for\n' 65 | '{program}\n' 66 | 'has been successfully added for user `{username}` to \n' 67 | '/etc/sudoers.d/lucky-luks\n').format( 68 | program=os.path.abspath(sys.argv[0]), 69 | username=os.getenv("USER")) 70 | show_info(self.parent, message, _('Success')) 71 | 72 | def _spawn_worker(self): 73 | """ Init worker subprocess with sudo && setup ipc handler 74 | :raises: SudoException 75 | """ 76 | # using poll to wait for feedback from sudo 77 | self.pipe_events = select.poll() 78 | self._connect_to_sudo() 79 | dlg_message = _('luckyLUKS needs administrative privileges.\nPlease enter your password:') 80 | incorrent_pw_entered = False 81 | 82 | try: 83 | while True: 84 | __, event = self.pipe_events.poll()[0] # blocking 85 | # sudo process wrote to pipe -> read message 86 | msg = self.worker.stdout.read() 87 | 88 | if event & select.POLLIN: 89 | if 'ESTABLISHED' in msg: 90 | # Helper process initialized, from here on all com-messages on the pipe 91 | # will be terminated with newline -> switch back to blocking IO 92 | fl = fcntl.fcntl(self.worker.stdout.fileno(), fcntl.F_GETFL) 93 | fcntl.fcntl(self.worker.stdout.fileno(), fcntl.F_SETFL, fl & (~os.O_NONBLOCK)) 94 | break 95 | 96 | if 'SUDO_PASSWD_PROMPT' in msg: 97 | if incorrent_pw_entered: 98 | dlg_msg = _('Sorry, incorrect password.\n') + dlg_message 99 | else: 100 | dlg_msg = dlg_message 101 | self.worker.stdin.write( 102 | SudoDialog(parent=self.parent, 103 | message=dlg_msg, 104 | toggle_function=lambda val: setattr(self, 'modify_sudoers', val) 105 | ).get_password() + '\n') 106 | self.worker.stdin.flush() 107 | incorrent_pw_entered = True 108 | 109 | elif 'incorrect password attempts' in msg: 110 | # max password attempts reached -> restart sudo process and continue 111 | self._connect_to_sudo() 112 | 113 | elif 'not allowed to execute' in msg or 'not in the sudoers file' in msg: 114 | dlg_su_message = _('You are not allowed to execute this script with `sudo`.\n' 115 | 'If you want to modify your `sudo` configuration,\n' 116 | 'please enter the root/administrator password.\n') 117 | incorrent_pw_entered = False 118 | while True: 119 | master, slave = os.openpty() # su has to be run from a terminal 120 | p = subprocess.Popen( 121 | "su -c '" + sys.argv[0] + " --ishelperprocess --sudouser " + str(os.getuid()) + "'", 122 | shell=True, stdin=slave, stderr=subprocess.PIPE, stdout=subprocess.PIPE, 123 | universal_newlines=True, close_fds=True 124 | ) 125 | if incorrent_pw_entered: 126 | dlg_msg = _('Sorry, incorrect password.\n') + dlg_su_message 127 | else: 128 | dlg_msg = dlg_su_message 129 | os.write(master, (PasswordDialog(parent=self.parent, 130 | message=dlg_msg 131 | ).get_password() + '\n').encode('UTF-8')) 132 | p.wait() 133 | 134 | if p.returncode == 0: 135 | show_info( 136 | self.parent, 137 | _('`sudo` configuration successfully modified, now\n' 138 | 'you can use luckyLUKS with your user password.\n\n' 139 | 'If you want to grant permanent administrative rights\n' 140 | 'just tick the checkbox in the following dialog.\n'), 141 | _('Success') 142 | ) 143 | incorrent_pw_entered = False 144 | self._connect_to_sudo() 145 | break 146 | if p.returncode == 1: 147 | incorrent_pw_entered = True 148 | else: 149 | # worker prints exceptions to stdout 150 | # to keep them separated from 'su: Authentication failure' 151 | raise SudoException(p.stdout.read()) 152 | 153 | elif event & select.POLLERR or event & select.POLLHUP: 154 | raise SudoException(msg) 155 | 156 | except SudoException: # don't touch 157 | raise 158 | except UserInputError as e: # user cancelled dlg -> quit without msg 159 | raise SudoException() from e 160 | except Exception as e: # catch ANY other exception to show via gui 161 | raise SudoException( 162 | _('Communication with sudo process failed\n{error}') 163 | .format(error=''.join(traceback.format_exception(*sys.exc_info()))) 164 | ) from e 165 | finally: 166 | try: 167 | self.pipe_events.unregister(self.worker.stdout.fileno()) 168 | except KeyError: 169 | pass # fd might already gone (IOError etc) 170 | del self.pipe_events 171 | 172 | def _connect_to_sudo(self): 173 | """ Calls worker process with sudo and initializes pipes for communication """ 174 | if self.worker is not None: 175 | # disconnect event listener and wait for process termination 176 | self.pipe_events.unregister(self.worker.stdout.fileno()) 177 | self.worker.wait() 178 | # since output from sudo gets parsed, it needs to be run without localization 179 | # saving original language settings / LC-environment to pass to the worker process 180 | original_language = os.getenv("LANGUAGE", "") 181 | env_lang_cleared = {prop: os.environ[prop] for prop in os.environ if prop[0:3] == 'LC_' or prop == 'LANG'} 182 | env_lang_cleared['LANGUAGE'] = 'C' 183 | cmd = ['sudo', '-S', '-p', 'SUDO_PASSWD_PROMPT', 184 | 'LANGUAGE=' + original_language, 185 | os.path.abspath(sys.argv[0]), 186 | '--ishelperprocess'] 187 | self.worker = subprocess.Popen(cmd, 188 | stdin=subprocess.PIPE, stderr=subprocess.STDOUT, 189 | stdout=subprocess.PIPE, universal_newlines=True, env=env_lang_cleared) 190 | # switch pipe to non-blocking IO 191 | fd = self.worker.stdout.fileno() 192 | fl = fcntl.fcntl(fd, fcntl.F_GETFL) 193 | fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK) 194 | # connect event listener 195 | self.pipe_events.register(self.worker.stdout.fileno(), select.POLLIN) 196 | 197 | def run(self): 198 | """ Listens on workers stdout and executes callbacks when answers arrive """ 199 | while True: 200 | try: 201 | buf = self.worker.stdout.readline() # blocks 202 | if buf: # check if worker output pipe closed 203 | response = json.loads(buf.strip()) 204 | else: 205 | return 206 | assert('type' in response and 'msg' in response) 207 | # there should be somebody waiting for an answer! 208 | assert(self.success_callback is not None and self.error_callback is not None) 209 | # valid response received 210 | if response['type'] == 'error': 211 | QApplication.postEvent(self.parent, WorkerEvent(self.error_callback, response['msg'])) 212 | else: 213 | QApplication.postEvent(self.parent, WorkerEvent(self.success_callback, response['msg'])) 214 | # reset callbacks 215 | self.success_callback, self.error_callback = None, None 216 | 217 | except ValueError: 218 | # worker didn't return json -> probably crashed, show everything printed to stdout 219 | os.set_blocking(self.worker.stdout.fileno(), False) 220 | buf += str(self.worker.stdout.readlines()) 221 | QApplication.postEvent( 222 | self.parent, 223 | WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), 224 | response=_('Error in communication:\n{error}').format(error=_(buf))) 225 | ) 226 | return 227 | 228 | except (IOError, AssertionError) as communication_error: 229 | QApplication.postEvent( 230 | self.parent, 231 | WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), 232 | response=_('Error in communication:\n{error}').format(error=str(communication_error))) 233 | ) 234 | return 235 | 236 | def execute(self, command, success_callback, error_callback): 237 | """ Writes command to workers stdin and sets callbacks for listener thread 238 | :param command: The function to be done by the worker is in command[`msg`] 239 | the arguments are passed as named properties command[`device_name`] etc. 240 | :type command: dict 241 | :param success_callback: The function to be called if the worker finished successfully 242 | :type success_callback: function 243 | :param error_callback: The function to be called if the worker returns an error 244 | :type error_callback: function 245 | """ 246 | try: 247 | # valid command obj? 248 | assert('type' in command and 'msg' in command) 249 | # channel clear? (no qeue neccessary for the backend process) 250 | assert(self.success_callback is None and self.error_callback is None) 251 | self.success_callback = success_callback 252 | self.error_callback = error_callback 253 | self.worker.stdin.write(json.dumps(command) + '\n') 254 | self.worker.stdin.flush() 255 | except (IOError, AssertionError) as communication_error: 256 | QApplication.postEvent( 257 | self.parent, 258 | WorkerEvent(callback=lambda msg: show_alert(self.parent, msg, critical=True), 259 | response=_('Error in communication:\n{error}').format(error=str(communication_error))) 260 | ) 261 | 262 | 263 | class WorkerEvent(QEvent): 264 | 265 | """ thread-safe callback execution by raising 266 | these custom events in the main ui loop 267 | """ 268 | EVENT_TYPE = QEvent.Type(QEvent.registerEventType()) 269 | 270 | def __init__(self, callback, response): 271 | """ A WorkerEvent encapsulates a function to be called in the main ui loop and its argument 272 | :param callback: The function to be called when the event gets processed 273 | :type callback: function 274 | :param response: Response message from the worker, passed as argument to the callback function 275 | :type response: str 276 | """ 277 | QEvent.__init__(self, WorkerEvent.EVENT_TYPE) 278 | self.callback = callback 279 | self.response = response 280 | 281 | 282 | class KeyfileCreator(QThread): 283 | 284 | """ Create a 1KByte key file with random data 285 | Worker thread to avoid blocking the ui loop 286 | """ 287 | 288 | def __init__(self, parent, path): 289 | """ :param parent: The parent widget to be passed to modal dialogs 290 | :type parent: :class:`PyQt5.QtGui.QWidget` 291 | :param path: The designated key file path 292 | :type path: str 293 | """ 294 | super().__init__() 295 | self.parent = parent 296 | self.path = path 297 | self.process = None 298 | 299 | def run(self): 300 | """ Spawns child process and passes a WorkerEvent to the main event loop when finished """ 301 | try: 302 | output_file = str(self.path) 303 | except UnicodeEncodeError: 304 | output_file = self.path.encode('utf-8') # assume uft8 encoding for shell - see worker 305 | # oflag=excl -> fail if the output file already exists 306 | cmd = ['dd', 'if=/dev/random', 'of=' + output_file, 'bs=1', 'count=1024', 'conv=excl'] 307 | with open(os.devnull) as DEVNULL: 308 | self.process = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=DEVNULL, 309 | universal_newlines=True, close_fds=True) 310 | __, errors = self.process.communicate() 311 | if self.process.returncode != 0: 312 | QApplication.postEvent( 313 | self.parent.parent(), 314 | WorkerEvent(callback=lambda msg: self.parent.display_create_failed(msg, stop_timer=True), 315 | response=_('Error while creating key file:\n{error}').format(error=errors)) 316 | ) 317 | else: 318 | QApplication.postEvent( 319 | self.parent.parent(), 320 | WorkerEvent(callback=lambda msg: self.parent.on_keyfile_created(msg), 321 | response=self.path) 322 | ) 323 | 324 | def terminate(self): 325 | """ kill dd process """ 326 | self.process.kill() 327 | 328 | 329 | def is_installed(executable): 330 | """ Checks if executable is present 331 | Because the executables will be run by the privileged worker process, 332 | the usual root path gets added to the users environment path. 333 | Note: an executable at a custom user path will only be used by the worker process, 334 | if it is also present in the root path -> therefore this check might not be 100% accurate, 335 | but almost always sufficient. Checking the real root path would require calling 336 | the worker process, this way in rare cases the worker might throw an error on startup 337 | :param executable: executable to search for 338 | :type executable: str 339 | :returns: True if executable found 340 | :rtype: bool 341 | """ 342 | return any([os.path.exists(os.path.join(p, executable)) 343 | for p in os.environ["PATH"].split(os.pathsep) + ['/sbin', '/usr/sbin'] 344 | ]) 345 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | luckyLUKS 2 | ========= 3 | luckyLUKS is a Linux GUI for creating and (un-)locking encrypted volumes from container files. Unlocked containers leave an icon in the systray \ 4 | as a reminder to close them eventually ;) Supports cryptsetup/LUKS and Truecrypt container files. 5 | 6 | luckyLUKS was brought to life to offer an equivalent to the Windows TrueCrypt application. Although most Linux distributions provide excellent support for \ 7 | encrypted partitions these days - you can choose to run from a completely encrypted harddrive on installation with one or two clicks - the situation with \ 8 | encrypted containers is not that great. An encrypted container is basically a large file which encapsulates an encrypted partition. This approach has some advantages, especially for casual computer users: 9 | 10 | - No need to deal with partition table wizardry when creating an encrypted container, you basically create a file on a harddrive, it doesn't matter if its an internal one or an external usbstick etc.. 11 | - Backup is straightforward as well, just copy the file somewhere else - done! No need to backup your precious data unencrypted 12 | - Share confidential information by copying the container file. Similar to gpg encrypted archives but easier to handle (unlock - view or modify data - lock again) 13 | - You can easily add some encrypted private data to an unencrypted external harddrive you want to share with friends or take with you while travelling 14 | - Lots of users are already quite familiar with all this, because their first touch with data encryption has been TrueCrypt which uses the encrypted container approach 15 | 16 | luckyLUKS follows a keep-it-simple philosophy that aims to keep users from shooting themselves in the foot and might be a bit too simple for power users - \ 17 | please use `ZuluCrypt `_ and/or `cryptsetup `_/`tcplay `_ on the command line \ 18 | if you need special options when creating new containers. On the other hand, to unlock existing containers luckyLUKS offers all you need and the possibility \ 19 | to create a shortcut to a container in your start menu or on the desktop. From the shortcut its just one click and you can enter your password to \ 20 | unlock the container. For technical details please see the FAQ at the end of this page. For a first impression: 21 | 22 | .. image:: https://github.com/jas-per/luckyLUKS/blob/gh-pages/screencast.gif 23 | :align: center 24 | :alt: screencast of luckyLUKS 25 | 26 | Installation 27 | ============ 28 | 29 | Since 2022 luckyLUKS is available in Debian/Ubuntu based distributions - just use your package manager to install. \ 30 | For older Ubuntu and derivates use this `ppa `_:: 31 | 32 | > sudo add-apt-repository ppa:jas-per/lucky-luks 33 | > sudo apt-get update && sudo apt-get upgrade 34 | > sudo apt-get install luckyluks 35 | 36 | (For Ubuntu LTS <20.04 install :code:`python-luckyLUKS` or :code:`python3-luckyLUKS` still present in that ppa) 37 | 38 | Alternatively Debian based distributions can use this Debian package and install manually: 39 | 40 | `luckyluks_2.1.0_all.deb `_ 41 | 42 | On other distriubutions you can use the following zip-packaged python file: 43 | 44 | `luckyLUKS-2.1.0 `_ 45 | 46 | This file contains all resources and can be executed directly by the python intepreter. Place in :code:`/usr/bin` and change ownership to root:: 47 | 48 | > sudo mv luckyLUKS-2.1.0 /usr/bin/ 49 | > sudo chown root:root /usr/bin/luckyLUKS-2.1.0 50 | > sudo chmod 755 /usr/bin/luckyLUKS-2.1.0 51 | 52 | Then start with :code:`luckyLUKS-2.1.0` on the command line or create a desktop shortcut manually. 53 | 54 | Dependencies 55 | ------------ 56 | 57 | To run luckyLUKS, make sure you have the following installed: 58 | 59 | - :code:`python3` 60 | - :code:`cryptsetup` 61 | - :code:`sudo` 62 | - :code:`python3-pyqt5` 63 | - :code:`tcplay` (if you want to create TrueCrypt containers) 64 | 65 | When using the ubuntu-ppa these will get installed automatically, if you use the deb-/zip-package \ 66 | please install the dependencies manually with your distributions repository tools. 67 | 68 | Desktop environments / distributions 69 | ------------------------------------ 70 | 71 | luckyLUKS gets tested with the major desktop environments: 72 | 73 | - :code:`gnome` (needs extension for `tray icons `_) 74 | - :code:`kde` 75 | - :code:`ubuntu gnome` 76 | - :code:`xfce` 77 | - :code:`cinnamon` 78 | - :code:`mate` 79 | - :code:`lxqt` 80 | 81 | There are also some distribution specifics with Debian: 82 | 83 | - since debian doesn't rely on sudo you have to manually add your user to the sudo group:: 84 | 85 | > su 86 | > usermod -aG sudo USERNAME 87 | 88 | - dev-mapper is not configured to do automatic mounts on some desktop environments:: 89 | 90 | please use the 'mount point' option in luckyLUKS 91 | 92 | Using luckyLUKS with a wayland-based display server / compositor instead of Xorg is possible eg with `gnome`, `kde` or `sway` \ 93 | and for security reasons this is very much recommended! There is still some work left to get wayland running smooth though, \ 94 | so check usability for yourself - things like gaming, input drivers, screen recording and also tray icon functionality \ 95 | might stop you from using a wayland compositor yet. 96 | 97 | 98 | FAQ 99 | === 100 | 101 | luckyLUKS is basically a GUI wrapper for two command line tools: `cryptsetup` and `tcplay`. The cryptsetup project has an excellent `FAQ `_ that explains the underlying cryptography and security in great detail. \ 102 | If you want to know more e.g. about choosing a secure password or further protecting your computer, please read the cryptsetup FAQ first. The following \ 103 | information mainly refers to questions specific to encrypted containers and luckyLUKS as a graphical interface to cryptsetup and tcplay. 104 | 105 | Backup 106 | ------ 107 | 108 | There is a whole chapter in the cryptsetup FAQ dealing with backup details. This is because cryptsetup is normally used for encrypted partitions, which complicates things a bit. Since luckyLUKS uses encrypted containers, backup is rather straightforward - just copy the whole container and you're done. \ 109 | By copying you technically create a clone of the encrypted LUKS container - see section 6.15 in the cryptsetup `FAQ `_ in case you would like to change your passphrase later on. 110 | 111 | Key files 112 | --------- 113 | 114 | A key file can be used to allow access to an encrypted container instead of a password. Using a key file resembles unlocking a door with a key in the real world - anyone with access to the key file can open your encrypted container. Make sure to store it at a protected location. \ 115 | Its okay to store it on your computer if you are using a digital keystore or an already encrypted harddrive that you unlock on startup with a password. Having the key file on a `small USB drive `_ attached to your real chain of keys \ 116 | would be an option as well. Since you don't have to enter a password, using a key file can be a convenient way to access your encrypted container. Just make sure you don't lose the key (file) - backup to a safe location separate from the encrypted container. Printing the raw data \ 117 | (use a hex-editor/viewer) to paper is fine as a last resort as well. 118 | 119 | Although basically any file could be used as a key file, a file with predictable content leads to similar problems as using weak passwords. Audio files or pictures are a good choice. If unsure use the 'create key file' function in luckyLUKS to generate a small key file filled with random data. 120 | 121 | With LUKS it is also possible to use both, a passphrase and a keyfile. LUKS uses a concept called 'keyslots' that enables up to 8 keys to be used exchangeably to unlock a container. You could use a keyfile to unlock a container on an external drive when using your own computer with an already encrypted system, \ 122 | and a passphrase to open the same container on a different computer or in case you lost the keyfile. Because it might be a bit confusing for casual users, this option is not provided in the graphical interface of luckyLUKS. If you want to use it, you have to do the following once on the command line: 123 | 124 | - generate a new keyfile with luckyLUKS 125 | - open the container with luckyLUKS 126 | - check which loopback device is used: :code:`sudo losetup -a` 127 | - view the LUKS keyslots of this container: :code:`sudo cryptsetup luksDump /dev/loopX` 128 | - add the keyfile to the keyslots: :code:`sudo cryptsetup luksAddKey /dev/loopX /PATH/TO/KEYFILE` 129 | - view the LUKS keyslots again and you will see another keyslot in use: :code:`sudo cryptsetup luksDump /dev/loopX` 130 | 131 | After you did this once, you can use the GUI of luckyLUKS, to open the container with either passphrase or keyfile and generate shortcuts for the startup menu as needed. 132 | 133 | The TrueCrypt format offers another possibility when using keyfiles, where you have to provide both keyfile and password to unlock a container. While this provides a nice `two factor authentication `_ it is also a more advanced approach \ 134 | that is beyond the scope of luckyLUKS - please use `ZuluCrypt `_ or the command line for this. And be aware that security through obscurity might not be the right approach for your privacy needs: a weak password combined with a keyfile \ 135 | is easily broken if the keyfile gets into the wrong hands. 136 | 137 | Sudo Access 138 | ----------- 139 | 140 | On Linux encrypted containers get mounted as loopback devices by using the device mapper infrastructure. Access to /dev/mapper is restricted to root for good reason: besides managing encrypted containers, the device mapper is also used by the Logical Volume Manager (LVM) and Software RAIDs for example. \ 141 | There have been `ideas `_ on how to allow device-mapper access without root privileges but its complicated - the device mapper developers seem to prefer controlling loopback device mounts by integrating cryptsetup into udisks/dbus/udev/policykit/systemd. \ 142 | While this approach can enable fine grained access control in userspace, it also complicates things quite substantially - nowadays it might be possible to use encrypted containers this way, but decent documentation is hard to find. 143 | 144 | So for now accessing the device mapper directly with administrative privileges is needed to use encrypted containers. Almost every Unix systems offers two ways to do this: setuid and sudo. With `setuid `_ an executable gains elevated privileges directly, \ 145 | while `sudo `_ is a program used to give elevated privileges to other executables, that can be configured to allow fine grained access control in userspace similar to the policykit framework mentioned above. With both setuid and sudo, \ 146 | it is the application developer's responsibility to take great care that the program running with elevated privileges cannot be used in any malicious way. \ 147 | Popular methods for privilege escalation in this context are buffer overruns, unsanitized environments, shell injection or toctou-attacks. 148 | 149 | Because running setuid executables does not require an additional password, setuid is generally considered a security risk and to be avoided whenever possible. There are usually very few (well reviewed) setuid binaries on a modern Linux system. Sudo on the other hand requires the user's password, \ 150 | has a long record of security-conscious development and lots of flexibility in its access control \ 151 | (e.g.. the *Ubuntu distributions or Apples OSX rely heavily on using sudo for administrative tasks). luckyLUKS uses sudo for all privileged operations and also offers the option to create a sudo-rule to allow the current user to omit their password for running luckyLUKS. 152 | 153 | The last remark on elevated privileges is about luckyLUKS graphical user interface. To minimize the possible attack surface, all UI code is run with normal user rights, while all privileged operations are executed in separate helper processes (privilege separation). 154 | 155 | Is my data/passphrase safe? 156 | --------------------------- 157 | 158 | This depends more on general computer security issues than on this particular application. In times where you cannot even trust your `hard drive `_ you have to go a long way to be at least reasonably safe from state-level attackers. \ 159 | If this is a requirement for you, consider using a readonly operating system like `Tails `_ and keep learning about computer security. Sad to say, but a GUI to unlock your encrypted data should be the least of your concerns. 160 | 161 | OK, but what about the safety of my passphrase in luckyLUKS compared to using cryptsetup/tcplay directly in a terminal? There are two areas that might be problematic: The first is the standard window system on Unix called X. The X window system originates in a time where the requirements \ 162 | and possibilities of a graphical interface where quite different from what they are now. The security architecture is fundamentally broken from todays point of view. It is for instance not possible to keep other applications from receiving all key-events - which includes the passphrase in our case \ 163 | (keep in mind that this is also true when using cryptsetup in an X-windowed terminal). That said, the successor to X called Wayland is just around the corner, if you feel adventurous try using luckyLUKS in a Wayland based compositor today. 164 | 165 | The second problem is about keeping the passphrase in memory. In general you `should `_ trust your operating system to restrict memory access. Nevertheless it is good practice to overwrite the data in memory \ 166 | as soon as unneeded while handling sensitive information. Since luckyLUKS is written in Python, direct memory access is not possible, only removing all references to the passphrase and wait for the garbage collection to clean up later. This it not a problem per-se, since you have to trust your operating system anyway, \ 167 | but can turn into a security issue when the memory content gets written to disk on hibernation or into the swapfile. When this happens any sensitive data could still be found in clear text even weeks after the computer was shut down. \ 168 | Easy solution: use `encrypted swap `_! And consider using full disk encryption, to make sure nobody with physical access to your computer can e.g.. add a keylogger on startup. 169 | 170 | OK, so whats the bottom line? LUKS or TrueCrypt containers are safe, nobody that gets access to such a container of yours will be able to open it without your passphrase. The vulnerable point is the computer you use to access the encrypted data. The degree of vulnerability depends on the resources \ 171 | and determination of an attacker. Furthermore safety is relative to your own needs being a tradeoff between comfort and security. Using luckyLUKS on your daily operating system without any further precautions will still protect your private data against almost all those prying eyes. \ 172 | If you want more certainty use full disk encryption, a live operating system like :code:`Tails` or a computer permanently disconnected from the internet in that order. 173 | 174 | Accessing containers on Windows 175 | ------------------------------- 176 | 177 | If you want to access encrypted containers on Linux and Windows, use NTFS as the filesystem inside the container. It is the only modern filesystem available on Windows and can be used from Linux as well. Since access permissions cannot be mapped from NTFS to Linux user accounts, \ 178 | access to NTFS devices is often not restricted -> take care when using unlocked NTFS devices in a multiuser environment! If you share a computer with other people like family members, always close your encrypted container before switching sessions. 179 | 180 | To access LUKS containers from Windows use `LibreCrypt `_. To access TrueCrypt containers use the original TrueCrypt or a successor like `VeraCrypt `_. 181 | 182 | 183 | Translations 184 | ============ 185 | 186 | The user interface of luckyLUKS is fully translateable, and to offer more translations your help is needed. Since the application is not too complex and more or less feature complete at this point, it won't take long to translate all the neccessary strings and translating won't be an ongoing effort. 187 | 188 | - install a translations editor (eg `Poedit `_) and `python-babel `_ 189 | - `Download `_ the source code of luckyLUKS 190 | - Open a terminal, change directory to the location of the luckyLUKS source files 191 | - Create new locale file (eg :code:`make init_locale NEW_LANG="pt"` for Portuguese, see two-letter codes `here `_) 192 | - You will find the new locale file in :code:`luckyLUKS/locale//LC_MESSAGES/luckyLUKS.po` 193 | - Edit this file in the translations editor 194 | - After editing the po file has to be compiled. Poedit can do this automatically: go to :code:`Preferences` and check :code:`Automatically compile .mo file on save`. Or use :code:`make compile_locales` from the source directory. 195 | - To test your translation, start luckyLUKS from the command line. You might have to set the locale explicitly, if your operation system is using a different locale (eg :code:`LANG=pt_PT.utf-8 LANGUAGE=pt ./luckyluks`) 196 | 197 | When you are happy with the results, mail the .po-file you created and your translation will get included in the next release. Pull requests are welcome too :) 198 | 199 | 200 | Bugs 201 | ==== 202 | 203 | Please report all bugs on the github `issue tracker `_. Since this is a GUI tool, the most important information is the exact name of the distribution including the version/year \ 204 | and the desktop environment used (eg Gnome, KDE, Mate, XFCE, LXDE). This will help reproducing bugs on a virtual machine a lot. 205 | -------------------------------------------------------------------------------- /luckyLUKS/mainUI.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the main window of the application 3 | 4 | luckyLUKS Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | """ 16 | 17 | import sys 18 | import os.path 19 | 20 | from PyQt5.QtCore import Qt 21 | from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QDesktopWidget, QDialog,\ 22 | QSystemTrayIcon, QMessageBox, QMenu, QAction, QLabel, QPushButton, QGridLayout, QStyle 23 | from PyQt5.QtGui import QIcon 24 | 25 | from luckyLUKS import utils, PROJECT_URL 26 | from luckyLUKS.unlockUI import UnlockContainerDialog, UserInputError 27 | from luckyLUKS.utilsUI import show_alert 28 | 29 | 30 | class MainWindow(QMainWindow): 31 | 32 | """ A window that shows the current status of the encrypted container 33 | and a button to unlock/close it. Open containers 34 | leave an icon in the systray as a reminder to close them eventually. 35 | """ 36 | 37 | def __init__(self, device_name=None, container_path=None, key_file=None, mount_point=None): 38 | """ Command line arguments checks are done here to be able to display a graphical dialog with error messages . 39 | If no arguments were supplied on the command line a setup dialog will be shown. 40 | All commands will be executed from a separate worker process with administrator privileges 41 | that gets initialized here. 42 | :param device_name: The device mapper name 43 | :type device_name: str/unicode or None 44 | :param container_path: The path of the container file 45 | :type container_path: str/unicode or None 46 | :param key_file: The path of an optional key file 47 | :type key_file: str/unicode or None 48 | :param mount_point: The path of an optional mount point 49 | :type mount_point: str/unicode or None 50 | """ 51 | super().__init__() 52 | 53 | self.luks_device_name = device_name 54 | self.encrypted_container = container_path 55 | self.key_file = key_file 56 | self.mount_point = mount_point 57 | 58 | self.worker = None 59 | self.is_waiting_for_worker = False 60 | self.is_unlocked = False 61 | self.is_initialized = False 62 | self.has_tray = QSystemTrayIcon.isSystemTrayAvailable() 63 | 64 | # L10n: program name - translatable for startmenu titlebar etc 65 | self.setWindowTitle(_('luckyLUKS')) 66 | self.setWindowIcon(QIcon.fromTheme('dialog-password', QApplication.style().standardIcon(QStyle.SP_DriveHDIcon))) 67 | 68 | # check if cryptsetup and sudo are installed 69 | not_installed_msg = _('{program_name} executable not found!\n' 70 | 'Please install, eg for Debian/Ubuntu\n`apt-get install {program_name}`') 71 | if not utils.is_installed('cryptsetup'): 72 | show_alert(self, not_installed_msg.format(program_name='cryptsetup'), critical=True) 73 | if not utils.is_installed('sudo'): 74 | show_alert(self, not_installed_msg.format(program_name='sudo'), critical=True) 75 | # quick sanity checks before asking for passwd 76 | if os.getuid() == 0: 77 | show_alert(self, 78 | _('Graphical programs should not be run as root!\nPlease call as normal user.'), 79 | critical=True 80 | ) 81 | if self.encrypted_container and not os.path.exists(self.encrypted_container): 82 | show_alert( 83 | self, 84 | _('Container file not accessible\nor path does not exist:\n\n{file_path}') 85 | .format(file_path=self.encrypted_container), 86 | critical=True 87 | ) 88 | 89 | # only either encrypted_container or luks_device_name supplied 90 | if bool(self.encrypted_container) != bool(self.luks_device_name): 91 | show_alert(self, _('Invalid arguments:\n' 92 | 'Please call without any arguments\n' 93 | 'or supply both container and name.\n\n' 94 | '{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n\n' 95 | 'CONTAINER = Path of the encrypted container file\n' 96 | 'NAME = A (unique) name to identify the unlocked container\n' 97 | 'Optional: MOUNTPOINT = where to mount the encrypted filesystem\n\n' 98 | 'If automatic mounting is configured on your system,\n' 99 | 'explicitly setting a mountpoint is not required\n\n' 100 | 'For more information, visit\n' 101 | '{project_url}' 102 | ).format(executable=os.path.basename(sys.argv[0]), 103 | project_url=PROJECT_URL), critical=True) 104 | 105 | # spawn worker process with root privileges 106 | try: 107 | self.worker = utils.WorkerMonitor(self) 108 | # start communication thread 109 | self.worker.start() 110 | except utils.SudoException as se: 111 | show_alert(self, str(se), critical=True) 112 | return 113 | 114 | # if no arguments supplied, display dialog to gather this information 115 | if self.encrypted_container is None and self.luks_device_name is None: 116 | 117 | from luckyLUKS.setupUI import SetupDialog 118 | sd = SetupDialog(self) 119 | 120 | if sd.exec_() == QDialog.Accepted: 121 | self.luks_device_name = sd.get_luks_device_name() 122 | self.encrypted_container = sd.get_encrypted_container() 123 | self.mount_point = sd.get_mount_point() 124 | self.key_file = sd.get_keyfile() 125 | 126 | self.is_unlocked = True # all checks in setup dialog -> skip initializing state 127 | else: 128 | # user closed dialog -> quit program 129 | # and check if a keyfile create thread has to be stopped 130 | # the worker process terminates itself when its parent dies 131 | if hasattr(sd, 'create_thread') and sd.create_thread is not None and sd.create_thread.isRunning(): 132 | sd.create_thread.terminate() 133 | QApplication.instance().quit() 134 | return 135 | 136 | # center window on desktop 137 | qr = self.frameGeometry() 138 | cp = QDesktopWidget().availableGeometry().center() 139 | qr.moveCenter(cp) 140 | self.move(qr.topLeft()) 141 | 142 | # widget content 143 | main_grid = QGridLayout() 144 | main_grid.setSpacing(10) 145 | icon = QLabel() 146 | icon.setPixmap(QIcon.fromTheme( 147 | 'dialog-password', 148 | QApplication.style().standardIcon(QStyle.SP_DriveHDIcon) 149 | ).pixmap(32)) 150 | main_grid.addWidget(icon, 0, 0) 151 | main_grid.addWidget(QLabel('' + _('Handle encrypted container') + '\n'), 0, 1, alignment=Qt.AlignCenter) 152 | 153 | main_grid.addWidget(QLabel(_('Name:')), 1, 0) 154 | main_grid.addWidget(QLabel('{dev_name}'.format(dev_name=self.luks_device_name)), 155 | 1, 1, alignment=Qt.AlignCenter) 156 | 157 | main_grid.addWidget(QLabel(_('File:')), 2, 0) 158 | main_grid.addWidget(QLabel(self.encrypted_container), 2, 1, alignment=Qt.AlignCenter) 159 | 160 | if self.key_file is not None: 161 | main_grid.addWidget(QLabel(_('Key:')), 3, 0) 162 | main_grid.addWidget(QLabel(self.key_file), 3, 1, alignment=Qt.AlignCenter) 163 | 164 | if self.mount_point is not None: 165 | main_grid.addWidget(QLabel(_('Mount:')), 4, 0) 166 | main_grid.addWidget(QLabel(self.mount_point), 4, 1, alignment=Qt.AlignCenter) 167 | 168 | main_grid.addWidget(QLabel(_('Status:')), 5, 0) 169 | self.label_status = QLabel('') 170 | main_grid.addWidget(self.label_status, 5, 1, alignment=Qt.AlignCenter) 171 | 172 | self.button_toggle_status = QPushButton('') 173 | self.button_toggle_status.setMinimumHeight(34) 174 | self.button_toggle_status.clicked.connect(self.toggle_container_status) 175 | main_grid.setRowMinimumHeight(6, 10) 176 | main_grid.addWidget(self.button_toggle_status, 7, 1) 177 | 178 | widget = QWidget() 179 | widget.setLayout(main_grid) 180 | widget.setContentsMargins(10, 10, 10, 10) 181 | self.setCentralWidget(widget) 182 | 183 | # tray popup menu 184 | if self.has_tray: 185 | tray_popup = QMenu(self) 186 | tray_popup.addAction(QIcon.fromTheme('dialog-password', 187 | QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)), 188 | self.luks_device_name).setEnabled(False) 189 | tray_popup.addSeparator() 190 | self.tray_toggle_action = QAction(QApplication.style().standardIcon(QStyle.SP_DesktopIcon), _('Hide'), self) 191 | self.tray_toggle_action.triggered.connect(self.toggle_main_window) 192 | tray_popup.addAction(self.tray_toggle_action) 193 | quit_action = QAction(QApplication.style().standardIcon(QStyle.SP_MessageBoxCritical), _('Quit'), self) 194 | quit_action.triggered.connect(self.tray_quit) 195 | tray_popup.addAction(quit_action) 196 | # systray 197 | self.tray = QSystemTrayIcon(self) 198 | self.tray.setIcon(QIcon.fromTheme( 199 | 'dialog-password', 200 | QApplication.style().standardIcon(QStyle.SP_DriveHDIcon)) 201 | ) 202 | self.tray.setContextMenu(tray_popup) 203 | self.tray.activated.connect(self.toggle_main_window) 204 | self.tray.show() 205 | 206 | self.init_status() 207 | 208 | def refresh(self): 209 | """ Update widgets to reflect current container status. Adds systray icon if needed """ 210 | if self.is_unlocked: 211 | self.label_status.setText(_('Container is {unlocked_green_bold}').format( 212 | unlocked_green_bold='' + _('unlocked') + '')) 213 | self.button_toggle_status.setText(_('Close Container')) 214 | if self.has_tray: 215 | self.tray.setToolTip(_('{device_name} is unlocked').format(device_name=self.luks_device_name)) 216 | else: 217 | self.label_status.setText(_('Container is {closed_red_bold}').format( 218 | closed_red_bold='' + _('closed') + '')) 219 | self.button_toggle_status.setText(_('Unlock Container')) 220 | if self.has_tray: 221 | self.tray.setToolTip(_('{device_name} is closed').format(device_name=self.luks_device_name)) 222 | 223 | self.show() 224 | self.setFixedSize(self.sizeHint()) 225 | 226 | def tray_quit(self): 227 | """ Triggered by clicking on `quit` in the systray popup: asks to close an unlocked container """ 228 | if not self.is_unlocked: 229 | QApplication.instance().quit() 230 | elif not self.is_waiting_for_worker: 231 | self.show() 232 | self.confirm_close() 233 | 234 | def toggle_main_window(self, tray_icon_clicked): 235 | """ Triggered by clicking on the systray icon: show/hide main window """ 236 | # don't activate on rightclick/contextmenu 237 | if not tray_icon_clicked or tray_icon_clicked == QSystemTrayIcon.Trigger: 238 | if self.isVisible(): 239 | self.hide() 240 | self.tray_toggle_action.setText(_('Show')) 241 | else: 242 | self.show() 243 | self.tray_toggle_action.setText(_('Hide')) 244 | 245 | def closeEvent(self, event): 246 | """ Triggered by closing the window: If the container is unlocked, 247 | the program won't quit but remain in the systray. """ 248 | if not self.is_waiting_for_worker: 249 | if self.is_unlocked: 250 | if self.has_tray: 251 | self.hide() 252 | self.tray_toggle_action.setText(_('Show')) 253 | else: 254 | self.confirm_close() 255 | event.ignore() 256 | else: 257 | event.accept() 258 | 259 | def confirm_close(self): 260 | """ Inform about opened container and ask for confirmation to close & quit """ 261 | message = _('{device_name} >> {container_path}\n' 262 | 'is currently unlocked,\n' 263 | 'Close Container now and quit?').format(device_name=self.luks_device_name, 264 | container_path=self.encrypted_container) 265 | mb = QMessageBox(QMessageBox.Question, '', message, QMessageBox.Ok | QMessageBox.Cancel, self) 266 | mb.button(QMessageBox.Ok).setText(_('Quit')) 267 | if mb.exec_() == QMessageBox.Ok: 268 | self.do_close_container(shutdown=True) 269 | 270 | def customEvent(self, event): 271 | """ Receives response from worker and calls supplied callback function """ 272 | event.callback(event.response) 273 | 274 | def toggle_container_status(self): 275 | """ Unlock or close container """ 276 | if self.is_unlocked: 277 | self.do_close_container() 278 | else: 279 | try: 280 | UnlockContainerDialog( 281 | self, self.worker, self.luks_device_name, 282 | self.encrypted_container, self.key_file, 283 | self.mount_point 284 | ).communicate() 285 | self.is_unlocked = True 286 | except UserInputError as uie: 287 | show_alert(self, str(uie)) 288 | self.is_unlocked = False 289 | self.refresh() 290 | 291 | def do_close_container(self, shutdown=False): 292 | """ Send close command to worker and supply callbacks 293 | :param shutdown: Quit application after container successfully closed? (default=False) 294 | :type shutdown: bool 295 | """ 296 | self.disable_ui(_('Closing Container ..')) 297 | self.worker.execute(command={'type': 'request', 298 | 'msg': 'close', 299 | 'device_name': self.luks_device_name, 300 | 'container_path': self.encrypted_container 301 | }, 302 | success_callback=lambda msg: self.on_container_closed(msg, error=False, shutdown=shutdown), 303 | error_callback=lambda msg: self.on_container_closed(msg, error=True, shutdown=shutdown)) 304 | 305 | def on_container_closed(self, message, error, shutdown): 306 | """ Callback after worker closed container 307 | :param message: Contains an error description if error=True, 308 | otherwise the current state of the container (unlocked/closed) 309 | :type message: str 310 | :param error: Error during closing of container 311 | :type error: bool 312 | :param shutdown: Quit application after container successfully closed? 313 | :type shutdown: bool 314 | """ 315 | if error: 316 | show_alert(self, message) 317 | else: 318 | self.is_unlocked = False 319 | if not error and shutdown: # automatic shutdown only if container successfully closed 320 | QApplication.instance().quit() 321 | else: 322 | self.enable_ui() 323 | 324 | def init_status(self): 325 | """ Request current status of container from worker if needed """ 326 | if not self.is_unlocked: 327 | self.disable_ui(_('Initializing ..')) 328 | self.worker.execute(command={'type': 'request', 329 | 'msg': 'status', 330 | 'device_name': self.luks_device_name, 331 | 'container_path': self.encrypted_container, 332 | 'key_file': self.key_file, 333 | 'mount_point': self.mount_point 334 | }, 335 | success_callback=self.on_initialized, 336 | error_callback=lambda msg: self.on_initialized(msg, error=True)) 337 | else: # unlocked by setup-dialog -> just refresh UI 338 | self.enable_ui() 339 | self.is_initialized = True # qt event loop can start now 340 | 341 | def on_initialized(self, message, error=False): 342 | """ Callback after worker send current state of container 343 | :param message: Contains an error description if error=True, 344 | otherwise the current state of the container (unlocked/closed) 345 | :type message: str 346 | :param critical: Error during initialization (default=False) 347 | :type critical: bool 348 | """ 349 | if error: 350 | show_alert(self, message, critical=True) 351 | else: 352 | self.is_unlocked = bool(message == 'unlocked') 353 | self.enable_ui() 354 | 355 | def enable_ui(self): 356 | """ Enable buttons and refresh state """ 357 | self.refresh() 358 | self.is_waiting_for_worker = False 359 | self.button_toggle_status.setEnabled(True) 360 | 361 | def disable_ui(self, reason): 362 | """ Disable buttons and display waiting message 363 | :param reason: A waiting message that gets displayed 364 | :type reason: str/unicode 365 | """ 366 | self.is_waiting_for_worker = True 367 | self.button_toggle_status.setText(reason) 368 | self.button_toggle_status.setEnabled(False) 369 | -------------------------------------------------------------------------------- /luckyLUKS/locale/luckyLUKS.pot: -------------------------------------------------------------------------------- 1 | # Translations template for luckyLUKS. 2 | # Copyright (C) 2022 Jasper van Hoorn (muzius@gmail.com) 3 | # This file is distributed under the same license as the luckyLUKS project. 4 | # 5 | #, fuzzy 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: luckyLUKS 2.1.0\n" 9 | "Report-Msgid-Bugs-To: Jasper van Hoorn (muzius@gmail.com)\n" 10 | "POT-Creation-Date: 2022-02-16 23:09+0100\n" 11 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 12 | "Last-Translator: FULL NAME \n" 13 | "MIME-Version: 1.0\n" 14 | "Content-Type: text/plain; charset=utf-8\n" 15 | "Content-Transfer-Encoding: 8bit\n" 16 | "Generated-By: Babel 2.8.0\n" 17 | 18 | #: luckyLUKS/main.py:33 19 | msgid "GUI for creating and unlocking LUKS/TrueCrypt volumes from container files" 20 | msgstr "" 21 | 22 | #: luckyLUKS/main.py:34 23 | msgid "" 24 | "When called without any arguments a setup dialog will be shown before " 25 | "unlocking,\n" 26 | "where you can select containerfile and name, or create a new encrypted " 27 | "container.\n" 28 | "If both arguments are supplied, the unlock dialog will be shown directly." 29 | "\n" 30 | "\n" 31 | "Example:\n" 32 | " {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n" 33 | "\n" 34 | "If automatic mounting (eg udisks/polkit) is configured on your system,\n" 35 | "explicitly setting a mountpoint is usually not needed (but still " 36 | "possible)\n" 37 | "\n" 38 | "Homepage: {project_url}" 39 | msgstr "" 40 | 41 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 42 | #. --help) 43 | #: luckyLUKS/main.py:47 44 | msgid "usage: " 45 | msgstr "" 46 | 47 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 48 | #. --help) 49 | #: luckyLUKS/main.py:49 50 | msgid "optional arguments" 51 | msgstr "" 52 | 53 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 54 | #. --help) 55 | #: luckyLUKS/main.py:51 56 | msgid "show this help message and exit" 57 | msgstr "" 58 | 59 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 60 | #. --help) 61 | #: luckyLUKS/main.py:53 62 | #, python-format 63 | msgid "%(prog)s: error: %(message)s\n" 64 | msgstr "" 65 | 66 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 67 | #. --help) 68 | #: luckyLUKS/main.py:55 69 | #, python-format 70 | msgid "unrecognized arguments: %s" 71 | msgstr "" 72 | 73 | #: luckyLUKS/main.py:59 luckyLUKS/main.py:63 luckyLUKS/main.py:65 74 | msgid "PATH" 75 | msgstr "" 76 | 77 | #: luckyLUKS/main.py:60 78 | msgid "Path to the encrypted container file" 79 | msgstr "" 80 | 81 | #: luckyLUKS/main.py:61 82 | msgid "NAME" 83 | msgstr "" 84 | 85 | #: luckyLUKS/main.py:62 86 | msgid "Choose a device name to identify the unlocked container" 87 | msgstr "" 88 | 89 | #: luckyLUKS/main.py:64 90 | msgid "Where to mount the encrypted filesystem" 91 | msgstr "" 92 | 93 | #: luckyLUKS/main.py:66 94 | msgid "Path to an optional key file" 95 | msgstr "" 96 | 97 | #: luckyLUKS/main.py:68 98 | msgid "show program's version number and exit" 99 | msgstr "" 100 | 101 | #. L10n: program name - translatable for startmenu titlebar etc 102 | #: luckyLUKS/mainUI.py:65 luckyLUKS/setupUI.py:48 luckyLUKS/setupUI.py:507 103 | #: luckyLUKS/unlockUI.py:116 104 | msgid "luckyLUKS" 105 | msgstr "" 106 | 107 | #: luckyLUKS/mainUI.py:69 108 | msgid "" 109 | "{program_name} executable not found!\n" 110 | "Please install, eg for Debian/Ubuntu\n" 111 | "`apt-get install {program_name}`" 112 | msgstr "" 113 | 114 | #: luckyLUKS/mainUI.py:78 115 | msgid "" 116 | "Graphical programs should not be run as root!\n" 117 | "Please call as normal user." 118 | msgstr "" 119 | 120 | #: luckyLUKS/mainUI.py:84 luckyLUKS/worker.py:206 121 | msgid "" 122 | "Container file not accessible\n" 123 | "or path does not exist:\n" 124 | "\n" 125 | "{file_path}" 126 | msgstr "" 127 | 128 | #: luckyLUKS/mainUI.py:91 129 | msgid "" 130 | "Invalid arguments:\n" 131 | "Please call without any arguments\n" 132 | "or supply both container and name.\n" 133 | "\n" 134 | "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" 135 | "\n" 136 | "CONTAINER = Path of the encrypted container file\n" 137 | "NAME = A (unique) name to identify the unlocked container\n" 138 | "Optional: MOUNTPOINT = where to mount the encrypted filesystem\n" 139 | "\n" 140 | "If automatic mounting is configured on your system,\n" 141 | "explicitly setting a mountpoint is not required\n" 142 | "\n" 143 | "For more information, visit\n" 144 | "{project_url}" 145 | msgstr "" 146 | 147 | #: luckyLUKS/mainUI.py:151 148 | msgid "Handle encrypted container" 149 | msgstr "" 150 | 151 | #: luckyLUKS/mainUI.py:153 152 | msgid "Name:" 153 | msgstr "" 154 | 155 | #: luckyLUKS/mainUI.py:157 156 | msgid "File:" 157 | msgstr "" 158 | 159 | #: luckyLUKS/mainUI.py:161 160 | msgid "Key:" 161 | msgstr "" 162 | 163 | #: luckyLUKS/mainUI.py:165 164 | msgid "Mount:" 165 | msgstr "" 166 | 167 | #: luckyLUKS/mainUI.py:168 168 | msgid "Status:" 169 | msgstr "" 170 | 171 | #: luckyLUKS/mainUI.py:190 luckyLUKS/mainUI.py:243 172 | msgid "Hide" 173 | msgstr "" 174 | 175 | #: luckyLUKS/mainUI.py:193 luckyLUKS/mainUI.py:266 luckyLUKS/setupUI.py:589 176 | #: luckyLUKS/unlockUI.py:191 177 | msgid "Quit" 178 | msgstr "" 179 | 180 | #: luckyLUKS/mainUI.py:211 181 | msgid "Container is {unlocked_green_bold}" 182 | msgstr "" 183 | 184 | #: luckyLUKS/mainUI.py:212 185 | msgid "unlocked" 186 | msgstr "" 187 | 188 | #: luckyLUKS/mainUI.py:213 189 | msgid "Close Container" 190 | msgstr "" 191 | 192 | #: luckyLUKS/mainUI.py:215 193 | msgid "{device_name} is unlocked" 194 | msgstr "" 195 | 196 | #: luckyLUKS/mainUI.py:217 197 | msgid "Container is {closed_red_bold}" 198 | msgstr "" 199 | 200 | #: luckyLUKS/mainUI.py:218 201 | msgid "closed" 202 | msgstr "" 203 | 204 | #: luckyLUKS/mainUI.py:219 luckyLUKS/setupUI.py:121 205 | msgid "Unlock Container" 206 | msgstr "" 207 | 208 | #: luckyLUKS/mainUI.py:221 209 | msgid "{device_name} is closed" 210 | msgstr "" 211 | 212 | #: luckyLUKS/mainUI.py:240 luckyLUKS/mainUI.py:252 213 | msgid "Show" 214 | msgstr "" 215 | 216 | #: luckyLUKS/mainUI.py:261 217 | msgid "" 218 | "{device_name} >> {container_path}\n" 219 | "is currently unlocked,\n" 220 | "Close Container now and quit?" 221 | msgstr "" 222 | 223 | #: luckyLUKS/mainUI.py:296 224 | msgid "Closing Container .." 225 | msgstr "" 226 | 227 | #: luckyLUKS/mainUI.py:327 luckyLUKS/unlockUI.py:213 228 | msgid "Initializing .." 229 | msgstr "" 230 | 231 | #: luckyLUKS/setupUI.py:67 luckyLUKS/setupUI.py:758 232 | msgid "Unlock an encrypted container\n" 233 | msgstr "" 234 | 235 | #: luckyLUKS/setupUI.py:68 236 | msgid "Please select container file and name" 237 | msgstr "" 238 | 239 | #: luckyLUKS/setupUI.py:72 luckyLUKS/setupUI.py:130 240 | msgid "container file" 241 | msgstr "" 242 | 243 | #: luckyLUKS/setupUI.py:78 244 | msgid "choose file" 245 | msgstr "" 246 | 247 | #: luckyLUKS/setupUI.py:82 luckyLUKS/setupUI.py:140 248 | msgid "device name" 249 | msgstr "" 250 | 251 | #: luckyLUKS/setupUI.py:88 luckyLUKS/setupUI.py:163 252 | msgid "Advanced" 253 | msgstr "" 254 | 255 | #: luckyLUKS/setupUI.py:91 luckyLUKS/setupUI.py:166 luckyLUKS/setupUI.py:725 256 | #: luckyLUKS/setupUI.py:766 257 | msgid "key file" 258 | msgstr "" 259 | 260 | #: luckyLUKS/setupUI.py:97 luckyLUKS/setupUI.py:172 261 | msgid "choose keyfile" 262 | msgstr "" 263 | 264 | #: luckyLUKS/setupUI.py:102 luckyLUKS/setupUI.py:776 265 | msgid "mount point" 266 | msgstr "" 267 | 268 | #: luckyLUKS/setupUI.py:108 269 | msgid "choose folder" 270 | msgstr "" 271 | 272 | #: luckyLUKS/setupUI.py:115 luckyLUKS/setupUI.py:208 luckyLUKS/utilsUI.py:48 273 | msgid "Help" 274 | msgstr "" 275 | 276 | #: luckyLUKS/setupUI.py:125 luckyLUKS/setupUI.py:709 277 | msgid "Create a new encrypted container\n" 278 | msgstr "" 279 | 280 | #: luckyLUKS/setupUI.py:126 281 | msgid "Please choose container file, name and size" 282 | msgstr "" 283 | 284 | #: luckyLUKS/setupUI.py:136 285 | msgid "set file" 286 | msgstr "" 287 | 288 | #: luckyLUKS/setupUI.py:146 289 | msgid "container size" 290 | msgstr "" 291 | 292 | #: luckyLUKS/setupUI.py:159 293 | msgid "Quickformat" 294 | msgstr "" 295 | 296 | #: luckyLUKS/setupUI.py:177 297 | msgid "Create key file" 298 | msgstr "" 299 | 300 | #: luckyLUKS/setupUI.py:182 301 | msgid "format" 302 | msgstr "" 303 | 304 | #: luckyLUKS/setupUI.py:194 luckyLUKS/setupUI.py:747 305 | msgid "filesystem" 306 | msgstr "" 307 | 308 | #: luckyLUKS/setupUI.py:214 309 | msgid "Create New Container" 310 | msgstr "" 311 | 312 | #: luckyLUKS/setupUI.py:219 luckyLUKS/setupUI.py:595 luckyLUKS/setupUI.py:610 313 | #: luckyLUKS/setupUI.py:617 luckyLUKS/unlockUI.py:227 314 | msgid "Unlock" 315 | msgstr "" 316 | 317 | #: luckyLUKS/setupUI.py:242 318 | msgid "Creating new container\n" 319 | msgstr "" 320 | 321 | #: luckyLUKS/setupUI.py:243 322 | msgid "patience .. this might take a while" 323 | msgstr "" 324 | 325 | #: luckyLUKS/setupUI.py:247 luckyLUKS/setupUI.py:289 luckyLUKS/setupUI.py:316 326 | msgid "Step" 327 | msgstr "" 328 | 329 | #: luckyLUKS/setupUI.py:248 330 | msgid "Initializing Container File" 331 | msgstr "" 332 | 333 | #: luckyLUKS/setupUI.py:290 334 | msgid "Initializing Encryption" 335 | msgstr "" 336 | 337 | #: luckyLUKS/setupUI.py:304 338 | msgid "Initialize container aborted" 339 | msgstr "" 340 | 341 | #: luckyLUKS/setupUI.py:317 342 | msgid "Initializing Filesystem" 343 | msgstr "" 344 | 345 | #: luckyLUKS/setupUI.py:333 346 | msgid "" 347 | "{device_name}\n" 348 | "successfully created!\n" 349 | "Click on unlock to use the new container" 350 | msgstr "" 351 | 352 | #: luckyLUKS/setupUI.py:334 luckyLUKS/setupUI.py:471 luckyLUKS/setupUI.py:551 353 | #: luckyLUKS/utils.py:70 luckyLUKS/utils.py:141 354 | msgid "Success" 355 | msgstr "" 356 | 357 | #: luckyLUKS/setupUI.py:377 358 | msgid "Done" 359 | msgstr "" 360 | 361 | #: luckyLUKS/setupUI.py:427 362 | msgid "new_keyfile.bin" 363 | msgstr "" 364 | 365 | #: luckyLUKS/setupUI.py:436 366 | msgid "Creating key file" 367 | msgstr "" 368 | 369 | #: luckyLUKS/setupUI.py:443 370 | msgid "" 371 | "This might take a while. Since computers are deterministic machines\n" 372 | "it is quite a challenge to generate real random data for the key.\n" 373 | "\n" 374 | "You can speed up the process by typing, moving the mouse\n" 375 | "and generally use the computer while the key gets generated." 376 | msgstr "" 377 | 378 | #: luckyLUKS/setupUI.py:469 379 | msgid "" 380 | "{key_file}\n" 381 | "successfully created!\n" 382 | "You can use this key file now,\n" 383 | "to create a new container." 384 | msgstr "" 385 | 386 | #: luckyLUKS/setupUI.py:477 387 | msgid "" 388 | "Successfully unlocked!\n" 389 | "\n" 390 | "Do you want to create\n" 391 | "a startup menu entry for {device_name}?\n" 392 | "\n" 393 | "-> Your password will NOT be saved!\n" 394 | " This just creates a shortcut,\n" 395 | " to the unlock container dialog.\n" 396 | msgstr "" 397 | 398 | #: luckyLUKS/setupUI.py:486 399 | msgid "Create shortcut" 400 | msgstr "" 401 | 402 | #: luckyLUKS/setupUI.py:487 403 | msgid "No, thanks" 404 | msgstr "" 405 | 406 | #: luckyLUKS/setupUI.py:520 407 | msgid "Unlock {device_name}" 408 | msgstr "" 409 | 410 | #: luckyLUKS/setupUI.py:523 411 | msgid "Encrypted Container Tool" 412 | msgstr "" 413 | 414 | #: luckyLUKS/setupUI.py:524 415 | msgid "Encrypted Container" 416 | msgstr "" 417 | 418 | #: luckyLUKS/setupUI.py:551 419 | msgid "" 420 | "` {name} `\n" 421 | "added to start menu" 422 | msgstr "" 423 | 424 | #: luckyLUKS/setupUI.py:559 luckyLUKS/setupUI.py:566 425 | msgid "" 426 | "Adding to start menu not possible,\n" 427 | "please place your shortcut manually.\n" 428 | "\n" 429 | "Desktop file saved to\n" 430 | "{location}" 431 | msgstr "" 432 | 433 | #: luckyLUKS/setupUI.py:587 434 | msgid "" 435 | "Currently processing your request!\n" 436 | "Do you really want to quit?" 437 | msgstr "" 438 | 439 | #: luckyLUKS/setupUI.py:598 440 | msgid "" 441 | "No tools to format the filesystem found\n" 442 | "Please install, eg for Debian/Ubuntu\n" 443 | "`apt-get install e2fslibs ntfs-3g`" 444 | msgstr "" 445 | 446 | #: luckyLUKS/setupUI.py:601 luckyLUKS/setupUI.py:655 447 | msgid "Create" 448 | msgstr "" 449 | 450 | #: luckyLUKS/setupUI.py:606 451 | msgid "Please choose a container file" 452 | msgstr "" 453 | 454 | #: luckyLUKS/setupUI.py:615 455 | msgid "Please choose a folder as mountpoint" 456 | msgstr "" 457 | 458 | #: luckyLUKS/setupUI.py:621 459 | msgid "Please choose a key file" 460 | msgstr "" 461 | 462 | #: luckyLUKS/setupUI.py:634 463 | msgid "new_container.bin" 464 | msgstr "" 465 | 466 | #: luckyLUKS/setupUI.py:650 467 | msgid "Please create a new file" 468 | msgstr "" 469 | 470 | #: luckyLUKS/setupUI.py:658 471 | msgid "" 472 | "File already exists:\n" 473 | "{filename}\n" 474 | "\n" 475 | "Please create a new file!" 476 | msgstr "" 477 | 478 | #: luckyLUKS/setupUI.py:710 479 | msgid "" 480 | "Enter the path of the new container file in the textbox or click " 481 | "the button next to the box for a graphical create file dialog.\n" 482 | "\n" 483 | "The device name will be used to identify the unlocked container. " 484 | "It can be any name up to 16 unicode characters, as long as it is unique." 485 | "\n" 486 | "\n" 487 | "The size of the container can be provided in GB or MB. The " 488 | "container will get initialized with random data, this can take quite a " 489 | "while - 1 hour for a 10GB container on an external drive is nothing " 490 | "unusual.\n" 491 | "\n" 492 | "To speed up container creation Quickformat can be enabled to use " 493 | "`fallocate` instead of initializing the container with random data - this" 494 | " means previous data will not be overwritten and some conclusions about " 495 | "encrypted data inside closed containers can be drawn.\n" 496 | msgstr "" 497 | 498 | #: luckyLUKS/setupUI.py:726 luckyLUKS/setupUI.py:767 499 | msgid "" 500 | "A key file can be used to allow access to an encrypted container instead " 501 | "of a password. Using a key file resembles unlocking a door with a key in " 502 | "the real world - anyone with access to the key file can open your " 503 | "encrypted container. Make sure to store it at a protected location. Its " 504 | "okay to store it on your computer if you are using an already encrypted " 505 | "harddrive or a digital keystore. Having the key file on a small USB " 507 | "drive attached to your real chain of keys would be an option as well." 508 | "\n" 509 | "Since you dont have to enter a password, using a key file can be a " 510 | "convenient way to access your encrypted container. Just make sure you " 511 | "dont lose the key (file) ;)" 512 | msgstr "" 513 | 514 | #: luckyLUKS/setupUI.py:735 515 | msgid "" 516 | "\n" 517 | "\n" 518 | "Although basically any file could be used as a key file, a file with " 519 | "predictable content leads to similar problems as using weak passwords. " 520 | "Audio files or pictures are a good choice. If unsure use the `create key " 521 | "file` button to generate a small key file filled with random data." 522 | msgstr "" 523 | 524 | #: luckyLUKS/setupUI.py:740 525 | msgid "encryption format" 526 | msgstr "" 527 | 528 | #: luckyLUKS/setupUI.py:741 529 | msgid "" 530 | "The standard disk encryption format on Linux is called LUKS. With LibreCrypt you can use " 532 | "LUKS containers on Windows as well. The TrueCrypt format is quite popular" 533 | " on Windows/Mac, and can be created on Linux if `tcplay` is installed. " 534 | "Please note, that \"hidden\" TrueCrypt partitions are not supported by " 535 | "luckyLUKS!" 536 | msgstr "" 537 | 538 | #: luckyLUKS/setupUI.py:748 539 | msgid "" 540 | "Choose the ntfs filesystem to be able to access your data from Linux, " 541 | "Windows and Mac OSX. Since access permissions cannot be mapped from ntfs " 542 | "to Linux, access to ntfs devices is usually not restricted -> take care " 543 | "when using unlocked ntfs devices in a multiuser environment!" 544 | msgstr "" 545 | 546 | #: luckyLUKS/setupUI.py:759 547 | msgid "" 548 | "Select the encrypted container file by clicking the button next to" 549 | " the textbox. Both LUKS and Truecrypt containers are supported!\n" 550 | "\n" 551 | "The device name will be used to identify the unlocked container. " 552 | "It can be any name up to 16 unicode characters, as long as it is unique " 553 | "-> you cannot give two unlocked containers the same name" 554 | msgstr "" 555 | 556 | #: luckyLUKS/setupUI.py:777 557 | msgid "" 558 | "The mount point is the folder on your computer, where you can access the " 559 | "files inside the container after unlocking. If automatic mounting is " 560 | "configured on your system (eg with udisks), explicitly setting a " 561 | "mountpoint is not neccessary (but still possible)." 562 | msgstr "" 563 | 564 | #: luckyLUKS/unlockUI.py:122 565 | msgid "" 566 | "Always allow luckyLUKS to be run\n" 567 | "with administrative privileges" 568 | msgstr "" 569 | 570 | #: luckyLUKS/unlockUI.py:153 571 | msgid "" 572 | "Please choose a passphrase\n" 573 | "to encrypt the new container:\n" 574 | msgstr "" 575 | 576 | #: luckyLUKS/unlockUI.py:154 577 | msgid "Enter new Passphrase" 578 | msgstr "" 579 | 580 | #: luckyLUKS/unlockUI.py:156 581 | msgid "Display passphrase" 582 | msgstr "" 583 | 584 | #: luckyLUKS/unlockUI.py:189 585 | msgid "" 586 | "Currently creating new container!\n" 587 | "Do you really want to quit?" 588 | msgstr "" 589 | 590 | #: luckyLUKS/unlockUI.py:220 591 | msgid "" 592 | "Using keyfile\n" 593 | "{keyfile}\n" 594 | "to open container.\n" 595 | "\n" 596 | "Please wait .." 597 | msgstr "" 598 | 599 | #: luckyLUKS/unlockUI.py:254 600 | msgid "Checking passphrase .." 601 | msgstr "" 602 | 603 | #: luckyLUKS/unlockUI.py:280 604 | msgid "" 605 | "Please enter\n" 606 | "container passphrase:" 607 | msgstr "" 608 | 609 | #: luckyLUKS/unlockUI.py:282 610 | msgid "" 611 | "Wrong passphrase, please retry!\n" 612 | "Enter container passphrase:" 613 | msgstr "" 614 | 615 | #: luckyLUKS/utils.py:64 616 | msgid "" 617 | "Permanent `sudo` authorization for\n" 618 | "{program}\n" 619 | "has been successfully added for user `{username}` to \n" 620 | "/etc/sudoers.d/lucky-luks\n" 621 | msgstr "" 622 | 623 | #: luckyLUKS/utils.py:79 624 | msgid "" 625 | "luckyLUKS needs administrative privileges.\n" 626 | "Please enter your password:" 627 | msgstr "" 628 | 629 | #: luckyLUKS/utils.py:98 luckyLUKS/utils.py:126 630 | msgid "Sorry, incorrect password.\n" 631 | msgstr "" 632 | 633 | #: luckyLUKS/utils.py:114 634 | msgid "" 635 | "You are not allowed to execute this script with `sudo`.\n" 636 | "If you want to modify your `sudo` configuration,\n" 637 | "please enter the root/administrator password.\n" 638 | msgstr "" 639 | 640 | #: luckyLUKS/utils.py:137 641 | msgid "" 642 | "`sudo` configuration successfully modified, now\n" 643 | "you can use luckyLUKS with your user password.\n" 644 | "\n" 645 | "If you want to grant permanent administrative rights\n" 646 | "just tick the checkbox in the following dialog.\n" 647 | msgstr "" 648 | 649 | #: luckyLUKS/utils.py:162 650 | msgid "" 651 | "Communication with sudo process failed\n" 652 | "{error}" 653 | msgstr "" 654 | 655 | #: luckyLUKS/utils.py:224 luckyLUKS/utils.py:232 luckyLUKS/utils.py:259 656 | #: luckyLUKS/worker.py:130 657 | msgid "" 658 | "Error in communication:\n" 659 | "{error}" 660 | msgstr "" 661 | 662 | #: luckyLUKS/utils.py:315 663 | msgid "" 664 | "Error while creating key file:\n" 665 | "{error}" 666 | msgstr "" 667 | 668 | #: luckyLUKS/utilsUI.py:69 669 | msgid "Advanced Topics:" 670 | msgstr "" 671 | 672 | #: luckyLUKS/utilsUI.py:93 673 | msgid "" 674 | "luckyLUKS version {version}\n" 675 | "For more information, visit\n" 676 | "{project_url}" 677 | msgstr "" 678 | 679 | #: luckyLUKS/utilsUI.py:135 680 | msgid "Error" 681 | msgstr "" 682 | 683 | #: luckyLUKS/worker.py:72 684 | msgid "Please call with sudo." 685 | msgstr "" 686 | 687 | #: luckyLUKS/worker.py:75 688 | msgid "" 689 | "Missing information of the calling user in sudo environment.\n" 690 | "Please make sure sudo is configured correctly." 691 | msgstr "" 692 | 693 | #: luckyLUKS/worker.py:121 694 | msgid "Helper process received unknown command" 695 | msgstr "" 696 | 697 | #: luckyLUKS/worker.py:201 luckyLUKS/worker.py:454 698 | msgid "Device Name is empty" 699 | msgstr "" 700 | 701 | #: luckyLUKS/worker.py:215 702 | msgid "" 703 | "Could not use container:\n" 704 | "{file_path}\n" 705 | "{device_name} is already unlocked\n" 706 | "using a different container\n" 707 | "Please change the name to unlock this container" 708 | msgstr "" 709 | 710 | #: luckyLUKS/worker.py:231 711 | msgid "" 712 | "Device Name too long:\n" 713 | "Only up to 16 characters possible, even less for unicode\n" 714 | "(roughly 8 non-ascii characters possible)" 715 | msgstr "" 716 | 717 | #: luckyLUKS/worker.py:238 luckyLUKS/worker.py:458 718 | msgid "" 719 | "Illegal Device Name!\n" 720 | "Names starting with `-` or using `/` are not possible" 721 | msgstr "" 722 | 723 | #: luckyLUKS/worker.py:258 724 | msgid "" 725 | "Cannot use the container\n" 726 | "{file_path}\n" 727 | "The container is already in use ({existing_device})." 728 | msgstr "" 729 | 730 | #: luckyLUKS/worker.py:270 luckyLUKS/worker.py:497 731 | msgid "" 732 | "Key file not accessible\n" 733 | "or path does not exist:\n" 734 | "\n" 735 | "{file_path}" 736 | msgstr "" 737 | 738 | #: luckyLUKS/worker.py:279 739 | msgid "" 740 | "Mount point not accessible\n" 741 | "or path does not exist:\n" 742 | "\n" 743 | "{mount_dir}" 744 | msgstr "" 745 | 746 | #: luckyLUKS/worker.py:284 747 | msgid "" 748 | "Already mounted at mount point:\n" 749 | "\n" 750 | "{mount_dir}" 751 | msgstr "" 752 | 753 | #: luckyLUKS/worker.py:289 754 | msgid "" 755 | "Designated mount directory\n" 756 | "{mount_dir}\n" 757 | "is not empty" 758 | msgstr "" 759 | 760 | #: luckyLUKS/worker.py:368 761 | msgid "" 762 | "Open container failed.\n" 763 | "Please check key file" 764 | msgstr "" 765 | 766 | #: luckyLUKS/worker.py:404 767 | msgid "Unable to close container, device is busy" 768 | msgstr "" 769 | 770 | #: luckyLUKS/worker.py:450 771 | msgid "Container Filename not supplied" 772 | msgstr "" 773 | 774 | #: luckyLUKS/worker.py:462 775 | msgid "" 776 | "Device Name too long:\n" 777 | "Only up to 16 characters possible, even less for unicode \n" 778 | "(roughly 8 non-ascii characters possible)" 779 | msgstr "" 780 | 781 | #: luckyLUKS/worker.py:467 782 | msgid "" 783 | "Device Name already in use:\n" 784 | "\n" 785 | "{device_name}" 786 | msgstr "" 787 | 788 | #: luckyLUKS/worker.py:471 789 | msgid "" 790 | "Container size too small\n" 791 | "to create encrypted filesystem\n" 792 | "Please choose at least 5MB" 793 | msgstr "" 794 | 795 | #: luckyLUKS/worker.py:485 796 | msgid "" 797 | "Not enough free disc space for container:\n" 798 | "\n" 799 | "{space_needed} MB needed\n" 800 | "{space_available} MB available" 801 | msgstr "" 802 | 803 | #: luckyLUKS/worker.py:504 804 | msgid "Unknown filesystem type: {filesystem_type}" 805 | msgstr "" 806 | 807 | #: luckyLUKS/worker.py:507 808 | msgid "" 809 | "If you want to use TrueCrypt containers\n" 810 | "make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n" 811 | "and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)" 812 | msgstr "" 813 | 814 | #: luckyLUKS/worker.py:598 815 | msgid "Unknown encryption format: {enc_fmt}" 816 | msgstr "" 817 | 818 | #: luckyLUKS/worker.py:653 819 | msgid "Cannot change sudo rights, invalid username" 820 | msgstr "" 821 | 822 | #: luckyLUKS/worker.py:659 823 | msgid "" 824 | "I`m afraid I can`t do that.\n" 825 | "\n" 826 | "To be able to permit permanent changes to sudo rights,\n" 827 | "please make sure the program is owned by root\n" 828 | "and not writeable by others.\n" 829 | "Execute the following commands in your shell:\n" 830 | "\n" 831 | "chmod 755 {program}\n" 832 | "sudo chown root:root {program}\n" 833 | "\n" 834 | msgstr "" 835 | 836 | -------------------------------------------------------------------------------- /luckyLUKS/locale/de/LC_MESSAGES/luckyLUKS.po: -------------------------------------------------------------------------------- 1 | # German translations for luckyLUKS. 2 | # Copyright (C) 2014 Jasper van Hoorn (muzius@gmail.com) 3 | # This file is distributed under the same license as the luckyLUKS project. 4 | # 5 | msgid "" 6 | msgstr "" 7 | "Project-Id-Version: luckyLUKS 0.9.1\n" 8 | "Report-Msgid-Bugs-To: Jasper van Hoorn (muzius@gmail.com)\n" 9 | "POT-Creation-Date: 2022-02-16 23:09+0100\n" 10 | "PO-Revision-Date: 2022-02-16 23:42+0100\n" 11 | "Last-Translator: Jasper van Hoorn \n" 12 | "Language: de\n" 13 | "Language-Team: de \n" 14 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=utf-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Generated-By: Babel 2.8.0\n" 19 | "X-Generator: Poedit 2.3\n" 20 | 21 | #: luckyLUKS/main.py:33 22 | msgid "GUI for creating and unlocking LUKS/TrueCrypt volumes from container files" 23 | msgstr "" 24 | "GUI zum Erstellen und Öffnen von verschlüsselten Containern (LUKS/TrueCrypt)" 25 | 26 | #: luckyLUKS/main.py:34 27 | msgid "" 28 | "When called without any arguments a setup dialog will be shown before " 29 | "unlocking,\n" 30 | "where you can select containerfile and name, or create a new encrypted " 31 | "container.\n" 32 | "If both arguments are supplied, the unlock dialog will be shown directly.\n" 33 | "\n" 34 | "Example:\n" 35 | " {executable} -c /usbstick/encrypted.bin -n mydata -m /home/user/enc\n" 36 | "\n" 37 | "If automatic mounting (eg udisks/polkit) is configured on your system,\n" 38 | "explicitly setting a mountpoint is usually not needed (but still possible)\n" 39 | "\n" 40 | "Homepage: {project_url}" 41 | msgstr "" 42 | "Wird das Programm ohne Parameter aufgerufen, erscheint zunächst ein " 43 | "Dialogfenster,\n" 44 | "um die Containerdatei und einen Namen auszuwahlen, oder einen neuen " 45 | "verschlüsselten\n" 46 | "Container zu erstellen.\n" 47 | "Werden beide Parameter übergeben, wird der Dialog zum Öffnen des Containers " 48 | "direkt angezeigt.\n" 49 | "\n" 50 | "Beispiel:\n" 51 | " {executable} -c /usbstick/geheim.bin -n MeineDateien -m /home/user/Dokumente\n" 52 | "\n" 53 | "Ist das Betriebssystem so konfiguriert, dass neue Datenträger automatisch " 54 | "eingebunden werden\n" 55 | "braucht ein Pfad zum Einhängen des Containers nicht unbedingt angegeben " 56 | "werden.\n" 57 | "\n" 58 | "Homepage: {project_url}" 59 | 60 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 61 | #. --help) 62 | #: luckyLUKS/main.py:47 63 | msgid "usage: " 64 | msgstr "Aufruf: " 65 | 66 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 67 | #. --help) 68 | #: luckyLUKS/main.py:49 69 | msgid "optional arguments" 70 | msgstr "Optionale Parameter" 71 | 72 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 73 | #. --help) 74 | #: luckyLUKS/main.py:51 75 | msgid "show this help message and exit" 76 | msgstr "Diese Hilfe anzeigen und beenden" 77 | 78 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 79 | #. --help) 80 | #: luckyLUKS/main.py:53 81 | #, python-format 82 | msgid "%(prog)s: error: %(message)s\n" 83 | msgstr "%(prog)s Fehler: %(message)s\n" 84 | 85 | #. L10n: used by argsparse to generate help output on the console (luckyLUKS 86 | #. --help) 87 | #: luckyLUKS/main.py:55 88 | #, python-format 89 | msgid "unrecognized arguments: %s" 90 | msgstr "Unbekannter Parameter: %s" 91 | 92 | #: luckyLUKS/main.py:59 luckyLUKS/main.py:63 luckyLUKS/main.py:65 93 | msgid "PATH" 94 | msgstr "PFAD" 95 | 96 | #: luckyLUKS/main.py:60 97 | msgid "Path to the encrypted container file" 98 | msgstr "Pfad zur verschlüsselten Container Datei" 99 | 100 | #: luckyLUKS/main.py:61 101 | msgid "NAME" 102 | msgstr "NAME" 103 | 104 | #: luckyLUKS/main.py:62 105 | msgid "Choose a device name to identify the unlocked container" 106 | msgstr "Frei wählbarer Name zur Identifikation des geöffneten Containers" 107 | 108 | #: luckyLUKS/main.py:64 109 | msgid "Where to mount the encrypted filesystem" 110 | msgstr "Pfad zum Einhängen des geöffneten Containers" 111 | 112 | #: luckyLUKS/main.py:66 113 | msgid "Path to an optional key file" 114 | msgstr "Pfad zu einer optionalen Schlüsseldatei" 115 | 116 | #: luckyLUKS/main.py:68 117 | msgid "show program's version number and exit" 118 | msgstr "Versionsinformation anzeigen und beenden" 119 | 120 | #. L10n: program name - translatable for startmenu titlebar etc 121 | #: luckyLUKS/mainUI.py:65 luckyLUKS/setupUI.py:48 luckyLUKS/setupUI.py:507 122 | #: luckyLUKS/unlockUI.py:116 123 | msgid "luckyLUKS" 124 | msgstr "luckyLUKS" 125 | 126 | #: luckyLUKS/mainUI.py:69 127 | msgid "" 128 | "{program_name} executable not found!\n" 129 | "Please install, eg for Debian/Ubuntu\n" 130 | "`apt-get install {program_name}`" 131 | msgstr "" 132 | "{program_name} konnte nicht gefunden werden!\n" 133 | "Bitte installieren, z.B. unter Debian/Ubuntu\n" 134 | "`apt-get install {program_name}`" 135 | 136 | #: luckyLUKS/mainUI.py:78 137 | msgid "" 138 | "Graphical programs should not be run as root!\n" 139 | "Please call as normal user." 140 | msgstr "" 141 | "Grafische Programme sollten nicht\n" 142 | "als Administrator gestarten werden!\n" 143 | "Bitte als normaler Nutzer ausführen." 144 | 145 | #: luckyLUKS/mainUI.py:84 luckyLUKS/worker.py:206 146 | msgid "" 147 | "Container file not accessible\n" 148 | "or path does not exist:\n" 149 | "\n" 150 | "{file_path}" 151 | msgstr "" 152 | "Kein Zugriff auf die gewünschte Container Datei:\n" 153 | "\n" 154 | "{file_path}" 155 | 156 | #: luckyLUKS/mainUI.py:91 157 | msgid "" 158 | "Invalid arguments:\n" 159 | "Please call without any arguments\n" 160 | "or supply both container and name.\n" 161 | "\n" 162 | "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" 163 | "\n" 164 | "CONTAINER = Path of the encrypted container file\n" 165 | "NAME = A (unique) name to identify the unlocked container\n" 166 | "Optional: MOUNTPOINT = where to mount the encrypted filesystem\n" 167 | "\n" 168 | "If automatic mounting is configured on your system,\n" 169 | "explicitly setting a mountpoint is not required\n" 170 | "\n" 171 | "For more information, visit\n" 172 | "{project_url}" 173 | msgstr "" 174 | "Parameter unvollständig:\n" 175 | "Bitte mit Container Datei und Name,\n" 176 | "oder ganz ohne Parameter aufrufen.\n" 177 | "\n" 178 | "{executable} -c CONTAINER -n NAME [-m MOUNTPOINT]\n" 179 | "\n" 180 | "CONTAINER = Pfad zur verschlüsselten Container Datei\n" 181 | "NAME = Frei wählbarer Name zur Identifikation des Containers\n" 182 | "Optional: MOUNTPOINT = Pfad zum Einhängen des Containers\n" 183 | "\n" 184 | "Ist das Betriebssystem so konfiguriert, dass neue Datenträger\n" 185 | "automatisch eingebunden werden (z.B. mit udisks/polkit),\n" 186 | "braucht ein Pfad zum Einhängen des Containers\n" 187 | "nicht unbedingt angegeben werden.\n" 188 | "\n" 189 | "Weitere Informationen auf der Homepage\n" 190 | "{project_url}" 191 | 192 | #: luckyLUKS/mainUI.py:151 193 | msgid "Handle encrypted container" 194 | msgstr "Verschlüsselten Container verwalten" 195 | 196 | #: luckyLUKS/mainUI.py:153 197 | msgid "Name:" 198 | msgstr "Name:" 199 | 200 | #: luckyLUKS/mainUI.py:157 201 | msgid "File:" 202 | msgstr "Datei:" 203 | 204 | #: luckyLUKS/mainUI.py:161 205 | msgid "Key:" 206 | msgstr "Schlüssel:" 207 | 208 | #: luckyLUKS/mainUI.py:165 209 | msgid "Mount:" 210 | msgstr "Pfad:" 211 | 212 | #: luckyLUKS/mainUI.py:168 213 | msgid "Status:" 214 | msgstr "Status:" 215 | 216 | #: luckyLUKS/mainUI.py:190 luckyLUKS/mainUI.py:243 217 | msgid "Hide" 218 | msgstr "Minimieren" 219 | 220 | #: luckyLUKS/mainUI.py:193 luckyLUKS/mainUI.py:266 luckyLUKS/setupUI.py:589 221 | #: luckyLUKS/unlockUI.py:191 222 | msgid "Quit" 223 | msgstr "Beenden" 224 | 225 | #: luckyLUKS/mainUI.py:211 226 | msgid "Container is {unlocked_green_bold}" 227 | msgstr "Container ist {unlocked_green_bold}" 228 | 229 | #: luckyLUKS/mainUI.py:212 230 | msgid "unlocked" 231 | msgstr "geöffnet" 232 | 233 | #: luckyLUKS/mainUI.py:213 234 | msgid "Close Container" 235 | msgstr "Container Schliessen" 236 | 237 | #: luckyLUKS/mainUI.py:215 238 | msgid "{device_name} is unlocked" 239 | msgstr "{device_name} ist geöffnet" 240 | 241 | #: luckyLUKS/mainUI.py:217 242 | msgid "Container is {closed_red_bold}" 243 | msgstr "Container ist {closed_red_bold}" 244 | 245 | #: luckyLUKS/mainUI.py:218 246 | msgid "closed" 247 | msgstr "verschlossen" 248 | 249 | #: luckyLUKS/mainUI.py:219 luckyLUKS/setupUI.py:121 250 | msgid "Unlock Container" 251 | msgstr "Container Öffnen" 252 | 253 | #: luckyLUKS/mainUI.py:221 254 | msgid "{device_name} is closed" 255 | msgstr "{device_name} ist verschlossen" 256 | 257 | #: luckyLUKS/mainUI.py:240 luckyLUKS/mainUI.py:252 258 | msgid "Show" 259 | msgstr "Anzeigen" 260 | 261 | #: luckyLUKS/mainUI.py:261 262 | msgid "" 263 | "{device_name} >> {container_path}\n" 264 | "is currently unlocked,\n" 265 | "Close Container now and quit?" 266 | msgstr "" 267 | "{device_name} >> {container_path}\n" 268 | "ist zur Zeit geöffnet,\n" 269 | "Container jetzt schliessen\n" 270 | "und das Programm beenden?" 271 | 272 | #: luckyLUKS/mainUI.py:296 273 | msgid "Closing Container .." 274 | msgstr "Schliesse Container .." 275 | 276 | #: luckyLUKS/mainUI.py:327 luckyLUKS/unlockUI.py:213 277 | msgid "Initializing .." 278 | msgstr "Initialisiere .." 279 | 280 | #: luckyLUKS/setupUI.py:67 luckyLUKS/setupUI.py:758 281 | msgid "Unlock an encrypted container\n" 282 | msgstr "Verschlüsselten Container öffnen\n" 283 | 284 | #: luckyLUKS/setupUI.py:68 285 | msgid "Please select container file and name" 286 | msgstr "Bitte Container Datei und Namen wählen" 287 | 288 | #: luckyLUKS/setupUI.py:72 luckyLUKS/setupUI.py:130 289 | msgid "container file" 290 | msgstr "Container Datei" 291 | 292 | #: luckyLUKS/setupUI.py:78 293 | msgid "choose file" 294 | msgstr "Datei auswählen" 295 | 296 | #: luckyLUKS/setupUI.py:82 luckyLUKS/setupUI.py:140 297 | msgid "device name" 298 | msgstr "Gerätename" 299 | 300 | #: luckyLUKS/setupUI.py:88 luckyLUKS/setupUI.py:163 301 | msgid "Advanced" 302 | msgstr "Erweitert" 303 | 304 | #: luckyLUKS/setupUI.py:91 luckyLUKS/setupUI.py:166 luckyLUKS/setupUI.py:725 305 | #: luckyLUKS/setupUI.py:766 306 | msgid "key file" 307 | msgstr "Schlüssel Datei" 308 | 309 | #: luckyLUKS/setupUI.py:97 luckyLUKS/setupUI.py:172 310 | msgid "choose keyfile" 311 | msgstr "Schlüsseldatei auswählen" 312 | 313 | #: luckyLUKS/setupUI.py:102 luckyLUKS/setupUI.py:776 314 | msgid "mount point" 315 | msgstr "Einhängepunkt" 316 | 317 | #: luckyLUKS/setupUI.py:108 318 | msgid "choose folder" 319 | msgstr "Verzeichnis auswählen" 320 | 321 | #: luckyLUKS/setupUI.py:115 luckyLUKS/setupUI.py:208 luckyLUKS/utilsUI.py:48 322 | msgid "Help" 323 | msgstr "Hilfe" 324 | 325 | #: luckyLUKS/setupUI.py:125 luckyLUKS/setupUI.py:709 326 | msgid "Create a new encrypted container\n" 327 | msgstr "Neuen verschlüsselten Container erstellen\n" 328 | 329 | #: luckyLUKS/setupUI.py:126 330 | msgid "Please choose container file, name and size" 331 | msgstr "Bitte Container Datei, Namen und Grösse wählen" 332 | 333 | #: luckyLUKS/setupUI.py:136 334 | msgid "set file" 335 | msgstr "Datei auswählen" 336 | 337 | #: luckyLUKS/setupUI.py:146 338 | msgid "container size" 339 | msgstr "Container Grösse" 340 | 341 | #: luckyLUKS/setupUI.py:159 342 | msgid "Quickformat" 343 | msgstr "Quickformat" 344 | 345 | #: luckyLUKS/setupUI.py:177 346 | msgid "Create key file" 347 | msgstr "Schlüsseldatei erzeugen" 348 | 349 | #: luckyLUKS/setupUI.py:182 350 | msgid "format" 351 | msgstr "Format" 352 | 353 | #: luckyLUKS/setupUI.py:194 luckyLUKS/setupUI.py:747 354 | msgid "filesystem" 355 | msgstr "Dateisystem" 356 | 357 | #: luckyLUKS/setupUI.py:214 358 | msgid "Create New Container" 359 | msgstr "Neuen Container erstellen" 360 | 361 | #: luckyLUKS/setupUI.py:219 luckyLUKS/setupUI.py:595 luckyLUKS/setupUI.py:610 362 | #: luckyLUKS/setupUI.py:617 luckyLUKS/unlockUI.py:227 363 | msgid "Unlock" 364 | msgstr "Öffnen" 365 | 366 | #: luckyLUKS/setupUI.py:242 367 | msgid "Creating new container\n" 368 | msgstr "Erstelle neuen Container\n" 369 | 370 | #: luckyLUKS/setupUI.py:243 371 | msgid "patience .. this might take a while" 372 | msgstr "bitte Geduld .. das kann eine Weile dauern" 373 | 374 | #: luckyLUKS/setupUI.py:247 luckyLUKS/setupUI.py:289 luckyLUKS/setupUI.py:316 375 | msgid "Step" 376 | msgstr "Schritt" 377 | 378 | #: luckyLUKS/setupUI.py:248 379 | msgid "Initializing Container File" 380 | msgstr "Erstelle Container Datei" 381 | 382 | #: luckyLUKS/setupUI.py:290 383 | msgid "Initializing Encryption" 384 | msgstr "Initialisiere Verschlüsselung" 385 | 386 | #: luckyLUKS/setupUI.py:304 387 | msgid "Initialize container aborted" 388 | msgstr "Abbruch durch den Benutzer" 389 | 390 | #: luckyLUKS/setupUI.py:317 391 | msgid "Initializing Filesystem" 392 | msgstr "Erstelle Dateisystem" 393 | 394 | #: luckyLUKS/setupUI.py:333 395 | msgid "" 396 | "{device_name}\n" 397 | "successfully created!\n" 398 | "Click on unlock to use the new container" 399 | msgstr "" 400 | "{device_name}\n" 401 | "erfolgreich erstellt!\n" 402 | "Auf `Öffnen` klicken, um den neuen Container zu benutzen" 403 | 404 | #: luckyLUKS/setupUI.py:334 luckyLUKS/setupUI.py:471 luckyLUKS/setupUI.py:551 405 | #: luckyLUKS/utils.py:70 luckyLUKS/utils.py:141 406 | msgid "Success" 407 | msgstr "Erfolgreich" 408 | 409 | #: luckyLUKS/setupUI.py:377 410 | msgid "Done" 411 | msgstr "Abgeschlossen" 412 | 413 | #: luckyLUKS/setupUI.py:427 414 | msgid "new_keyfile.bin" 415 | msgstr "neuer_schlüssel.bin" 416 | 417 | #: luckyLUKS/setupUI.py:436 418 | msgid "Creating key file" 419 | msgstr "Schlüsseldatei wird erstellt" 420 | 421 | #: luckyLUKS/setupUI.py:443 422 | msgid "" 423 | "This might take a while. Since computers are deterministic machines\n" 424 | "it is quite a challenge to generate real random data for the key.\n" 425 | "\n" 426 | "You can speed up the process by typing, moving the mouse\n" 427 | "and generally use the computer while the key gets generated." 428 | msgstr "" 429 | "Bitte Geduld, das kann eine Weile dauern. Das Erzeugen von\n" 430 | "Zufallsdaten ist eine echte Herausforderung für Computer.\n" 431 | "\n" 432 | "Der Vorgang kann beschleunigt werden, durch nicht vorher-\n" 433 | "sehbare Aktionen, wie Mausbewegungen, Tastatureingaben \n" 434 | "und allgemein durch die aktive Benutzung des Computers\n" 435 | "während der Erzeugung des Schlüssels." 436 | 437 | #: luckyLUKS/setupUI.py:469 438 | msgid "" 439 | "{key_file}\n" 440 | "successfully created!\n" 441 | "You can use this key file now,\n" 442 | "to create a new container." 443 | msgstr "" 444 | "{key_file}\n" 445 | "erfolgreich erstellt!\n" 446 | "Diese Schlüsseldatei kann jetzt mit\n" 447 | "einem neuen Container benutzt werden." 448 | 449 | #: luckyLUKS/setupUI.py:477 450 | msgid "" 451 | "Successfully unlocked!\n" 452 | "\n" 453 | "Do you want to create\n" 454 | "a startup menu entry for {device_name}?\n" 455 | "\n" 456 | "-> Your password will NOT be saved!\n" 457 | " This just creates a shortcut,\n" 458 | " to the unlock container dialog.\n" 459 | msgstr "" 460 | "Container erfolgreich geöffnet!\n" 461 | "\n" 462 | "Soll ein Eintrag ins Startmenü für\n" 463 | "{device_name} erstellt werden?\n" 464 | "\n" 465 | "-> Das Passwort wird NICHT gespeichert!\n" 466 | " Es wird nur eine Abkürzung zum\n" 467 | " `Container öffnen` Dialog erstellt.\n" 468 | 469 | #: luckyLUKS/setupUI.py:486 470 | msgid "Create shortcut" 471 | msgstr "Eintrag erstellen" 472 | 473 | #: luckyLUKS/setupUI.py:487 474 | msgid "No, thanks" 475 | msgstr "Nein, danke" 476 | 477 | #: luckyLUKS/setupUI.py:520 478 | msgid "Unlock {device_name}" 479 | msgstr "{device_name} öffnen" 480 | 481 | #: luckyLUKS/setupUI.py:523 482 | msgid "Encrypted Container Tool" 483 | msgstr "Programm für verschlüsselte Container" 484 | 485 | #: luckyLUKS/setupUI.py:524 486 | msgid "Encrypted Container" 487 | msgstr "Verschlüsselter Container" 488 | 489 | #: luckyLUKS/setupUI.py:551 490 | msgid "" 491 | "` {name} `\n" 492 | "added to start menu" 493 | msgstr "" 494 | "` {name} `\n" 495 | "Zum Startmenu hinzugefügt" 496 | 497 | #: luckyLUKS/setupUI.py:559 luckyLUKS/setupUI.py:566 498 | msgid "" 499 | "Adding to start menu not possible,\n" 500 | "please place your shortcut manually.\n" 501 | "\n" 502 | "Desktop file saved to\n" 503 | "{location}" 504 | msgstr "" 505 | "Automatisches Hinzufügen zum Startmenu\n" 506 | "nicht möglich, bitte manuell ausführen.\n" 507 | "\n" 508 | "Desktop-Datei wurde gespeichert unter:\n" 509 | "{location}" 510 | 511 | #: luckyLUKS/setupUI.py:587 512 | msgid "" 513 | "Currently processing your request!\n" 514 | "Do you really want to quit?" 515 | msgstr "" 516 | "Der Auftrag wird noch bearbeitet!\n" 517 | "Das Programm wirklich beenden?" 518 | 519 | #: luckyLUKS/setupUI.py:598 520 | msgid "" 521 | "No tools to format the filesystem found\n" 522 | "Please install, eg for Debian/Ubuntu\n" 523 | "`apt-get install e2fslibs ntfs-3g`" 524 | msgstr "" 525 | "Es konnten keine Programme zum Erstellen des Dateisystems gefunden werden!\n" 526 | "Bitte installieren, z.B. unter Debian/Ubuntu `apt-get install e2fslibs ntfs-3g`" 527 | 528 | #: luckyLUKS/setupUI.py:601 luckyLUKS/setupUI.py:655 529 | msgid "Create" 530 | msgstr "Erstellen" 531 | 532 | #: luckyLUKS/setupUI.py:606 533 | msgid "Please choose a container file" 534 | msgstr "Bitte Container Datei auswählen" 535 | 536 | #: luckyLUKS/setupUI.py:615 537 | msgid "Please choose a folder as mountpoint" 538 | msgstr "Bitte ein Verzeichnis als Einhängepunkt auswählen" 539 | 540 | #: luckyLUKS/setupUI.py:621 541 | msgid "Please choose a key file" 542 | msgstr "Bitte Schlüsseldatei auswählen" 543 | 544 | #: luckyLUKS/setupUI.py:634 545 | msgid "new_container.bin" 546 | msgstr "neuer_container.bin" 547 | 548 | #: luckyLUKS/setupUI.py:650 549 | msgid "Please create a new file" 550 | msgstr "Bitte neue Datei erstellen" 551 | 552 | #: luckyLUKS/setupUI.py:658 553 | msgid "" 554 | "File already exists:\n" 555 | "{filename}\n" 556 | "\n" 557 | "Please create a new file!" 558 | msgstr "" 559 | "Datei existiert bereits:\n" 560 | "{filename}\n" 561 | "\n" 562 | "Bitte als neue Datei erstellen!" 563 | 564 | #: luckyLUKS/setupUI.py:710 565 | msgid "" 566 | "Enter the path of the new container file in the textbox or click the " 567 | "button next to the box for a graphical create file dialog.\n" 568 | "\n" 569 | "The device name will be used to identify the unlocked container. It can " 570 | "be any name up to 16 unicode characters, as long as it is unique.\n" 571 | "\n" 572 | "The size of the container can be provided in GB or MB. The container " 573 | "will get initialized with random data, this can take quite a while - 1 hour for " 574 | "a 10GB container on an external drive is nothing unusual.\n" 575 | "\n" 576 | "To speed up container creation Quickformat can be enabled to use " 577 | "`fallocate` instead of initializing the container with random data - this means " 578 | "previous data will not be overwritten and some conclusions about encrypted data " 579 | "inside closed containers can be drawn.\n" 580 | msgstr "" 581 | "Eingabe des Names der neuen Container Datei über das Textfeld, oder über " 582 | "einen graphischen Dialog durch Drücken des Knopfes daneben.\n" 583 | "\n" 584 | "Anhand des Gerätenamens wird der geöffnete Container identifiziert. " 585 | "Jeder Name mit bis zu 16 Buchstaben Länge ist möglich, solange nicht zwei " 586 | "geöffnete Container denselben Namen haben.\n" 587 | "\n" 588 | "Die Grösse der Container Datei kann in GB oder MB angegeben werden. Es " 589 | "kann eine Weile dauern, den Container mit Zufallsdaten zu initialisieren. " 590 | "( 1Stunde pro 10GB sind bei externen Laufwerken nicht ungewöhnlich).\n" 591 | "\n" 592 | "Um diesen Prozess zu beschleunigen kann Quickformat ausgewählt und " 593 | "`fallocate` zum Initialisieren benutzt werden - damit werden allerdings alte " 594 | "Datenreste möglicherweise nicht überschrieben und einige (meist unkritische) " 595 | "Informationen über die verschlüsselten Daten im Container von aussen " 596 | "ersichtlich. \n" 597 | 598 | #: luckyLUKS/setupUI.py:726 luckyLUKS/setupUI.py:767 599 | msgid "" 600 | "A key file can be used to allow access to an encrypted container instead of a " 601 | "password. Using a key file resembles unlocking a door with a key in the real " 602 | "world - anyone with access to the key file can open your encrypted container. " 603 | "Make sure to store it at a protected location. Its okay to store it on your " 604 | "computer if you are using an already encrypted harddrive or a digital keystore. " 605 | "Having the key file on a small USB drive attached to your real chain of keys would be an " 607 | "option as well.\n" 608 | "Since you dont have to enter a password, using a key file can be a convenient " 609 | "way to access your encrypted container. Just make sure you dont lose the key " 610 | "(file) ;)" 611 | msgstr "" 612 | "Anstelle eines Passwortes kann auch eine Schlüsseldatei zum Öffnen eines " 613 | "verschlüsselten Containers benutzt werden. Eine Schlüsseldatei ähnelt " 614 | "prinzipiell einem physischen Schlüssel, da ein jeder der in den Besitz dieser " 615 | "Schlüsseldatei kommt, auch den damit verschlossenen Container öffnen kann. Die " 616 | "Schlüsseldatei sollte daher an einem sicheren Ort gespeichert werden. Dies kann " 617 | "auch der eigene Computer sein, wenn dessen Festplatte ohnehin mit dem " 618 | "Nutzerpasswort verschlüsselt wurde. Alternativ bieten sich zum Beispiel USB-Sticks an, die " 620 | "an ein echtes Schlüsselbund angehängt werden können.\n" 621 | "Da kein Passwort eingegeben werden muss, bieten Schlüsseldateien eine bequeme " 622 | "Art zum Öffnen von verschlüsselten Containern. Beachtet werden muss allerdings, " 623 | "dass die Schlüsseldatei nicht verloren geht, oder in falsche Hände gerät." 624 | 625 | #: luckyLUKS/setupUI.py:735 626 | msgid "" 627 | "\n" 628 | "\n" 629 | "Although basically any file could be used as a key file, a file with " 630 | "predictable content leads to similar problems as using weak passwords. Audio " 631 | "files or pictures are a good choice. If unsure use the `create key file` button " 632 | "to generate a small key file filled with random data." 633 | msgstr "" 634 | "\n" 635 | "\n" 636 | "Auch wenn grundsätzlich jede Datei als Schlüssel benutzt werden könnte, führen " 637 | "vorhersehbare Daten zu ähnlichen Problemen, wie einfache Passwörter. Gut " 638 | "geeignet sind Audiodateien oder Bilder, alternativ kann mit der `Schlüsseldatei " 639 | "erzeugen` Funktion auch eine kleine Datei erstellt werden, die sich optimal als " 640 | "Schlüssel eignet." 641 | 642 | #: luckyLUKS/setupUI.py:740 643 | msgid "encryption format" 644 | msgstr "Verschlüsselungsformat" 645 | 646 | #: luckyLUKS/setupUI.py:741 647 | msgid "" 648 | "The standard disk encryption format on Linux is called LUKS. With LibreCrypt you can use LUKS " 650 | "containers on Windows as well. The TrueCrypt format is quite popular on Windows/" 651 | "Mac, and can be created on Linux if `tcplay` is installed. Please note, that " 652 | "\"hidden\" TrueCrypt partitions are not supported by luckyLUKS!" 653 | msgstr "" 654 | "Das Standard Verschlüsselungs-Format unter Linux ist LUKS. Mit LibreCrypt können LUKS Container " 656 | "auch unter Windows genutzt werden. Populärer unter Windows ist das TrueCrypt " 657 | "Format. TrueCrypt Container können auch mit Linux erstellt werden, wenn " 658 | "`tcplay` installiert ist. Sogenannte \"hidden\" TrueCrypt Container werden " 659 | "allerdings nicht von luckyLUKS unterstützt!" 660 | 661 | #: luckyLUKS/setupUI.py:748 662 | msgid "" 663 | "Choose the ntfs filesystem to be able to access your data from Linux, Windows " 664 | "and Mac OSX. Since access permissions cannot be mapped from ntfs to Linux, " 665 | "access to ntfs devices is usually not restricted -> take care when using " 666 | "unlocked ntfs devices in a multiuser environment!" 667 | msgstr "" 668 | "Auf das NTFS Dateisystem kann sowohl unter Windows als auch unter Linux " 669 | "zugegriffen werden, allerdings ist der Zugriff auf eingehängte NTFS Geräte " 670 | "unter Linux oft nicht auf den aktuellen Benutzer begrenzt. Vorsicht also beim " 671 | "Öffnen von NTFS-Containern auf Rechnern mit mehreren Benutzern, möglichst den " 672 | "Container vor jeden Benutzerwechsel schliessen!" 673 | 674 | #: luckyLUKS/setupUI.py:759 675 | msgid "" 676 | "Select the encrypted container file by clicking the button next to the " 677 | "textbox. Both LUKS and Truecrypt containers are supported!\n" 678 | "\n" 679 | "The device name will be used to identify the unlocked container. It can " 680 | "be any name up to 16 unicode characters, as long as it is unique -> you cannot " 681 | "give two unlocked containers the same name" 682 | msgstr "" 683 | "Die Auswahl der verschlüsselten Container Datei ist per Dialog durch " 684 | "Drücken des Knopfes neben dem Textfeld möglich. Sowohl LUKS als auch Truecrypt " 685 | "Container werden unterstützt!\n" 686 | "\n" 687 | "Anhand des Gerätenamens wird der geöffnete Container identifiziert. " 688 | "Jeder Name mit bis zu 16 Buchstaben Länge ist möglich, solange nicht zwei " 689 | "geöffnete Container denselben Namen haben." 690 | 691 | #: luckyLUKS/setupUI.py:777 692 | msgid "" 693 | "The mount point is the folder on your computer, where you can access the files " 694 | "inside the container after unlocking. If automatic mounting is configured on " 695 | "your system (eg with udisks), explicitly setting a mountpoint is not neccessary " 696 | "(but still possible)." 697 | msgstr "" 698 | "Der Einhängepunkt ist das Verzeichnis auf dem Rechner, in dem die Inhalte nach " 699 | "dem Öffnen des Containers erscheinen. Wenn das verwendete System externe Medien " 700 | "automatisch einhängt, braucht ein Einhängepunkt nicht explizit angegeben werden." 701 | 702 | #: luckyLUKS/unlockUI.py:122 703 | msgid "" 704 | "Always allow luckyLUKS to be run\n" 705 | "with administrative privileges" 706 | msgstr "" 707 | "Administrator Rechte für luckyLUKS \n" 708 | "dauerhaft freigeben" 709 | 710 | #: luckyLUKS/unlockUI.py:153 711 | msgid "" 712 | "Please choose a passphrase\n" 713 | "to encrypt the new container:\n" 714 | msgstr "" 715 | "Bitte ein Passwort zum Verschlüsseln\n" 716 | "des neuen Containers wählen:\n" 717 | 718 | #: luckyLUKS/unlockUI.py:154 719 | msgid "Enter new Passphrase" 720 | msgstr "Neues Passwort eingeben" 721 | 722 | #: luckyLUKS/unlockUI.py:156 723 | msgid "Display passphrase" 724 | msgstr "Passwort anzeigen" 725 | 726 | #: luckyLUKS/unlockUI.py:189 727 | msgid "" 728 | "Currently creating new container!\n" 729 | "Do you really want to quit?" 730 | msgstr "" 731 | "Es wird zur Zeit ein neuer Container erstellt!\n" 732 | "Das Programm wirklich beenden?" 733 | 734 | #: luckyLUKS/unlockUI.py:220 735 | msgid "" 736 | "Using keyfile\n" 737 | "{keyfile}\n" 738 | "to open container.\n" 739 | "\n" 740 | "Please wait .." 741 | msgstr "" 742 | "Benutze Schlüsseldatei\n" 743 | "{keyfile}\n" 744 | "um den Container zu öffnen.\n" 745 | "\n" 746 | "Bitte einen Moment Geduld .." 747 | 748 | #: luckyLUKS/unlockUI.py:254 749 | msgid "Checking passphrase .." 750 | msgstr "Überprüfe Passwort .." 751 | 752 | #: luckyLUKS/unlockUI.py:280 753 | msgid "" 754 | "Please enter\n" 755 | "container passphrase:" 756 | msgstr "" 757 | "Bitte das Passwort zum Öffnen\n" 758 | "des verschlüsselten Containers eingeben:" 759 | 760 | #: luckyLUKS/unlockUI.py:282 761 | msgid "" 762 | "Wrong passphrase, please retry!\n" 763 | "Enter container passphrase:" 764 | msgstr "" 765 | "Falsches Passwort, bitte erneut versuchen!\n" 766 | "Passwort des verschlüsselten Containers eingeben:" 767 | 768 | #: luckyLUKS/utils.py:64 769 | msgid "" 770 | "Permanent `sudo` authorization for\n" 771 | "{program}\n" 772 | "has been successfully added for user `{username}` to \n" 773 | "/etc/sudoers.d/lucky-luks\n" 774 | msgstr "" 775 | "Administrator Rechte für\n" 776 | "{program}\n" 777 | "erfolgreich in /etc/sudoers.d/lucky-luks\n" 778 | "für den Benutzer `{username}` freigegeben.\n" 779 | 780 | #: luckyLUKS/utils.py:79 781 | msgid "" 782 | "luckyLUKS needs administrative privileges.\n" 783 | "Please enter your password:" 784 | msgstr "" 785 | "luckyLUKS benötigt Administrator Rechte.\n" 786 | "Bitte Passwort eingeben:" 787 | 788 | #: luckyLUKS/utils.py:98 luckyLUKS/utils.py:126 789 | msgid "Sorry, incorrect password.\n" 790 | msgstr "Falsches Passwort, bitte erneut versuchen.\n" 791 | 792 | #: luckyLUKS/utils.py:114 793 | msgid "" 794 | "You are not allowed to execute this script with `sudo`.\n" 795 | "If you want to modify your `sudo` configuration,\n" 796 | "please enter the root/administrator password.\n" 797 | msgstr "" 798 | "Fehlende Berechtigung, dieses Programm mit `sudo` auszuführen.\n" 799 | "Um die Konfiguration von `sudo` anzupassen,\n" 800 | "bitte das root/Administrator Passwort eingeben.\n" 801 | 802 | #: luckyLUKS/utils.py:137 803 | msgid "" 804 | "`sudo` configuration successfully modified, now\n" 805 | "you can use luckyLUKS with your user password.\n" 806 | "\n" 807 | "If you want to grant permanent administrative rights\n" 808 | "just tick the checkbox in the following dialog.\n" 809 | msgstr "" 810 | "Konfiguration von `sudo` erfolgreich geändert, Starten von\n" 811 | "luckyLUKS ist jetzt mit dem Passwort des Nutzers möglich.\n" 812 | "\n" 813 | "Um Administratorrechte dauerhaft zu vergeben\n" 814 | "einfach die Checkbox im folgenden Dialog auswählen.\n" 815 | 816 | #: luckyLUKS/utils.py:162 817 | msgid "" 818 | "Communication with sudo process failed\n" 819 | "{error}" 820 | msgstr "" 821 | "Fehler in der Kommunikation mit `sudo`\n" 822 | "{error}" 823 | 824 | #: luckyLUKS/utils.py:224 luckyLUKS/utils.py:232 luckyLUKS/utils.py:259 825 | #: luckyLUKS/worker.py:130 826 | msgid "" 827 | "Error in communication:\n" 828 | "{error}" 829 | msgstr "" 830 | "Kommunikationsfehler:\n" 831 | "{error}" 832 | 833 | #: luckyLUKS/utils.py:315 834 | msgid "" 835 | "Error while creating key file:\n" 836 | "{error}" 837 | msgstr "" 838 | "Fehler beim Erstellen der Schlüsseldatei:\n" 839 | "{error}" 840 | 841 | #: luckyLUKS/utilsUI.py:69 842 | msgid "Advanced Topics:" 843 | msgstr "Erweiterte Optionen:" 844 | 845 | #: luckyLUKS/utilsUI.py:93 846 | msgid "" 847 | "luckyLUKS version {version}\n" 848 | "For more information, visit\n" 849 | "{project_url}" 850 | msgstr "" 851 | "luckyLUKS Version {version}\n" 852 | "Weitere Informationen auf der Homepage\n" 853 | "{project_url}" 854 | 855 | #: luckyLUKS/utilsUI.py:135 856 | msgid "Error" 857 | msgstr "Fehler" 858 | 859 | #: luckyLUKS/worker.py:72 860 | msgid "Please call with sudo." 861 | msgstr "Bitte mit sudo aufrufen." 862 | 863 | #: luckyLUKS/worker.py:75 864 | msgid "" 865 | "Missing information of the calling user in sudo environment.\n" 866 | "Please make sure sudo is configured correctly." 867 | msgstr "" 868 | "Sudo Informationen unvollständig - Aufrufender Benutzer nicht angegeben\n" 869 | "Bitte sicherstellen, dass Sudo richtig konfiguriert ist." 870 | 871 | #: luckyLUKS/worker.py:121 872 | msgid "Helper process received unknown command" 873 | msgstr "Hilfsprozess hat eine unbekannte Anweisung bekommen" 874 | 875 | #: luckyLUKS/worker.py:201 luckyLUKS/worker.py:454 876 | msgid "Device Name is empty" 877 | msgstr "Kein Gerätename angegeben" 878 | 879 | #: luckyLUKS/worker.py:215 880 | msgid "" 881 | "Could not use container:\n" 882 | "{file_path}\n" 883 | "{device_name} is already unlocked\n" 884 | "using a different container\n" 885 | "Please change the name to unlock this container" 886 | msgstr "" 887 | "Container konnte nicht verwendet werden:\n" 888 | "{file_path}\n" 889 | "Der Name {device_name} wird bereits\n" 890 | "von einem anderen Container verwendet.\n" 891 | "Bitte den Namen ändern, um diesen Container zu öffnen" 892 | 893 | #: luckyLUKS/worker.py:231 894 | msgid "" 895 | "Device Name too long:\n" 896 | "Only up to 16 characters possible, even less for unicode\n" 897 | "(roughly 8 non-ascii characters possible)" 898 | msgstr "" 899 | "Gerätename zu lang:\n" 900 | "Nur bis zu 16 Buchstaben möglich,\n" 901 | "wird Unicode verwendet etwas weniger.\n" 902 | "(ungefähr 8 nicht-ASCII Buchstaben möglich)" 903 | 904 | #: luckyLUKS/worker.py:238 luckyLUKS/worker.py:458 905 | msgid "" 906 | "Illegal Device Name!\n" 907 | "Names starting with `-` or using `/` are not possible" 908 | msgstr "" 909 | "Gerätename nicht erlaubt!\n" 910 | "Namen, die mit einem Bindestrich anfangen (`-`),\n" 911 | "oder `/` verwenden sind nicht möglich." 912 | 913 | #: luckyLUKS/worker.py:258 914 | msgid "" 915 | "Cannot use the container\n" 916 | "{file_path}\n" 917 | "The container is already in use ({existing_device})." 918 | msgstr "" 919 | "Container kann nicht benutzt werden\n" 920 | "{file_path}\n" 921 | "Der Container wird bereits verwendet ({existing_device})." 922 | 923 | #: luckyLUKS/worker.py:270 luckyLUKS/worker.py:497 924 | msgid "" 925 | "Key file not accessible\n" 926 | "or path does not exist:\n" 927 | "\n" 928 | "{file_path}" 929 | msgstr "" 930 | "Kein Zugriff auf die gewünschte Schlüssel Datei:\n" 931 | "\n" 932 | "{file_path}" 933 | 934 | #: luckyLUKS/worker.py:279 935 | msgid "" 936 | "Mount point not accessible\n" 937 | "or path does not exist:\n" 938 | "\n" 939 | "{mount_dir}" 940 | msgstr "" 941 | "Kein Zugriff auf den gewünschten Einhängepunkt möglich:\n" 942 | "\n" 943 | "{mount_dir}" 944 | 945 | #: luckyLUKS/worker.py:284 946 | msgid "" 947 | "Already mounted at mount point:\n" 948 | "\n" 949 | "{mount_dir}" 950 | msgstr "" 951 | "Der gewünschte Einhängepunkt wird bereits verwendet:\n" 952 | "\n" 953 | "{mount_dir}" 954 | 955 | #: luckyLUKS/worker.py:289 956 | msgid "" 957 | "Designated mount directory\n" 958 | "{mount_dir}\n" 959 | "is not empty" 960 | msgstr "" 961 | "Der gewünschte Einhängepunkt\n" 962 | "{mount_dir}\n" 963 | "ist nicht leer." 964 | 965 | #: luckyLUKS/worker.py:368 966 | msgid "" 967 | "Open container failed.\n" 968 | "Please check key file" 969 | msgstr "" 970 | "Öffnen des Containers fehlgeschlagen.\n" 971 | "Bitte Schlüsseldatei überprüfen." 972 | 973 | #: luckyLUKS/worker.py:404 974 | msgid "Unable to close container, device is busy" 975 | msgstr "Container wird zur Zeit verwendet, Schliessen nicht möglich." 976 | 977 | #: luckyLUKS/worker.py:450 978 | msgid "Container Filename not supplied" 979 | msgstr "Keine Container Datei angegeben" 980 | 981 | #: luckyLUKS/worker.py:462 982 | msgid "" 983 | "Device Name too long:\n" 984 | "Only up to 16 characters possible, even less for unicode \n" 985 | "(roughly 8 non-ascii characters possible)" 986 | msgstr "" 987 | "Gerätename zu lang:\n" 988 | "Nur bis zu 16 Buchstaben möglich,\n" 989 | "wird Unicode verwendet etwas weniger.\n" 990 | "(ungefähr 8 nicht-ASCII Buchstaben möglich)" 991 | 992 | #: luckyLUKS/worker.py:467 993 | msgid "" 994 | "Device Name already in use:\n" 995 | "\n" 996 | "{device_name}" 997 | msgstr "" 998 | "Gerätename wird bereits verwendet:\n" 999 | "\n" 1000 | "{device_name}" 1001 | 1002 | #: luckyLUKS/worker.py:471 1003 | msgid "" 1004 | "Container size too small\n" 1005 | "to create encrypted filesystem\n" 1006 | "Please choose at least 5MB" 1007 | msgstr "" 1008 | "Grösse der Container Datei zu klein,\n" 1009 | "um ein verschlüsseltes Dateisystem anzulegen.\n" 1010 | "Bitte mindestens 5MB auswählen." 1011 | 1012 | #: luckyLUKS/worker.py:485 1013 | msgid "" 1014 | "Not enough free disc space for container:\n" 1015 | "\n" 1016 | "{space_needed} MB needed\n" 1017 | "{space_available} MB available" 1018 | msgstr "" 1019 | "Nicht genügend freier Speciherplatz für den Container:\n" 1020 | "\n" 1021 | "{space_needed} MB benötigt\n" 1022 | "{space_available} MB vorhanden" 1023 | 1024 | #: luckyLUKS/worker.py:504 1025 | msgid "Unknown filesystem type: {filesystem_type}" 1026 | msgstr "Unbekanntes Dateisystem: {filesystem_type}" 1027 | 1028 | #: luckyLUKS/worker.py:507 1029 | msgid "" 1030 | "If you want to use TrueCrypt containers\n" 1031 | "make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n" 1032 | "and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)" 1033 | msgstr "" 1034 | "Um einen TrueCrypt Container zu öffnen\n" 1035 | "muss `cryptsetup` mindestens Version 1.6 sein (`cryptsetup --version`),\n" 1036 | "und `tcplay` installiert sein (z.B. in Debian/Ubuntu `apt-get install tcplay`)" 1037 | 1038 | #: luckyLUKS/worker.py:598 1039 | msgid "Unknown encryption format: {enc_fmt}" 1040 | msgstr "Unbekanntes Verschlüsselungsformat: {enc_fmt}" 1041 | 1042 | #: luckyLUKS/worker.py:653 1043 | msgid "Cannot change sudo rights, invalid username" 1044 | msgstr "Ungültiger Benutzername, Ändern der sudo-Rechte nicht möglich." 1045 | 1046 | #: luckyLUKS/worker.py:659 1047 | msgid "" 1048 | "I`m afraid I can`t do that.\n" 1049 | "\n" 1050 | "To be able to permit permanent changes to sudo rights,\n" 1051 | "please make sure the program is owned by root\n" 1052 | "and not writeable by others.\n" 1053 | "Execute the following commands in your shell:\n" 1054 | "\n" 1055 | "chmod 755 {program}\n" 1056 | "sudo chown root:root {program}\n" 1057 | "\n" 1058 | msgstr "" 1059 | "`Das kann ich nicht tun, Dave`\n" 1060 | "\n" 1061 | "Um dauerhafte Rechte mit `sudo` vergeben zu können,\n" 1062 | "muss das Programm dem User `root` gehören,\n" 1063 | "andere Nutzer dürfen es nicht verändern können!\n" 1064 | "Bitte die folgenden Befehle auf der Kommandozeile eingeben:\n" 1065 | "\n" 1066 | "chmod 755 {program}\n" 1067 | "sudo chown root:root {program}\n" 1068 | "\n" 1069 | -------------------------------------------------------------------------------- /luckyLUKS/worker.py: -------------------------------------------------------------------------------- 1 | """ 2 | Because access to /dev/mapper is restricted to root, administrator privileges are needed 3 | to handle encrypted containers as block devices. The GUI will be run from a normal 4 | user account while all calls to cryptsetup and mount will be executed from a separate 5 | worker process with administrator privileges. Everything that runs with elevated privs 6 | is included in this module. 7 | 8 | Copyright (c) 2014,2015,2022 Jasper van Hoorn (muzius@gmail.com) 9 | 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU General Public License as published by 12 | the Free Software Foundation, either version 3 of the License, or 13 | (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, 16 | but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | GNU General Public License for more details. 19 | """ 20 | 21 | import subprocess 22 | import os 23 | import json 24 | import sys 25 | import traceback 26 | import pwd 27 | import stat 28 | import warnings 29 | import threading 30 | import signal 31 | import random 32 | from uuid import uuid4 33 | from time import sleep 34 | import queue 35 | 36 | 37 | class WorkerException(Exception): 38 | """ Used to catch error messages from shell calls to give feedback via gtk-gui """ 39 | 40 | 41 | class UserAbort(Exception): 42 | """ Worker gets notified about user canceling the command -> no response needed """ 43 | 44 | 45 | def com_thread(cmdqueue): 46 | """ Monitors the incoming pipe and puts commands in a queue. Linux signals cannot be sent 47 | from the parent to a privileged childprocess, so to send sigterm this thread detects if the parent 48 | closes the pipe instead, to terminate the main thread and all its child processes. 49 | :param cmdqueue: Queue to pass incoming commands to main thread 50 | :type cmdqueue: queue 51 | """ 52 | while True: 53 | try: 54 | buf = sys.stdin.readline().strip() # blocks 55 | if not buf: # check if input pipe closed 56 | break 57 | cmdqueue.put(json.loads(buf)) 58 | except IOError: 59 | break 60 | # send INT to all child processes (eg currently creating new container with dd etc..) 61 | os.killpg(0, signal.SIGINT) 62 | 63 | 64 | def run(): 65 | """ Initialize helper and setup ipc. Reads json encoded, newline terminated commands from stdin, 66 | performs the requested command and returns the json encoded, newline terminated answer on stdout. 67 | All commands are executed sequentially, but stdin.readline() gets read in another thread that monitors 68 | the incoming pipe and passes to a queue or quits the helper process if the parent closes the pipe. 69 | Watching for closed pipe is needed because normal signals cannot be sent to a privileged childprocess """ 70 | 71 | if os.getuid() != 0: 72 | sys.stdout.write(_('Please call with sudo.')) 73 | sys.exit(1) 74 | elif os.getenv("SUDO_USER") is None or os.getenv("SUDO_UID") is None or os.getenv("SUDO_GID") is None: 75 | sys.stdout.write(_('Missing information of the calling user in sudo environment.\n' 76 | 'Please make sure sudo is configured correctly.')) 77 | sys.exit(1) 78 | else: 79 | # send ack to establish simple json encoded request/response protocol using \n as terminator 80 | sys.stdout.write('ESTABLISHED') 81 | sys.stdout.flush() 82 | 83 | # start thread to monitor the incomming pipe, communicate via queue 84 | cmdqueue = queue.Queue() 85 | worker = WorkerHelper(cmdqueue) 86 | t = threading.Thread(target=com_thread, args=(cmdqueue,)) 87 | t.start() 88 | # create process group to be able to quit all child processes of the worker 89 | os.setpgrp() 90 | 91 | with warnings.catch_warnings(): 92 | warnings.filterwarnings('error') # catch warnings to keep them from messing up the pipe 93 | while True: 94 | response = {'type': 'response', 'msg': 'success'} # return success unless exception 95 | try: 96 | # arbitrary timeout needed to be able to get instant KeyboardInterrupt 97 | # pythons queue.get() without timeout prefers not to be interrupted by KeyboardInterrupt :) 98 | cmd = cmdqueue.get(timeout=32767) 99 | if cmd['msg'] == 'status': 100 | is_unlocked = worker.check_status(cmd['device_name'], cmd['container_path'], 101 | cmd['key_file'], cmd['mount_point']) 102 | if not is_unlocked and cmd['key_file'] is not None: # if keyfile used try to unlock on startup 103 | worker.unlock_container(cmd['device_name'], cmd['container_path'], 104 | cmd['key_file'], cmd['mount_point']) 105 | response['msg'] = 'unlocked' 106 | else: 107 | response['msg'] = 'unlocked' if is_unlocked else 'closed' 108 | elif cmd['msg'] == 'unlock': 109 | worker.unlock_container(cmd['device_name'], cmd['container_path'], 110 | cmd['key_file'], cmd['mount_point']) 111 | elif cmd['msg'] == 'close': 112 | worker.close_container(cmd['device_name'], cmd['container_path']) 113 | elif cmd['msg'] == 'create': 114 | worker.create_container(cmd['device_name'], cmd['container_path'], 115 | cmd['container_size'], cmd['filesystem_type'], 116 | cmd['encryption_format'], cmd['key_file'], 117 | cmd['quickformat']) 118 | elif cmd['msg'] == 'authorize': 119 | worker.modify_sudoers(os.getenv("SUDO_UID"), nopassword=True) 120 | else: 121 | raise WorkerException(_('Helper process received unknown command')) 122 | except queue.Empty: 123 | continue # timeout reached on queue.get() -> keep polling 124 | except UserAbort: 125 | continue # no response needed 126 | except WorkerException as we: 127 | response = {'type': 'error', 'msg': str(we)} 128 | except KeyError as ke: 129 | # thrown if required parameters missing 130 | response = {'type': 'error', 'msg': _('Error in communication:\n{error}').format(error=str(ke))} 131 | except KeyboardInterrupt: 132 | # gets raised by killpg -> quit 133 | sys.exit(0) 134 | except Exception: # catch ANY exception (including warnings) to show via gui 135 | response = {'type': 'error', 'msg': ''.join(traceback.format_exception(*sys.exc_info()))} 136 | sys.stdout.write(json.dumps(response) + '\n') 137 | sys.stdout.flush() 138 | 139 | 140 | class WorkerHelper(): 141 | 142 | """ accepts 5 commands: 143 | -> check_status() validates the input and returns the current state (unlocked/closed) of the container 144 | -> unlock_container() asks for the passphrase and tries to unlock and mount a container 145 | -> close_container() closes and unmounts a container 146 | -> create_container() initializes a new encrypted LUKS container and sets up the filesystem 147 | -> modify_sudoers() adds sudo access to the program without password for the current user (/etc/sudoers.d/) 148 | """ 149 | 150 | def __init__(self, cmdqueue=None): 151 | """ Check tcplay installation """ 152 | self.cmdqueue = cmdqueue 153 | self.is_tc_installed = any([os.path.exists(os.path.join(p, 'tcplay')) 154 | for p in os.environ["PATH"].split(os.pathsep)]) 155 | 156 | def communicate(self, request): 157 | """ Helper to get an synchronous response from UI (obtain Passphrase or signal create progress) 158 | :param request: message to send to the UI 159 | :type request: str 160 | :returns: The JSON encoded response from the UI 161 | :rtype: str 162 | :raises: UserAbort 163 | """ 164 | sys.stdout.write(json.dumps({'type': 'request', 'msg': request}) + '\n') 165 | sys.stdout.flush() 166 | response = self.cmdqueue.get() # wait for response 167 | try: 168 | assert('type' in response and 'msg' in response) 169 | except AssertionError as ae: 170 | raise UserAbort() from ae 171 | # quit if abort or unexpected msg from ui 172 | if response['type'] != 'response': 173 | raise UserAbort() 174 | 175 | return response['msg'] 176 | 177 | def check_status(self, device_name, container_path, key_file=None, mount_point=None): 178 | """ 179 | Validates the input and returns the current state (unlocked/closed) of the container. 180 | The checks are sufficient to keep users from shooting themselves in the foot and 181 | provide the most possible protection against TOCTOU attacks: Since most commands are 182 | executed using the device mapper name, there is not much attack surface for TOCTOU anyway, 183 | since root access is needed to access device mapper. It would be possible to unmount 184 | other users containers with the right timing though - slightly annoying but no serious threat 185 | :param device_name: The device mapper name 186 | :type device_name: str 187 | :param container_path: The path of the container file 188 | :type container_path: str 189 | :param key_file: The path to an optional keyfile to be used for the container 190 | :type key_file: str or None 191 | :param mount_point: The path of an optional mount point 192 | :type mount_point: str or None 193 | :returns: True if LUKS device is active/unlocked 194 | :rtype: bool 195 | :raises: WorkerException 196 | """ 197 | uid = int(os.getenv("SUDO_UID")) 198 | 199 | # device_name and container_path valid? 200 | if device_name == '': 201 | raise WorkerException(_('Device Name is empty')) 202 | # check access rights to container file 203 | if not os.path.exists(container_path) or os.stat(container_path).st_uid != uid: 204 | sleep(random.random()) # 0-1s to prevent misuse of exists() 205 | raise WorkerException( 206 | _('Container file not accessible\nor path does not exist:\n\n{file_path}') 207 | .format(file_path=container_path) 208 | ) 209 | 210 | is_unlocked = self.is_LUKS_active(device_name) 211 | 212 | if is_unlocked: 213 | # make sure container file currently in use for device name is the same as the supplied container path 214 | if container_path != self.get_container(device_name): 215 | raise WorkerException(_('Could not use container:\n{file_path}\n' 216 | '{device_name} is already unlocked\n' 217 | 'using a different container\n' 218 | 'Please change the name to unlock this container') 219 | .format(file_path=container_path, device_name=device_name)) 220 | 221 | # container is not unlocked 222 | else: 223 | # validate device name 224 | if len(bytes(device_name, 'utf-8')) > 16: 225 | # the goal here is not to confuse the user: 226 | # thus Name==Label of the partition inside the encrypted container 227 | # -> because thats what usually gets presented to the user (filemanager) 228 | # see checks in create_container below for length restictions on partition labels 229 | # dev/mapper would only be able to handle slightly longer names for unicode anyways 230 | raise WorkerException( 231 | _('Device Name too long:\n' 232 | 'Only up to 16 characters possible, even less for unicode\n' 233 | '(roughly 8 non-ascii characters possible)') 234 | ) 235 | 236 | if device_name[0:1] == '-' or '/' in device_name: 237 | # cryptsetup thinks -isanoption and "/" is not supported by devmapper 238 | raise WorkerException(_('Illegal Device Name!\nNames starting with `-` or using `/` are not possible')) 239 | 240 | # prevent container from being unlocked multiple times with different names 241 | container_found = subprocess.check_output( 242 | ['losetup', '-j', container_path], 243 | stderr=subprocess.STDOUT, universal_newlines=True 244 | ) 245 | if container_found != '': 246 | # container is already in use -> try to find out the device name 247 | existing_device_name = '' 248 | encrypted_devices = subprocess.check_output( 249 | ['dmsetup', 'status', '--target', 'crypt'], 250 | stderr=subprocess.STDOUT, universal_newlines=True 251 | ).strip() 252 | for encrypted_device in encrypted_devices.split('\n'): 253 | encrypted_device = encrypted_device[:encrypted_device.find(':')].strip() 254 | if container_path == self.get_container(encrypted_device): 255 | existing_device_name = encrypted_device 256 | break 257 | raise WorkerException( 258 | _('Cannot use the container\n' 259 | '{file_path}\n' 260 | 'The container is already in use ({existing_device}).') 261 | .format(file_path=container_path, existing_device=existing_device_name) 262 | ) 263 | 264 | # validate key_file if given 265 | if (key_file is not None and 266 | any([(not os.path.exists(key_file)), 267 | (os.stat(key_file).st_uid != int(os.getenv("SUDO_UID")))])): 268 | sleep(random.random()) # 0-1s to prevent misuse of exists() 269 | raise WorkerException( 270 | _('Key file not accessible\nor path does not exist:\n\n{file_path}') 271 | .format(file_path=key_file)) 272 | 273 | # validate mount_point if given 274 | if mount_point is not None: 275 | 276 | if not os.path.exists(mount_point) or os.stat(mount_point).st_uid != uid: 277 | sleep(random.random()) # 0-1s to prevent misuse of exists() 278 | raise WorkerException( 279 | _('Mount point not accessible\nor path does not exist:\n\n{mount_dir}') 280 | .format(mount_dir=mount_point) 281 | ) 282 | if os.path.ismount(mount_point): 283 | raise WorkerException( 284 | _('Already mounted at mount point:\n\n{mount_dir}') 285 | .format(mount_dir=mount_point) 286 | ) 287 | if os.listdir(mount_point): 288 | raise WorkerException( 289 | _('Designated mount directory\n{mount_dir}\nis not empty') 290 | .format(mount_dir=mount_point) 291 | ) 292 | 293 | return is_unlocked 294 | 295 | def unlock_container(self, device_name, container_path, key_file=None, mount_point=None, pw_callback=None): 296 | """ Unlocks LUKS or Truecrypt containers. 297 | Validates input and keeps asking 298 | for the passphrase until successfull unlock, 299 | followed by an optional mount. 300 | :param device_name: The device mapper name 301 | :type device_name: str 302 | :param container_path: The path of the container file 303 | :type container_path: str 304 | :param key_file: The path to an optional keyfile to be used for the container 305 | :type key_file: str or None 306 | :param mount_point: The path of an optional mount point 307 | :type mount_point: str or None 308 | :param pw_callback: A callback function that returns the password for unlocking 309 | :type pw_callback: function() 310 | :raises: WorkerException 311 | """ 312 | is_unlocked = self.check_status(device_name, container_path, key_file, mount_point) 313 | if not is_unlocked: # just return if unlocked -> does not mount an already unlocked container 314 | if pw_callback is None: 315 | pw_callback = lambda: self.communicate('getPassword') 316 | # workaround udisks-daemon crash (udisksd from udisks2 is okay): although cryptsetup is able to handle 317 | # loopback device creation/teardown itself, using this crashes udisks-daemon 318 | # -> manual loopback device handling here 319 | # TODO: could be removed, udisks is replaced with udisks2 since ~2016 320 | try: 321 | loop_dev = subprocess.check_output( 322 | ['losetup', '-f', '--show', container_path], 323 | stderr=subprocess.PIPE, universal_newlines=True 324 | ).strip() 325 | except subprocess.CalledProcessError as cpe: 326 | # most likely no more loopdevices available 327 | raise WorkerException(cpe.output) from cpe 328 | crypt_initialized = False 329 | 330 | try: 331 | # check if LUKS container, try Truecrypt otherwise (tc container cannot be identified by design) 332 | container_is_luks = (subprocess.call(['cryptsetup', 'isLuks', container_path]) == 0) 333 | if container_is_luks: 334 | open_command = ['cryptsetup', 'open', loop_dev, device_name] 335 | else: 336 | open_command = ['cryptsetup', 'open', '--type', 'tcrypt', loop_dev, device_name] 337 | 338 | with open(os.devnull) as DEVNULL: 339 | if key_file is None: 340 | while not is_unlocked: 341 | p = subprocess.Popen( 342 | open_command, 343 | stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, 344 | universal_newlines=True, close_fds=True 345 | ) 346 | __, errors = p.communicate(pw_callback()) 347 | if p.returncode == 0: 348 | is_unlocked = True 349 | elif p.returncode == 2: # cryptsetup: no permission (bad passphrase) 350 | continue 351 | else: 352 | raise WorkerException(errors) 353 | else: 354 | open_command += ['--key-file', key_file] 355 | p = subprocess.Popen( 356 | open_command, 357 | stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, 358 | universal_newlines=True, close_fds=True 359 | ) 360 | if container_is_luks: 361 | __, errors = p.communicate() 362 | else: 363 | # tcplay with keyfile only means empty password 364 | __, errors = p.communicate('\n') 365 | if p.returncode != 0: 366 | if p.returncode == 2: 367 | # error message from cryptsetup is a bit ambiguous 368 | raise WorkerException(_('Open container failed.\nPlease check key file')) 369 | raise WorkerException(errors) 370 | crypt_initialized = True 371 | finally: 372 | if not crypt_initialized: 373 | self.detach_loopback_device(loop_dev) 374 | 375 | if mount_point is not None: # only mount if optional parameter mountpoint is set 376 | try: 377 | subprocess.check_output( 378 | ['mount', '-o', 'nosuid,nodev', self.get_device_mapper_name(device_name), mount_point], 379 | stderr=subprocess.STDOUT, universal_newlines=True) 380 | except subprocess.CalledProcessError as cpe: 381 | raise WorkerException(cpe.output) from cpe 382 | 383 | def close_container(self, device_name, container_path): 384 | """ Validates input and tries to unmount /dev/mapper/ and close container 385 | :param device_name: The device mapper name 386 | :type device_name: str 387 | :param container_path: The path of the container file 388 | :type container_path: str 389 | :raises: WorkerException 390 | """ 391 | if self.check_status(device_name, container_path): # just return if not unlocked 392 | # for all mounting /dev/mapper/device_name is used 393 | try: 394 | # unfortunately, umount returns the same errorcode for device not mounted and device busy 395 | # clear locale to be able to parse output 396 | env_lang_cleared = os.environ.copy() 397 | env_lang_cleared['LANGUAGE'] = 'C' 398 | subprocess.check_output( 399 | ['umount', self.get_device_mapper_name(device_name)], 400 | stderr=subprocess.STDOUT, universal_newlines=True, env=env_lang_cleared 401 | ) 402 | except subprocess.CalledProcessError as cpe: 403 | if 'not mounted' not in cpe.output: # ignore if not mounted and proceed with closing the container 404 | raise WorkerException(_('Unable to close container, device is busy')) from cpe 405 | # get reference to loopback device before closing the container 406 | associated_loop = self.get_loopback_device(device_name) 407 | try: 408 | subprocess.check_output( 409 | ['cryptsetup', 'close', device_name], 410 | stderr=subprocess.STDOUT, universal_newlines=True 411 | ) 412 | except subprocess.CalledProcessError as cpe: 413 | raise WorkerException(cpe.output) from cpe 414 | # remove loopback device 415 | sleep(0.2) # give udisks some time to process closing of container .. 416 | self.detach_loopback_device(associated_loop) 417 | 418 | def create_container(self, device_name, container_path, container_size, 419 | filesystem_type, enc_format, key_file=None, quickformat=False): 420 | """ Creates a new LUKS2 container with requested size and filesystem after validating parameters 421 | Three step process: asks for passphrase after initializing container with random bits, 422 | and signals successful LUKS initialization before writing the filesystem 423 | :param device_name: The device mapper name, used as filesystem label as well 424 | :type device_name: str 425 | :param container_path: The path of the container file to be created 426 | :type container_path: str 427 | :param container_size: The size the new container in KB 428 | :type container_size: int 429 | :param filesystem_type: The type of the filesystem inside the new container 430 | (supported: 'ext4', 'ext2', 'ntfs') 431 | :type filesystem_type: str 432 | :param enc_format: The type of the encryption format used for the new container 433 | (supported: 'LUKS', 'TrueCrypt') 434 | :type enc_format: str 435 | :param key_file: The path to an optional keyfile to be used for the container 436 | :type key_file: str or None 437 | :param quickformat: Use fallocate instead of initializing container with random data 438 | :type quickformat: bool 439 | :raises: WorkerException 440 | """ 441 | # STEP0: ######################################################################### 442 | # Sanitize user input and perform some checks before starting the process to avoid 443 | # failure later on eg. dd writing out random data for 3 hours to initialize the 444 | # new container, only to find out that there is not enough space on the device, 445 | # or that the designated device name is already in use on /dev/mapper 446 | # 447 | 448 | # validate container file 449 | if os.path.basename(container_path).strip() == '': 450 | raise WorkerException(_('Container Filename not supplied')) 451 | 452 | # validate device name 453 | if device_name == '': 454 | raise WorkerException(_('Device Name is empty')) 455 | 456 | if device_name[0:1] == '-' or '/' in device_name: 457 | # cryptsetup thinks -isanoption and "/" is not supported by devmapper 458 | raise WorkerException(_('Illegal Device Name!\nNames starting with `-` or using `/` are not possible')) 459 | 460 | if len(bytes(device_name, 'utf-8')) > 16: # ext-labels are the most restricted (max 16Bytes) 461 | # dev-mapper and ntfs-label would support slightly longer strings.. 462 | raise WorkerException(_('Device Name too long:\n' 463 | 'Only up to 16 characters possible, even less for unicode \n' 464 | '(roughly 8 non-ascii characters possible)')) 465 | 466 | if os.path.exists(self.get_device_mapper_name(device_name)): 467 | raise WorkerException(_('Device Name already in use:\n\n{device_name}').format(device_name=device_name)) 468 | 469 | # validate container size 470 | if container_size < 5242880: 471 | raise WorkerException(_('Container size too small\n' 472 | 'to create encrypted filesystem\n' 473 | 'Please choose at least 5MB')) 474 | 475 | container_dir = os.path.dirname(container_path) 476 | 477 | if not os.path.dirname(container_dir): 478 | container_dir = os.path.expanduser('~' + os.getenv("SUDO_USER")) 479 | container_path = os.path.join(container_dir, os.path.basename(container_path)) 480 | 481 | free_space = os.statvfs(container_dir) 482 | free_space = free_space.f_bavail * free_space.f_bsize 483 | if container_size > free_space: 484 | raise WorkerException( 485 | _('Not enough free disc space for container:\n\n' 486 | '{space_needed} MB needed\n{space_available} MB available') 487 | .format(space_needed=str(int(container_size / 1024 / 1024)), 488 | space_available=str(int(free_space / 1024 / 1024))) 489 | ) 490 | 491 | # validate key_file if given 492 | if key_file is not None: 493 | # check access rights to keyfile 494 | if not os.path.exists(key_file) or os.stat(key_file).st_uid != int(os.getenv("SUDO_UID")): 495 | sleep(random.random()) # 0-1s to prevent misuse of exists() 496 | raise WorkerException( 497 | _('Key file not accessible\nor path does not exist:\n\n{file_path}').format(file_path=key_file) 498 | ) 499 | 500 | # validate encryption_format and filesystem 501 | # TODO: exFAT? 502 | if filesystem_type not in ['ext4', 'ext2', 'ntfs']: 503 | raise WorkerException( 504 | _('Unknown filesystem type: {filesystem_type}').format(filesystem_type=str(filesystem_type)) 505 | ) 506 | if enc_format == 'Truecrypt' and not self.is_tc_installed: 507 | raise WorkerException(_('If you want to use TrueCrypt containers\n' 508 | 'make sure `cryptsetup` is at least version 1.6 (`cryptsetup --version`)\n' 509 | 'and `tcplay` is installed (eg for Debian/Ubuntu `apt-get install tcplay`)')) 510 | 511 | # STEP1: ########################################################## 512 | # create container file by filling allocated space with random bits 513 | # 514 | 515 | # runas user to fail on access restictions 516 | if quickformat: 517 | # does not fail if the output file already exists, but check is in setupUI.on_save_file() anyway 518 | cmd = ['sudo', '-u', os.getenv("SUDO_USER"), 519 | 'fallocate', '-x', '-l', str(container_size), container_path] 520 | else: 521 | count = str(int(container_size / 1024 / 1024)) + 'K' 522 | # oflag=excl -> fail if the output file already exists 523 | cmd = ['sudo', '-u', os.getenv("SUDO_USER"), 524 | 'dd', 'if=/dev/urandom', 'of=' + container_path, 525 | 'bs=1K', 'count=' + count, 'conv=excl'] 526 | 527 | with open(os.devnull) as DEVNULL: 528 | p = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=DEVNULL, universal_newlines=True, close_fds=True) 529 | __, errors = p.communicate() 530 | if p.returncode != 0: 531 | # 'sudo -u' might add this -> don't display 532 | raise WorkerException(errors.replace('Sessions still open, not unmounting', '').strip()) 533 | # TODO: this can only work with english locale, 534 | # but this errormessage doesn't seem to be localized in sudo yet .. 535 | # get rid of the problem (strip env?) or remove msg in all languages 536 | 537 | # setup loopback device with created container 538 | try: 539 | reserved_loopback_device = subprocess.check_output( 540 | ['losetup', '-f', '--show', container_path], 541 | stderr=subprocess.PIPE, universal_newlines=True 542 | ).strip() 543 | except subprocess.CalledProcessError as cpe: 544 | raise WorkerException(cpe.output) from cpe 545 | 546 | # STEP2: ###################################################### 547 | # ask user for password and initialize LUKS/TrueCrypt container 548 | # 549 | resp = '' 550 | try: 551 | if key_file is None: 552 | resp = self.communicate('getPassword') 553 | else: 554 | self.communicate('containerDone') 555 | 556 | if enc_format == 'LUKS': 557 | 558 | cmd = ['cryptsetup', 'luksFormat', '--type', 'luks2', '-q', reserved_loopback_device] 559 | if key_file is not None: 560 | cmd += ['--key-file', key_file] 561 | with open(os.devnull) as DEVNULL: 562 | p = subprocess.Popen(cmd, 563 | stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, 564 | universal_newlines=True, close_fds=True) 565 | __, errors = p.communicate(resp) 566 | if p.returncode != 0: 567 | raise WorkerException(errors) 568 | 569 | elif enc_format == 'TrueCrypt': 570 | 571 | with open(os.devnull) as DEVNULL: 572 | # secure erase already done with dd, no need to use tcplay for that 573 | cmd = ['tcplay', '-c', '-d', reserved_loopback_device, '--insecure-erase'] 574 | if key_file is not None: 575 | cmd += ['--keyfile', key_file] 576 | p = subprocess.Popen(cmd, 577 | stdin=subprocess.PIPE, stderr=subprocess.PIPE, stdout=DEVNULL, 578 | universal_newlines=True, close_fds=True) 579 | # tcplay needs the password twice & confirm -> using sleep instead of parsing output 580 | # ugly, but crypt-init takes ages with truecrypt anyways 581 | sleep(1) 582 | p.stdin.write(resp + '\n') 583 | p.stdin.flush() 584 | sleep(1) 585 | p.stdin.write(resp + '\n') 586 | p.stdin.flush() 587 | sleep(1) 588 | p.stdin.write('y\n') # .. until tcplay gets localized :) 589 | p.stdin.flush() 590 | p.stdin.close() 591 | p.stderr.close() 592 | p.wait() 593 | 594 | if p.returncode != 0: 595 | raise WorkerException('TCPLAY ERROR') 596 | 597 | else: 598 | raise WorkerException(_('Unknown encryption format: {enc_fmt}').format(enc_fmt=enc_format)) 599 | self.communicate('formatDone') # signal status 600 | finally: # cleanup loopback device 601 | self.detach_loopback_device(reserved_loopback_device) 602 | 603 | # STEP3: ############################################ 604 | # open encrypted container and format with filesystem 605 | # 606 | pw_callback = lambda: resp 607 | self.unlock_container(device_name=device_name, 608 | container_path=container_path, 609 | key_file=key_file, 610 | pw_callback=pw_callback) 611 | resp = None # get rid of pw 612 | 613 | # fs-root of created ext-filesystem should belong to the user 614 | device_mapper_name = self.get_device_mapper_name(device_name) 615 | if filesystem_type == 'ext4': 616 | cmd = ['mkfs.ext4', '-L', device_name, '-O', '^has_journal', '-m', '0', '-q', device_mapper_name] 617 | elif filesystem_type == 'ext2': 618 | cmd = ['mkfs.ext2', '-L', device_name, '-m', '0', '-q', device_mapper_name] 619 | elif filesystem_type == 'ntfs': 620 | cmd = ['mkfs.ntfs', '-L', device_name, '-Q', '-q', device_mapper_name] 621 | try: 622 | subprocess.check_output(cmd, stderr=subprocess.STDOUT, universal_newlines=True) 623 | except subprocess.CalledProcessError as cpe: 624 | raise WorkerException(cpe.output) from cpe 625 | 626 | # remove group/other read/execute rights from fs root if possible 627 | if filesystem_type != 'ntfs': 628 | tmp_mount = os.path.join('/tmp/', str(uuid4())) 629 | os.mkdir(tmp_mount) 630 | try: 631 | subprocess.check_output( 632 | ['mount', '-o', 'nosuid,nodev', device_mapper_name, tmp_mount], 633 | stderr=subprocess.STDOUT, universal_newlines=True 634 | ) 635 | except subprocess.CalledProcessError as cpe: 636 | raise WorkerException(cpe.output) from cpe 637 | os.chown(tmp_mount, int(os.getenv("SUDO_UID")), int(os.getenv("SUDO_GID"))) 638 | os.chmod(tmp_mount, 0o700) 639 | 640 | self.close_container(device_name, container_path) 641 | 642 | def modify_sudoers(self, user_id, nopassword=False): 643 | """ Adds sudo access to the program (without password) for the current user (/etc/sudoers.d/) 644 | :param user_id: unix user id 645 | :type user_id: int 646 | :returns: should access be granted without password 647 | :rtype: bool 648 | """ 649 | program_path = os.path.abspath(sys.argv[0]) 650 | try: 651 | user_name = pwd.getpwuid(int(user_id))[0] 652 | except KeyError as ke: 653 | raise WorkerException(_('Cannot change sudo rights, invalid username')) from ke 654 | 655 | if any([(os.stat(program_path).st_uid != 0), 656 | (os.stat(program_path).st_gid != 0), 657 | (os.stat(program_path).st_mode & stat.S_IWOTH)]): 658 | 659 | raise WorkerException(_('I`m afraid I can`t do that.\n\n' 660 | 'To be able to permit permanent changes to sudo rights,\n' 661 | 'please make sure the program is owned by root\n' 662 | 'and not writeable by others.\n' 663 | 'Execute the following commands in your shell:\n\n' 664 | 'chmod 755 {program}\n' 665 | 'sudo chown root:root {program}\n\n').format(program=program_path)) 666 | 667 | if not os.path.exists('/etc/sudoers.d'): 668 | os.makedirs('/etc/sudoers.d') 669 | 670 | sudoers_file_path = '/etc/sudoers.d/lucky-luks' 671 | sudoers_file = open(sudoers_file_path, 'a') 672 | sudoers_file.write( 673 | "{username} ALL = (root) {nopasswd}{program}\n".format( 674 | username=user_name, 675 | nopasswd='NOPASSWD: ' if nopassword else '', 676 | program=program_path 677 | ) 678 | ) 679 | sudoers_file.close() 680 | 681 | os.chmod(sudoers_file_path, 0o440) 682 | 683 | def is_LUKS_active(self, device_name): 684 | """ Checks if device is active/unlocked 685 | :param device_name: The device mapper name 686 | :type device_name: str 687 | :returns: True if active LUKS device found 688 | :rtype: bool 689 | """ 690 | with open(os.devnull) as DEVNULL: 691 | returncode = subprocess.call( 692 | ['cryptsetup', 'status', device_name], 693 | stdout=DEVNULL, stderr=subprocess.STDOUT 694 | ) 695 | return returncode == 0 696 | 697 | def detach_loopback_device(self, loopback_device): 698 | """ Detaches given loopback device 699 | :param loopback_device: The loopback device path (eg /dev/loop2) 700 | :type loopback_device: str 701 | """ 702 | with open(os.devnull) as DEVNULL: 703 | subprocess.call(['losetup', '-d', loopback_device], stdout=DEVNULL, stderr=subprocess.STDOUT) 704 | 705 | def get_loopback_device(self, device_name): 706 | """ Returns the corresponding loopback device path to a given device mapper name 707 | :param device_name: The device mapper name 708 | :type device_name: str 709 | :returns: The corresponding loopback device path (eg /dev/loop2) 710 | :rtype: str 711 | """ 712 | return self.get_crypt_status(device_name, 'device:') 713 | 714 | def get_container(self, device_name): 715 | """ Returns the corresponding container path to a given device mapper name 716 | :param device_name: The device mapper name 717 | :type device_name: str 718 | :returns: The corresponding container path 719 | :rtype: str 720 | """ 721 | return self.get_crypt_status(device_name, 'loop:') 722 | 723 | def get_crypt_status(self, device_name, search_property): 724 | """ Parses cryptsetup status output for a device mapper name 725 | and returns either loopback device or container path 726 | :param device_name: The device mapper name 727 | :type device_name: str 728 | :param search_property: The property to return from status output 729 | :type search_property: 'device:' or 'loop:' 730 | :returns: loopback device or container path 731 | :rtype: str 732 | """ 733 | try: 734 | stat_output = subprocess.check_output( 735 | ['cryptsetup', 'status', device_name], 736 | stderr=subprocess.STDOUT, universal_newlines=True 737 | ) 738 | # parsing status output: should be safe, output unchanged in cryptsetup since version 1.3 (2011) 739 | for line in stat_output.split('\n'): 740 | if search_property in line: 741 | return line[line.find('/'):].strip() 742 | return '' 743 | except subprocess.CalledProcessError: 744 | return '' # device not found 745 | 746 | def get_device_mapper_name(self, device_name): 747 | """ Mapping for filesystem access to /dev/mapper/ 748 | Escapes most non alphanumeric and all unicode characters in the device name 749 | 750 | -> from the device-mapper sources: 751 | * Mangle all characters in the input string which are not on a whitelist 752 | * with '\xCC' format, where CC is the hex value of the character. 753 | * Actually, DM supports any character in a device name. 754 | * This whitelist is just for proper integration with udev. 755 | 756 | :param device_name: The device mapper name 757 | :type device_name: str 758 | :returns: The mangled device mapper name for filesystem access 759 | :rtype: str 760 | """ 761 | dm_name = '/dev/mapper/' 762 | # use single-char list for python3 (splitting 2 or more byte utf8-encoded chars..) 763 | for char in [chr(byte) for byte in bytes(device_name, 'utf-8')]: 764 | if any([(char >= '0' and char <= '9'), 765 | (char >= 'A' and char <= 'Z'), 766 | (char >= 'a' and char <= 'z'), 767 | (char in "#+-.:=@_")]): 768 | dm_name += char 769 | else: 770 | dm_name += '\\' + str(hex(ord(char))[1:]) 771 | 772 | return dm_name 773 | --------------------------------------------------------------------------------