pyPinentry is a python interface to GnuPGs pinentry tool. pyPinentry ist ein Pythonmodul für den Zugriff auf pinentry (GnuPG).
"""
pyPinentry
A wrapper module to access GnuPG's pinentry program out of python
Copyright (c) 2008, Phillip Berndt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import os
import popen2
import signal
import sys
import warnings
import threading
"""
This module uses it's own $PATH to prevent compromized pinentry
versions from beeing used.
"""
PINENTRY_PATH = [ "/usr/bin/", "/usr/local/bin/", "/bin/", "/sbin/" ]
class PinentryStartupFailedException(Exception):
"""
Pinentry will throw this exception when pinentry failed to
start.
One could implement a fallback to getpass for that case.
"""
pass
class PinentrySyntaxException(Exception):
"""
This exception means that pinentry behaved strange...
"""
pass
class PinentryException(Exception):
"""
This exception is used if pinentry itself returns an error
message
"""
pass
class Pinentry(object):
"""
Pinentry wrapper class
Every time you create a Pinentry object a pinentry process will
be spawned. Use this class to interact with it.
If you intend to share an instance of this class between
threads always use the high-level functions
askpin/askconfirm/showmessage
"""
_pinentry_process = None
_usage_lock = None
def _readmsg(self):
"""
Read a response from pinentry
"""
msg = ""
while "OK" not in msg and "ERR" not in msg:
msg += self._pinentry_process.fromchild.readline()
return msg
def __init__(self, globalgrab=True, parentwindowid=None):
"""
If you set globalgrab to False, pinentry will not grab
keyboard and mouse in X11.
Use parentwindowid to position the pinentry dialog over
that window.
"""
# Spawn pinentry process
parameters = ""
if not globalgrab:
parameters += "-g "
if parentwindowid:
parameters += "--parent-wid %d" % parentwindowid
path = False
for dir in PINENTRY_PATH:
path = os.path.join(dir, "pinentry")
if os.access(path, os.X_OK):
break
if not path:
warnings.warn("Failed to find pinentry in prefered paths.")
path = "pinentry"
try:
self._pinentry_process = popen2.Popen3("%s %s" % (path, parameters))
except:
raise PinentryStartupFailedException()
if self._readmsg()[0:2] != "OK":
raise PinentryIncompatibleException()
# Set terminal options
self._runcmd("OPTION ttyname=/dev/tty")
if "TERM" in os.environ:
self._runcmd("OPTION ttytype=%s" % os.environ["TERM"])
else:
self._runcmd("OPTION ttytype=vt-100")
if "LC_CTYPE" in os.environ:
self._runcmd("OPTION lc-ctype=%s" % os.environ["LC_CTYPE"])
else:
self._runcmd("OPTION lc-ctype=UTF-8")
# Create a lock for threading
self._usage_lock = threading.Lock()
def __del__(self):
# Exit pinentry
if self._pinentry_process:
self._pinentry_process.tochild.close()
self._pinentry_process.fromchild.close()
try:
os.kill(self._pinentry_process.pid, signal.SIGTERM)
os.kill(self._pinentry_process.pid, signal.SIGKILL)
except:
pass
del self._pinentry_process
def _runcmd(self, command):
"""
Execute a pinentry Assuan command and return it's
output
For internal use.
"""
self._pinentry_process.tochild.write(command + "\n")
self._pinentry_process.tochild.flush()
response = self._readmsg().split()
if response[0] == "ERR":
raise PinentryException(" ".join(response[2:]), int(response[1]))
return response
def setdesc(self, description):
"""
Set the descriptive text to be displayed
This is used for getting pins, confirmations and
displaying messages.
"""
self._runcmd("SETDESC %s" % description)
def setprompt(self, prompt):
"""
Set the prompt to be shown when asking for a pin
"""
self._runcmd("SETPROMPT %s" % prompt)
def setbuttontext(self, oktext="Yes", canceltext="No"):
"""
Set the button texts for windows
"""
self._runcmd("SETOK %s" % oktext)
self._runcmd("SETCANCEL %s" % canceltext)
def seterror(self, errortext):
"""
Set the Error text
"""
self._runcmd("SETERROR %s" % errortext)
def enablequalityindicator(self, tooltip = False):
"""
Enable a passphrase quality indicator
"""
warnings.warn("Quality indicators crash in some pinentry versions. Ignoring request")
return False
self._runcmd("SETQUALITYBAR")
if tooltip:
self._runcmd("SETQUALITYBAR_TT %s" % tooltip)
def askpin(self, description=None, prompt=None, oktext=None, canceltext=None, errortext=None, enablequalityindicator=None):
"""
Ask for a PIN
"""
self._usage_lock.acquire()
if description:
self.setdesc(description)
if prompt:
self.setprompt(prompt)
if oktext or canceltext:
self.setbuttontext(oktext, canceltext)
if errortext:
self.seterror(errortext)
if enablequalityindicator:
self.enablequalityindicator()
response = self._runcmd("GETPIN")
if response[0] != "D" or "OK" not in response:
raise PinentrySyntaxException()
self._usage_lock.release()
return " ".join(response[1:response.index("OK")])
def askconfirm(self, description=None, oktext=None, canceltext=None):
"""
Ask for confirmation
"""
self._usage_lock.acquire()
if description:
self.setdesc(description)
if oktext or canceltext:
self.setbuttontext(oktext, canceltext)
try:
self._runcmd("CONFIRM")
self._usage_lock.release()
return True
except PinentryException:
self._usage_lock.release()
if sys.exc_info()[1].args[1] == 83886194: # Not confirmed
return False
else:
raise sys.exc_info()[1]
def showmessage(self, message=None, buttontext=None):
"""
Show a message dialog
"""
self._usage_lock.acquire()
if message:
self.setdesc(message)
if buttontext:
self.setbuttontext(oktext=buttontext)
self._runcmd("MESSAGE")
self._usage_lock.release()
if __name__ == "__main__":
# Some tests...
test = Pinentry()
try:
passw = test.askpin("Enter a password", "Prompt:", enablequalityindicator=True)
except:
passw = "1234"
try:
if test.askpin("Enter %s" % passw) != passw:
while test.askpin("Enter %s" % passw, errortext="You are too dumb") != passw:
pass
print "Neat"
except PinentryException:
print "Why did u cancel? :("
if test.askconfirm("I will now rm -rf /, ok?", "Maybe", "Yes"):
print "Bashian roulette!"
else:
print "Haha. Fool."
test.showmessage("Ok. Done.", "Merci")
print "Killing object"
i = test._pinentry_process.pid
os.system("ps -p %d" % i)
del test
os.system("ps -p %d" % i)
print "Process test"
test = Pinentry()
pid = os.fork()
try:
print test.askpin("Ask pin in %d" % pid)
except PinentrySyntaxException:
print sys.exc_info()[1]