Pberndt V4

Direkt zum Inhalt springen


Quellcode pinentry.py

Beschreibung

pyPinentry is a python interface to GnuPGs pinentry tool. pyPinentry ist ein Pythonmodul für den Zugriff auf pinentry (GnuPG).

Sourcecode

"""
    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]

Download

Dateiname
pinentry.py
Größe
7kb