#!/usr/bin/python
"""A utility to sign all UIDs on a list of PGP keys and PGP/Mime encrypt-email
them to the respective emails."""

# vim:shiftwidth=2:tabstop=2:expandtab:textwidth=80:softtabstop=2:ai:

#
# Copyright (c) 2008 - 2010 Phil Dibowitz (phil@ipom.com)
#
#   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, version 2.
#
# Note that we only import pexpect if -i is specified. In order to do this
# in a relatively clean way (if you just import it it will only be local), we
# use a trick that pylint will complain about. I'm quite alright with that.
#
# TODO:
#   - Offer ability to "pick up where we left off"
#

from email import MIMEBase
from email import MIMEMultipart
from email import MIMEText
from email import quopriMIME
from email.Utils import formatdate
from copy import copy
from optparse import OptionParser, Option, OptionValueError
import getpass
import os
import re
import smtplib
import socket
import subprocess
import sys

VERSION = '2.0.7'
DEBUG_ON = False

MODE_INTERACTIVE = 0
MODE_CACHE_PASSPHRASE = 1
MODE_AGENT = 2

HOME = os.environ.get('HOME')
GNUPGHOME = os.environ.get('GNUPGHOME', os.path.join(HOME, '.gnupg'))
DEFAULT_GPG_PATH = '/usr/bin/gpg'
DEFAULT_KEYRING = os.path.join(GNUPGHOME, 'pubring.gpg')
DEFAULT_TMP_DIR = '/tmp/pius_tmp'
DEFAULT_OUT_DIR = '/tmp/pius_out'
DEFAULT_MAIL_HOST = 'localhost'
DEFAULT_MAIL_PORT = 25

# Note the line with the email address on it below is intentionally
# shorter than the rest to give it space to grow and still be < 80.
DEFAULT_MIME_EMAIL_TEXT = '''Hello,

Attached is a copy of your PGP key (0x%(keyid)s) signed by my key
(0x%(signer)s).

If your key has more than one UID, than this key only has the UID associated
with this email address (%(email)s) signed and you will receive
additional emails containing signatures of the other UIDs at the respective
email addresses.

Please take the attached message and decrypt it and then import it.
Something like this should work:

   gpg --import <file>

(In mutt ctrl-k will do this.)

Then, don't forget to send it to a keyserver:

   gpg --keyserver pool.sks-keyservers.net --send-key %(keyid)s

If you have any questions, let me know.


Generated by PIUS (http://www.phildev.net/pius/).
'''

DEFAULT_NON_MIME_EMAIL_TEXT = '''Hello,

Attached is a copy of your PGP key (0x%(keyid)s) signed by my key
(0x%(signer)s).

If your key has more than one UID, than this key only has the UID associated
with this email address (%(email)s) signed and you will receive
additional emails containing signatures of the other UIDs at the respective
email addresses.

Please take the attached message and decrypt it and then import it.
Something like this should work:

   gpg -d <file> | gpg --import

Then, don't forget to send it to a keyserver:

   gpg --keyserver pool.sks-keyservers.net --send-key %(keyid)s

If you have any questions, let me know.


Generated by PIUS (http://www.phildev.net/pius/).
'''

def debug(line):
  '''Print a line, if debug is on, preceeded with DEBUG:.'''
  if DEBUG_ON:
    print 'DEBUG:', line

def print_default_email(no_mime):
  '''Print the default email that is sent out.'''
  interpolation_dict = {'keyid': '<keyid>', 'signer': '<signer>',
                        'email': '<email>'}
  print 'DEFAULT EMAIL TEXT:\n'
  if not no_mime:
    print DEFAULT_MIME_EMAIL_TEXT % interpolation_dict
  else:
    print DEFAULT_NON_MIME_EMAIL_TEXT % interpolation_dict


class AgentError(Exception):
  '''An exception for when Agent sucks.'''
  pass


class PassphraseError(Exception):
  '''An exception for when a 'good' cached passphrase didn't work.'''
  pass


class NoSelfKeyError(Exception):
  '''An exception for when the user didn't include their own public key in the
  keyring.'''
  pass


class EncryptionKeyError(Exception):
  '''An exception for when a key can't encrypt (no encryption subkey).'''
  pass


class EncryptionUnknownError(Exception):
  '''An exception for NOT the above. Should never happen.'''
  pass


class GpgUnknownError(Exception):
  '''An exception for NOT the above. Should never happen.'''
  pass


class MailSendError(Exception):
  '''An exception for for NOT the above. Should never happen.'''
  pass


class uids_signer(object):
  '''Main class for signing UIDs.'''

  TMP_KEYRING_FILE = 'pius_keyring.gpg'

  GPG_PROMPT = '[GNUPG:] GET_LINE keyedit.prompt'
  GPG_ACK = '[GNUPG:] GOT_IT'
  GPG_ALREADY_SIGNED = '[GNUPG:] ALREADY_SIGNED'
  GPG_CONFIRM = '[GNUPG:] GET_BOOL sign_uid.okay'
  GPG_SAVE = '[GNUPG:] GET_BOOL keyedit.save.okay'
  GPG_ENC_BEG = '[GNUPG:] BEGIN_ENCRYPTION'
  GPG_ENC_END = '[GNUPG:] END_ENCRYPTION'
  GPG_ENC_INV = '[GNUPG:] INV_RECP'
  GPG_KEY_EXP = '[GNUPG:] KEYEXPIRED'
  GPG_SIG_EXP = '[GNUPG:] SIGEXPIRED'
  GPG_USERID = '[GNUPG:] USERID_HINT'
  GPG_NEED_PASS = '[GNUPG:] NEED_PASSPHRASE'
  GPG_GOOD_PASS = '[GNUPG:] GOOD_PASSPHRASE'
  GPG_SIG_BEG = '[GNUPG:] BEGIN_SIGNING'
  GPG_SIG_CREATED = '[GNUPG:] SIG_CREATED'

  def __init__(self, signer, mode, keyring, gpg_path, tmpdir, outdir,
               encrypt_outfiles, mail, verbose, mail_text,
               mail_override, mail_host, mail_port, mail_no_pgp_mime, mail_user,
               mail_tls):
    self.mode = mode
    self.signer = signer
    self.keyring = keyring
    self.gpg = gpg_path
    self.tmpdir = tmpdir
    self.outdir = outdir
    self.encrypt_outfiles = encrypt_outfiles
    self.mail = mail
    self.mail_text = mail_text
    self.mail_override = mail_override
    self.mail_host = mail_host
    self.mail_port = mail_port
    self.mail_no_pgp_mime = mail_no_pgp_mime
    self.mail_tls = mail_tls
    self.mail_user = mail_user
    self.mail_pass = ''
    self.verbose = verbose
    self.passphrase = ''
    self.tmp_keyring = '%s/%s' % (self.tmpdir, uids_signer.TMP_KEYRING_FILE)
    self.gpg_quiet_opts = '-q --no-tty --no-auto-check-trustdb --batch'
    self.gpg_fd_opts = '--command-fd 0 --passphrase-fd 0 --status-fd 1'

  def _outfile_path(self, ofile):
    '''Internal function to take a filename and put it in self.outdir.'''
    return '%s/%s' % (self.outdir, ofile)

  def _tmpfile_path(self, tfile):
    '''Internal function to take a filename and put it in self.tmpdir.'''
    return '%s/%s' % (self.tmpdir, tfile)

  def cleanup(self):
    '''Cleanup all our temp files.'''
    self._clean_files([self.tmp_keyring, ('%s~' % self.tmp_keyring)])

  def _clean_files(self, flist):
    '''Delete a list of files.'''
    for cfile in flist:
      if os.path.exists(cfile):
        os.unlink(cfile)

  def get_all_keyids(self):
    '''Given a keyring, get all the KeyIDs from it.'''
    debug('extracting all keyids from keyring')
    cmd = ('%s --no-default-keyring --keyring %s --no-options --with-colons'
           ' --fingerprint 2>&1' % (self.gpg, self.keyring))
    debug(cmd)
    gpg = os.popen(cmd, 'r')
    pub_re = re.compile('^pub:')
    key_map = {}
    for line in gpg.readlines():
      if not pub_re.search(line):
        continue
      lineparts = line.split(':')
      name = lineparts[9]
      uid = lineparts[4]
      # get the shirt version
      uid = uid[8:16]
      debug('Got id %s' % uid)
      key_map[name] = uid
    gpg.close()

    # sort the list
    keyids = [ i[1] for i in sorted(key_map.items())]
    return keyids

  def check_fingerprint(self, key):
    '''Prompt the user to see if they have verified this fingerprint.'''
    cmd = ('%s %s --no-default-keyring --keyring %s --fingerprint %s'
           % (self.gpg, self.gpg_quiet_opts, self.keyring, key))
    # Note we attach stderr to a pipe so it won't print errors to the terminal
    # which it does even if stderr=None
    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=None, stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE, close_fds=True)
    for line in gpg.stdout.readlines():
      if line != '\n':
        print line.strip()
    gpg.wait()
    retval = gpg.returncode
    if retval != 0:
      print 'WARNING: Keyid %s not valid, skipping.' % key
      return False

    ans = 'y'
    while ans == 'y':
      ans = raw_input('\nHave you verified this user/key, and if so, what level'
                      '\ndo you want to sign at? (0/1/2/3/N/q) [default: N] ')
      if ans == 'y':
        print ('\n"Yes" is no longer a valid answer, please specify a level to'
               ' sign at.')
          
    print
    if ans in ('0', '1', '2', '3'):
      return ans
    elif ans in ('q', 'Q'):
      print 'Dying at user request'
      sys.exit(1)
    return False

  def get_mail_pass(self):
    '''Prompt the user for their passphrase.'''
    self.mail_pass = getpass.getpass('Please enter your mail server password: ')

  def verify_mail_pass(self):
    '''Verify the password we got works for SMTPAUTH.'''
    smtp = smtplib.SMTP(self.mail_host, self.mail_port)
    # NOTE WELL: SECURITY IMPORTANT NOTE!
    # In python 2.6 if you attempt to starttls() and the server doesn't
    # understand an exception is raised. However before that, it just carried on
    # and one could attempt to auth over a plain-text session. This is BAD!
    #
    # So, in order be secure on older pythons we ehlo() and then check the
    # response before attempting startls.
    try:
      smtp.ehlo()
      if not smtp.has_extn('STARTTLS'):
        # Emulate 2.6 behavior
        raise smtplib.SMTPException('Server does not support STARTTLS')
      smtp.starttls()
      # must ehlo after startls
      smtp.ehlo()
      smtp.login(self.mail_user, self.mail_pass)
    except smtplib.SMTPAuthenticationError:
      return False
    except smtplib.SMTPException, msg:
      raise MailSendError, msg
    finally:
      smtp.quit()

    return True

  def get_passphrase(self):
    '''Prompt the user for their passphrase.'''
    self.passphrase = getpass.getpass('Please enter your PGP passphrase: ')

  def verify_passphrase(self):
    '''Verify a passpharse gotten from get_passpharse().'''
    magic_string = 'test1234'
    filename = self._tmpfile_path('pius_tmp')
    filename_enc = self._tmpfile_path('pius_tmp.gpg')
    filename_dec = self._tmpfile_path('pius_tmp2')
    self._clean_files([filename, filename_enc, filename_dec])
    tfile = open(filename, 'w')
    tfile.write(magic_string)
    tfile.close()
    cmd = ('%s %s --always-trust -r %s -e %s >/dev/null 2>/dev/null'
           % (self.gpg, self.gpg_quiet_opts, self.signer, filename))
    debug(cmd)
    gpg = os.popen(cmd, 'r')
    gpg.close()
    cmd = ('%s %s %s --output %s -d %s >/dev/null 2>/dev/null' %
           (self.gpg, self.gpg_quiet_opts, self.gpg_fd_opts, filename_dec,
            filename_enc))
    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)

    debug('Sending passphrase')
    gpg.stdin.write('%s\n' % self.passphrase)

    line = gpg.stdout.read()
    debug('wait()ing on gpg')
    gpg.wait()
    retval = gpg.returncode
    if retval != 0:
      debug('gpg decrypt return code %s' % retval)
      self._clean_files([filename, filename_enc, filename_dec])
      return False

    if not os.path.exists(filename_dec):
      debug('Resulting file %s not found' % filename_dec)
      self._clean_files([filename, filename_enc, filename_dec])
      return False
    tfile = open(filename_dec, 'r')
    line = tfile.readline()
    tfile.close()
    self._clean_files([filename, filename_enc, filename_dec])
    if line == magic_string:
      return True
    debug('File does not contain magic string')
    return False

  def get_uids(self, key):
    '''Get all UIDs on a given key.'''
    cmd = ('%s %s %s --no-default-keyring --keyring %s --no-options'
           ' --with-colons --edit-key %s' %
           (self.gpg, self.gpg_quiet_opts, self.gpg_fd_opts, self.keyring, key))
    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)

    gpg.stdin.write('\n')
    # We want the indexes to start the indexs at 1 (which is what pgp uses)
    # so we fill the 0th entry.
    uids = ['empty']
    unique_files = []
    while True:
      line = gpg.stdout.readline().strip()

      # skip the things we don't care about...
      if not line:
        debug('breaking, EOF')
        break
      if line == uids_signer.GPG_PROMPT:
        debug('got to command prompt')
        break

      # Parse the line...
      debug('Got a line %s' % line)
      fields = line.split(':')

      if 'uid' != fields[0]:
        continue

      status = fields[1]
      uid = fields[9]

      debug('Got UID %s with status %s' % (uid, status))

      # If we can we capture an email address is saved for 
      # emailing off signed keys (not yet implemented), and
      # also for the ID for that UID.
      #
      # If we can't, then we grab what we can and make it the
      # id and blank out the email.
      #
      # For the normal case (have email), we'll be storing each email twice
      # but that's OK since it means that email is *always* a valid email or
      # None and id is *always* a valid identifier
      match = re.search('.* <(.*)>', uid)
      if match:
        email = match.group(1)
        debug('got email %s' % email)
        filename = re.sub('@', '_at_', email)
        filename = '%s__%s' % (key, filename)
        uid = email
      else:
        # but if it doesn't have an email, do the right thing
        email = None
        debug('no email')
        uid = re.sub(' ', '_', uid)
        uid = re.sub('\'', '', uid)
        filename = '%s__%s' % (key, uid)

      if filename in unique_files:
        debug('Filename is a duplicate')
        count = 2
        while True:
          test = '%s_%s' % (filename, count) 
          debug('Trying %s' % test)
          if test not in unique_files:
            debug('%s worked!' % test)
            filename = test
            break
          else:
            count += 1
      else:
        debug('%s isn\'t in %s' % (filename, repr(unique_files)))

      # NOTE: Make sure to append the file BEFORE adding the extension
      #       since that's what we test against above!
      unique_files.append(filename)
      filename += '.asc'
      uids.append({'email': email, 'file': filename, 'status': status,
                   'id': uid})

    debug('quitting')
    # sometimes it wants a save here. I don't know why. We can quit and check
    # for a save prompt, and then hit no, but we have to make sure it's still
    # running or we'll hang. It's just easier to issue a 'save' instead of a
    # quit
    gpg.stdin.write('save\n')
    debug('waiting')
    gpg.wait()

    return uids

  def nuke_working_keyring(self):
    '''Delete our temporariy working keyring.'''
    if os.path.exists(self.tmp_keyring):
      os.unlink(self.tmp_keyring)

  def encrypt_signed_uid(self, key, filename):
    '''Encrypt the file we exported the signed UID to.'''
    (base, ext) = os.path.splitext(filename)
    path = self._outfile_path(filename)
    enc_file = '%s_ENCRYPTED%s' % (base, ext)
    enc_path = self._outfile_path(enc_file)
    if os.path.exists(enc_path):
      os.unlink(enc_path)
    cmd = ('%s %s %s --no-default-keyring --keyring %s --always-trust --armor'
           ' -r %s --output \'%s\' -e \'%s\'' %
           (self.gpg, self.gpg_quiet_opts, self.gpg_fd_opts, self.tmp_keyring,
            key, enc_path, path))
    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)

    # Must send a blank line...
    gpg.stdin.write('\n')
    while True:
      debug('Waiting for response')
      line = gpg.stdout.readline().strip()
      debug('Got %s' % line)
      if uids_signer.GPG_ENC_BEG in line:
        debug('Got GPG_ENC_BEG')
        continue
      elif uids_signer.GPG_ENC_END in line:
        debug('Got GPG_ENC_END')
        break
      elif uids_signer.GPG_ENC_INV in line:
        debug('Got GPG_ENC_INV')
        raise EncryptionKeyError
      elif (uids_signer.GPG_KEY_EXP in line or
            uids_signer.GPG_SIG_EXP in line):
        # These just mean we passed a given key/sig that's expired, there
        # may be ones left that are good. We cannot report an error until
        # we get a ENC_INV.
        debug('Got GPG_KEY_EXP')
        continue
      else:
        raise EncryptionUnknownError, line

    gpg.wait()
    return enc_file

  def _run_and_check_status(self, cmd):
    '''Helper function for running a gpg call that requires no input
    but that we want to make sure succeeded.'''
    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)
    gpg.wait()
    retval = gpg.returncode
    if retval != 0:
      # We don't catch this, but that's fine, if this errors, a stack
      # trace is what we want
      raise GpgUnknownError
    
  def _export_key(self, keyring, key, path):
    '''Internal function used by other export_* functions.'''
    if os.path.exists(path):
      os.unlink(path)
    cmd = ('%s %s --no-default-keyring --keyring %s --armor'
           ' --output \'%s\' --export %s' % (self.gpg, self.gpg_quiet_opts,
                                         keyring, path, key))
    self._run_and_check_status(cmd)

  def export_signed_uid(self, key, filename):
    '''Export the signed UID form working keyring.'''
    path = self._outfile_path(filename)
    debug('exporting %s' % key)
    self._export_key(self.tmp_keyring, key, path)

  def export_clean_key(self, key):
    '''Export clean key from the users' KeyID.'''
    debug('exporting %s' % key)
    # We have to export our own public key as well
    keys_to_export = '%s %s' % (key, self.signer)
    path = self._tmpfile_path('%s.asc' % key)
    self._export_key(self.keyring, keys_to_export, path)

  def clean_clean_key(self, key):
    '''Delete the "clean" unsigned key which we exported temporarily.'''
    path = self._tmpfile_path('%s.asc' % key)
    self._clean_files([path])

  def import_clean_key(self, key):
    '''Import the clean key we expoerted in export_clean_key() to our temp
    keyring.'''
    path = self._tmpfile_path('%s.asc' %  key)
    cmd = ('%s %s --no-default-keyring --keyring %s --import-options'
           ' import-minimal --import %s' % (self.gpg, self.gpg_quiet_opts,
                                            self.tmp_keyring, path))
    self._run_and_check_status(cmd)

  #
  # NOTE:
  #    This is a sucky hack. I may just completely delete it one day. The only
  #    reason it's still here is because agent support is flaky and some people
  #    may not like us storing their passphrase in memory.
  #
  def sign_uid_expect(self, key, index, level):
    '''Sign a UID, using the expect stuff. Interactive mode.'''
    cmd = ('%s --no-default-keyring --keyring %s --default-cert-level %s'
           ' --no-ask-cert-level --edit-key %s'
           % (self.gpg, self.tmp_keyring, level, key))
    debug(cmd)
    gpg = pexpect.spawn(cmd)
    gpg.setecho(False)
    gpg.expect('Command> ')
    debug('Selecting UID')
    gpg.sendline(str(index))
    gpg.expect('Command> ')
    debug('Running sign subcommand')
    gpg.sendline('sign')
    line = gpg.readline()
    if 'already signed' in line:
      print '  UID already signed'
      return False
    # else it's a blank line...

    gpg.expect(re.compile('Really sign.*'))
    debug('Confirming signing')
    gpg.sendline('y')
    # Tell the user how to get out of this, and then drop them into the gpg
    # shell.
    print '\n\nPassing you to gpg for passphrase.'
    print 'Hit ^] after succesfully typing in your passphrase'
    gpg.interact()
    # When we return, we have a Command> prompt that w can't
    # 'expect'... or at least if the user did it right
    print ''
    # Unselect this UID
    debug('unselecting uid')
    debug('Saving key')
    gpg.sendline('save')
    #gpg.close()
    return True

  def gpg_wait_for_string(self, fd, string):
    '''Look for a specific string on the status-fd.'''
    line = ''
    while line not in (string,):
      debug('Waiting for line')
      line = fd.readline().strip()
      debug('got line %s' % line)

  def sign_uid(self, key, index, level):
    '''Sign a single UID of a key.
    
    This can use either cached passpharse or gpg-agent.'''
    agent = ''
    if self.mode == MODE_AGENT:
      agent = '--use-agent'
    keyring = '--no-default-keyring --keyring %s' % self.tmp_keyring
    # Note that if passphrase-fd is different from command-fd, nothing works.
    cmd = ('%s %s %s %s -u %s %s --default-cert-level %s --no-ask-cert-level'
           ' --edit-key %s' %
           (self.gpg, self.gpg_quiet_opts, self.gpg_fd_opts, keyring,
            self.signer, agent, level, key))

    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)

    if self.mode == MODE_AGENT:
      # For some reason when using agent an initial enter is needed
      gpg.stdin.write('\n')
    else:
      # For some unidentified reason you must send the passphrase
      # first, not when it asks for it.
      debug('Sending passphrase')
      gpg.stdin.write('%s\n' % self.passphrase)


    debug('Waiting for prompt')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_PROMPT)
    debug('Selecting UID %d' % index)
    gpg.stdin.write('%s\n' % str(index))
    debug('Waiting for ack')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_ACK)

    debug('Running sign subcommand')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_PROMPT)
    debug('Sending sign command')
    gpg.stdin.write('sign\n')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_ACK)

    while True:
      debug('Waiting for response')
      line = gpg.stdout.readline()
      debug('Got %s' % line)
      if uids_signer.GPG_ALREADY_SIGNED in line:
        print '  UID already signed'
        gpg.stdin.write('quit\n')
        return False
      elif (uids_signer.GPG_KEY_EXP in line or
            uids_signer.GPG_SIG_EXP in line):
        # The user has an expired signing or encryption key, keep going
        debug('Got GPG_KEY/SIG_EXP')
        continue
      elif uids_signer.GPG_PROMPT in line:
        # Unfortunately PGP doesn't give us anything parsable in this case. It
        # just gives us another prompt. We give the most likely problem. Best we
        # can do.
        print ('  ERROR: GnuPG won\'t let us sign, this probably means it'
               ' can\'t find a secret key, which most likely means that the'
               ' keyring you are using doesn\'t have _your_ _public_ key on'
               ' it.')
        gpg.stdin.write('quit\n')
        raise NoSelfKeyError
      elif uids_signer.GPG_CONFIRM in line:
        # This is what we want
        break
      else:
        print '  ERROR: GnuPG reported an unknown error'
        gpg.stdin.write('quit\n')
        # Don't raise an exception, it's not probably just this UID...
        return False

    debug('Confirming signing')
    gpg.stdin.write('Y\n')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_ACK)

    #
    # gpg-agent doesn't always work as well as we like. Of the problems:
    #  * It can't always pop up an X window reliably (pinentry problems)
    #  * It doesn't seem able to figure out the best pinetry program
    #    to use in many situations
    #  * Sometimes it silently fails in odd ways
    #
    # So this chunk of code will follow gpg through as many tries as gpg-agent
    # is willing to give and then inform the user of an error and raise an
    # exception.
    #
    # Since we're here, we also handle the highly unlikely case where the
    # verified cached passphrase doesn't work.
    #
    while True:
      line = gpg.stdout.readline()
      debug('Got %s' % line)
      if 'BAD_PASSPHRASE' in line:
        if self.mode == MODE_AGENT:
          line = gpg.stdout.readline()
          debug('Got %s' % line)
          if 'USERID_HINT' in line:
            continue
          print '  ERROR: Agent didn\'t provide passphrase to PGP.'
          raise AgentError
        else:
          print '  ERROR: GPG didn\'t accept the passphrase.'
          raise PassphraseError
      if 'GOOD_PASSPHRASE' in line:
        break

    debug('Saving key')
    self.gpg_wait_for_string(gpg.stdout, uids_signer.GPG_PROMPT)
    gpg.stdin.write('save\n')

    gpg.wait()
    return True

  def print_filenames(self, uids):
    '''Print the filenames we created for the user.'''
    print '  Signed UNencrypted keys: '
    for index in range (1, len(uids)):
      if uids[index]['status'] != 'r' and uids[index]['result']:
        print '    %(id)s: %(file)s' % uids[index]
    if self.encrypt_outfiles:
      print '  Signed encrypted keys: '
      for index in range (1, len(uids)):
        if uids[index]['status'] != 'r' and uids[index]['result']:
          print '    %(id)s: %(enc_file)s' % uids[index]

  def sign_all_uids(self, key, level):
    '''The main function that signs all the UIDs on a given key.'''
    uids = self.get_uids(key)
    print '  There are %s UIDs on this key to sign' % (len(uids) - 1)
    # From the user key ring make a clean copy
    self.export_clean_key(key)
    for index in range(1, len(uids)):
      if uids[index]['status'] == 'r':
        print '  Skipping revoked uid %s' % index
        continue
      sys.stdout.write('  UID %s (%s): ' % (index, uids[index]['id']))

      # Make sure we have a clean keyring, and then import the key we care
      # about
      self.nuke_working_keyring()
      self.import_clean_key(key)

      # Sign the key...
      if self.mode in (MODE_CACHE_PASSPHRASE, MODE_AGENT):
        try:
          res = self.sign_uid(key, index, level)
        except AgentError:
          print '\ngpg-agent problems, bailing out!'
          sys.exit(1)
        except PassphraseError:
          print ('\nThe passphrase that worked a moment ago now doesn\'t work.'
                 ' I\'m bailing out!')
          sys.exit(1)
        except NoSelfKeyError:
          # No need to say anything else
          sys.exit(1)
      else:
        res = self.sign_uid_expect(key, index, level)
      if not res:
        uids[index]['result'] = False
        continue
      sys.stdout.write('signed')
      uids[index]['result'] = True

      # Export the signed key...
      self.export_signed_uid(key, uids[index]['file'])

      # If requested, encrypt the signed key...
      if self.encrypt_outfiles:
        try:
          uids[index]['enc_file'] = self.encrypt_signed_uid(key,
                                                            uids[index]['file'])
          sys.stdout.write(', encrypted')
        except EncryptionKeyError:
          print ('\nEncryption failed due to invalid key error. User may not'
                 ' have an encryption subkey or it may be expired.')
          uids[index]['enc_file'] = None 
          # If we can't encrypt, we don't want to mail - even if we're using
          # PGP/Mime the encryption for that will also fail. So we move on to
          # the next key
          continue

      # If requested, send keys out. Note this doesn't depend on
      # encrypt_outfiles, because if we use PGP/Mime, the default, the email
      # itself is encrypted
      if self.mail:
        try:
          if uids[index]['email'] == None:
            print ('  WARNING: No email for %s, cannot send key.'
                   % uids[index]['id'])
            continue
          self.send_mail(uids[index]['email'], key, uids[index])
          sys.stdout.write(', mailed')
        except MailSendError, msg:
          print '\nFailed to send mail: %s' % (msg)

      # add a newline to all the sys.stdout.write()s
      print ''

    # remote the signed file, if it exists (it might not, if it's
    # expired, the user chose not to sign it, etc.)
    if os.path.exists(self._outfile_path(uids[index]['file'])):
      os.unlink(self._outfile_path(uids[index]['file']))

    if self.verbose:
      self.print_filenames(uids)

    # Remove the clean keyfile we temporarily created
    self.clean_clean_key(key)

  def import_unsigned_keys(self):
    '''Import all the unsigned keys from keyring to main keyring.'''
    print 'Importing keyring...'
    cmd = ('%s %s --import %s' %
           (self.gpg, self.gpg_quiet_opts, self.keyring))
    self._run_and_check_status(cmd)

  def encrypt_and_sign_file(self, infile, outfile, keyid):
    '''Encrypt and sign a file.

    Used for PGP/Mime email generation.'''
    agent = ''
    if self.mode == MODE_AGENT:
      agent = '--use-agent'
    keyring = '--no-default-keyring --keyring %s' % self.tmp_keyring
    cmd = ('%s %s %s %s %s --always-trust -u %s -aes -r %s -r %s --output %s %s'
           % (self.gpg, self.gpg_quiet_opts, self.gpg_fd_opts, keyring,
              agent, self.signer, keyid, self.signer, outfile, infile))

    debug(cmd)
    gpg = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                           stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE,
                           close_fds=True)

    if self.mode == MODE_AGENT:
      # For some reason when using agent an initial enter is needed
      gpg.stdin.write('\n')
    else:
      # For some unidentified reason you must send the passphrase
      # first, not when it asks for it.
      debug('Sending passphrase')
      gpg.stdin.write('%s\n' % self.passphrase)

    while True:
      debug('Waiting for response')
      line = gpg.stdout.readline().strip()
      debug('Got %s' % line)
      if uids_signer.GPG_ENC_BEG in line:
        debug('Got GPG_ENC_BEG')
        continue
      elif uids_signer.GPG_ENC_END in line:
        debug('Got GPG_ENC_END')
        break
      elif uids_signer.GPG_ENC_INV in line:
        debug('Got GPG_ENC_INV')
        raise EncryptionKeyError
      elif (uids_signer.GPG_KEY_EXP in line or
            uids_signer.GPG_SIG_EXP in line):
        # These just mean we passed a given key/sig that's expired, there
        # may be ones left that are good. We cannot report an error until
        # we get a ENC_INV.
        debug('Got GPG_KEY/SIG_EXP')
        continue
      elif (uids_signer.GPG_USERID in line or
            uids_signer.GPG_NEED_PASS in line or
            uids_signer.GPG_GOOD_PASS in line or
            uids_signer.GPG_SIG_BEG in line or
            uids_signer.GPG_SIG_CREATED in line):
        debug('Got skippable stuff')
        continue
      else:
        raise EncryptionUnknownError, line

    gpg.wait()
    retval = gpg.returncode
    if retval != 0:
      raise EncryptionUnknownError, "Return code was %s" % retval

  def _get_email_body(self, keyid, email):
    '''Helper function to grab the right email body.'''
    interpolation_dict = {'keyid': keyid, 'signer': self.signer, 'email': email}
    if self.mail_text:
      return open(self.mail_text, 'r').read() % interpolation_dict
    else:
      if self.mail_no_pgp_mime:
        return DEFAULT_NON_MIME_EMAIL_TEXT % interpolation_dict
      else:
        return DEFAULT_MIME_EMAIL_TEXT % interpolation_dict

  def _generate_pgp_mime_email(self, email, keyid, filename):
    '''Generates the PGP/Mime body.

    The message headers MUST be added by the caller.'''

    msg = MIMEMultipart.MIMEMultipart('encrypted', micalg="pgp-sha1",
        protocol="application/pgp-encrypted")
    msg.preamble = 'This is an OpenPGP/MIME signed message (RFC 2440 and 3156)'

    # The signed part of the message. This is a MIME encapsulation
    # of the main body of the message *and* the key.
    encrypted_body = MIMEMultipart.MIMEMultipart('mixed')

    # First part of signed body
    textpart = MIMEBase.MIMEBase('text', 'plain', charset="ISO-8859-1")
    textpart.add_header('Content-Transfer-Encoding', 'quoted-printable')
    textpart.__delitem__('MIME-Version')
    textpart.set_payload(quopriMIME.encode(self._get_email_body(keyid, email)))
    encrypted_body.attach(textpart)

    # The second part of the signed body
    filepath = self._outfile_path(filename)
    attached_sig = MIMEBase.MIMEBase('application', 'pgp-keys', name='%s' % filename)
    attached_sig.add_header('Content-Disposition', 'inline',
                            filename='%s' % filename)
    attached_sig.__delitem__('MIME-Version')
    #
    # We USED to make this quoted-printable, but people with non-PGP-aware MUAs
    # were decrypting the body manually, and then trying to import the resulting
    # MIME message which was QP-encoded... so if there was an equals-sign in the
    # message, it would actually be an '=3D' and thus fail the import.
    #
    # RFC2015 strongly suggests using QP for any signed data to prevent MTAs
    # from messing with it... however, since this gets encrypted, this data is
    # never available for an MTA to mess with, so this ... should be safe, and
    # allows both methods of decrypting and importing the key.
    #
    # Side-note, if we ever turn to QP, be sure to use quopriMIME.encode to
    # encode the payload.
    #
    attached_sig.set_payload(open(filepath, 'r').read())
    encrypted_body.attach(attached_sig)

    encrypted_body.__delitem__('MIME-Version')

    # Encryt/Sign the MIME body.
    #
    # We have to conver to DOS newlines since that's what happens
    # to mail anyway and we don't want verification to fail
    dos_body = encrypted_body.as_string().replace('\n', '\r\n')
    tmpfile = self._tmpfile_path('pius_tmp')
    signed_tmpfile = '%s.asc' % tmpfile
    self._clean_files([tmpfile, signed_tmpfile])
    tfile = open(tmpfile, 'w')
    tfile.write(dos_body)
    tfile.close()
    try:
      self.encrypt_and_sign_file(tmpfile, signed_tmpfile, keyid)
    except EncryptionKeyError:
      raise EncryptionKeyError

    # Create the version part of the PGP/Mime encryption
    pgp_ver = MIMEBase.MIMEBase('application', 'pgp-encrypted')
    pgp_ver.add_header('Content-Description', 'PGP/MIME version identification')
    pgp_ver.__delitem__('MIME-Version')
    pgp_ver.set_payload('Version: 1\n')

    # Create the big sign-encrypted body part
    pgp_data = MIMEBase.MIMEBase('application', 'octet-stream',
                                 name='encrypted.asc')
    pgp_data.add_header('Content-Description', 'OpenPGP encrypted message')
    pgp_data.add_header('Content-Disposition', 'inline',
                        filename='encrypted.asc')
    pgp_data.__delitem__('MIME-Version')
    pgp_data.set_payload(open(signed_tmpfile, 'r').read())

    # This is the actual encrypt-signed PGP/Mime message
    msg.attach(pgp_ver)
    msg.attach(pgp_data)

    self._clean_files([tmpfile, signed_tmpfile])
    return msg

  def _generate_non_pgp_mime_email(self, email, keyid, filename):
    '''Send the encrypted uid off to the user.'''
    msg = MIMEMultipart.MIMEMultipart()
    msg.epilogue = ''

    part = MIMEText.MIMEText(self._get_email_body(keyid, email))
    msg.attach(part)

    part = MIMEBase.MIMEBase('application', 'octet-stream')
    part.add_header('Content-Disposition','inline; filename="%s"' %
                    filename)
    filepath = self._outfile_path(filename)
    part.set_payload(open(filepath, 'r').read())
    msg.attach(part)
    return msg

  def send_mail(self, email, keyid, uid_data):
    '''Send the encrypted uid off to the user.'''
    try:
      if self.mail_no_pgp_mime:
        msg = self._generate_non_pgp_mime_email(email, keyid,
                                                uid_data['enc_file'])
      else:
        msg = self._generate_pgp_mime_email(email, keyid, uid_data['file'])
    except EncryptionKeyError:
      msg = ('Failed to generate the email to the user. This is most'
             ' likely due to the user having no encryption subkey.')
      raise MailSendError, msg

    # We don't duplicate the header logic in the sub functions, we
    # do that here
    msg['From'] = self.mail
    if self.mail_override:
      msg['To'] = self.mail_override
    else:
      msg['To'] = email
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = 'Your signed PGP key'

    try:
      smtp = smtplib.SMTP(self.mail_host, self.mail_port)
      if self.mail_tls:
        # NOTE WELL: SECURITY IMPORTANT NOTE!
        # In python 2.6 if you attempt to starttls() and the server doesn't
        # understand an exception is raised. However before that, it just
        # carried on # and one could attempt to auth over a plain-text session.
        # This is BAD!
        #
        # So, in order be secure on older pythons we ehlo() and then check the
        # response before attempting startls.
        smtp.ehlo()
        if not smtp.has_extn('STARTTLS'):
          # Emulate 2.6 behavior
          raise smtplib.SMTPException('Server does not support STARTTLS')
        smtp.starttls()
        # must re-ehlo after STARTTLS
        smtp.ehlo()
        # Don't want to send auth information unless we're TLS'd
        if self.mail_user:
          smtp.login(self.mail_user, self.mail_pass)
      if self.mail_override:
        env_to = self.mail_override
      else:
        # BCC the user...
        env_to = [email, self.mail]

      smtp.sendmail(self.mail, env_to, msg.as_string())
      smtp.quit()
    except smtplib.SMTPException, msg:
      raise MailSendError, msg
    except socket.error, msg:
      raise MailSendError, msg
      
# END class uids_signer

#
# Stupid fucking optparse will assume "-m -e" means "-e is the email address
# being passed to -m"... instead of "oh, -e is an option, -m is missing it's
# required argument. This is an ugly hack around that.
#
def check_not_another_opt(option, opt, value):
  '''Ensure argument to an option isn't another option.'''
  match = re.search('^\-', value)
  if match:
    raise OptionValueError('Option %s: Value %s looks like another option'
                           ' instead of the required argument' % (opt, value))
  return value

def check_email(option, opt, value):
  '''Ensure argument seems like an email address.'''
  match = re.match('.+@.+\..+', value)
  if not match:
    raise OptionValueError('Option %s: Value %s does not appear like a well'
                           ' formed email address' % (opt, value))
  return value

def check_keyid(option, opt, value):
  '''Ensure argument seems like a keyid.'''
  match = re.match('[0-9a-fA-Fx]', value)
  if not match:
    raise OptionValueError('Option %s: Value %s does not appear to be a KeyID'
                           % (opt, value))
  return value


class MyOption(Option):
  '''Our own option class.'''
  TYPES = Option.TYPES + ('not_another_opt', 'email', 'keyid')
  TYPE_CHECKER = copy(Option.TYPE_CHECKER)
  TYPE_CHECKER.update({'not_another_opt': check_not_another_opt,
                       'email': check_email,
                       'keyid': check_keyid})
# END Stupid python optparse hack.


def check_options(parser, options, args):
  '''Given the parsed options, sanity check them.'''
  global DEBUG_ON

  if options.debug == True:
    print 'Setting debug'
    DEBUG_ON = True

  if not os.path.exists(options.gpg_path):
    parser.error('GnuPG binary not found at %s.' % options.gpg_path)

  if not options.signer:
    parser.error('You must specify a keyid to sign with.')

  if options.keyring:
    options.keyring = os.path.expanduser(options.keyring)
    if not os.path.exists(options.keyring):
      parser.error('Keyring %s doesn\'t exist' % options.keyring)

  if not options.all_keys:
    if not args:
      parser.error('Keyid required')
  elif not options.keyring:
    parser.error('The -A options requires the -r option')

  if options.mode == MODE_INTERACTIVE:
    try:
      global pexpect
      import pexpect
    except ImportError:
      parser.error('You chose interactive mode but do not have the pexpect'
                   ' module installed.')

  if options.mail and options.mail_no_pgp_mime and not options.encrypt_outfiles:
    print 'NOTE: -O and -m are present, turning on -e'
    options.encrypt_outfiles = True

  if options.mail_user and not options.mail_tls:
    print 'NOTE: -u is present, turning on -S.'
    options.mail_tls = True

  if options.mail_text and not options.mail:
    parser.error('ERROR: -M requires -m')

  for mydir in (options.tmp_dir, options.out_dir):
    if os.path.exists(mydir) and not os.path.isdir(mydir):
      parser.error('%s exists but isn\'t a directory. It must not exist or be\n'
                   'a directory.' % mydir)
    if not os.path.exists(mydir):
      os.mkdir(mydir, 0700)

def main():
  """Main."""
  usage = ('%prog [options] -s <signer_keyid> <keyid> [<keyid> ...]\n'
           '       %prog [options] -A -r <keyring_path> -s <signer_keyid>')
  parser = OptionParser(usage=usage, version='%%prog %s' % VERSION,
                        option_class=MyOption)
  parser.set_defaults(mode=MODE_CACHE_PASSPHRASE,
                      gpg_path=DEFAULT_GPG_PATH,
                      out_dir=DEFAULT_OUT_DIR,
                      tmp_dir=DEFAULT_TMP_DIR,
                      keyring=DEFAULT_KEYRING,
                      mail_host=DEFAULT_MAIL_HOST,
                      mail_port=DEFAULT_MAIL_PORT)
  parser.add_option('-a', '--use-agent', action='store_const', const=MODE_AGENT,
                    dest='mode',
                    help='Use pgp-agent instead of letting gpg prompt the'
                         ' user for every UID. [default: false]')
  parser.add_option('-A', '--all-keys', action='store_true', dest='all_keys',
                    help='Sign all keys on the keyring. Requires -r.')
  parser.add_option('-b', '--gpg-path', dest='gpg_path', metavar='PATH',
                    nargs=1, type="not_another_opt",
                    help='Path to gpg binary. [default: %default]')
  parser.add_option('-e', '--encrypt-outfiles', action='store_true',
                    dest='encrypt_outfiles',
                    help='Encrypt output files with respective keys.')
  parser.add_option('-d', '--debug', action='store_true', dest='debug',
                    help='Enable debugging output.')
  parser.add_option('-H', '--mail-host', dest='mail_host', metavar='HOSTNAME',
                    nargs=1, type='not_another_opt',
                    help='Hostname of SMTP server. [default: %default]')
  parser.add_option('-i', '--interactive', action='store_const',
                    const=MODE_INTERACTIVE, dest='mode', help='Use the pexpect'
                      ' module for signing and drop to the gpg shell for'
                      ' entering the passphrase. [default: false]')
  parser.add_option('-I', '--import', action='store_true',
                    dest='import_keyring',
                    help='Also import the unsigned keys from the keyring'
                         ' into the default keyring. Ignored if -r is not'
                         ' specified, or if it\'s the same as the default'
                         ' keyring.')
  parser.add_option('-m', '--mail', dest='mail', metavar='EMAIL', nargs=1,
                    type='email',
                    help='Email the encrypted, signed keys to the'
                          ' respective email addresses. EMAIL is the address'
                          ' to send from. See also -H and -p.')
  parser.add_option('-M', '--mail-text', dest='mail_text', metavar='FILE',
                    nargs=1, type='not_another_opt',
                    help='Use the text in FILE as the body of email when'
                          ' sending out emails instead of the default text.'
                          ' To see the default text use'
                          ' --print-default-email. Requires -m.')
  parser.add_option('-n', '--override-email', dest='mail_override',
                    metavar='EMAIL', nargs=1, type='email',
                    help='Rather than send to the user, send to this address.'
                         ' Mostly useful for debugging.')
  parser.add_option('-o', '--out-dir', dest='out_dir', metavar='OUTDIR',
                    nargs=1, type='not_another_opt',
                    help='Directory to put signed keys in. [default: %default]')
  parser.add_option('-O', '--no-pgp-mime', action='store_true',
                    dest='mail_no_pgp_mime',
                    help='Do not use PGP/Mime when sending email.')
  parser.add_option('-p', '--cache-passphrase', action='store_const',
                    const=MODE_CACHE_PASSPHRASE, dest='mode',
                    help='Cache private key passphrase in memory and provide'
                         ' it to gpg instead of letting gpg prompt the user'
                         ' for every UID. [default: true]')
  parser.add_option('-P', '--mail-port', dest='mail_port', metavar='PORT',
                    nargs=1, type='int',
                    help='Port of SMTP server. [default: %default]')
  parser.add_option('-r', '--keyring', dest='keyring', metavar='KEYRING',
                    nargs=1, type='not_another_opt',
                    help='The keyring to use. Be sure to specify full or'
                         ' relative path. Just a filename will cause GPG to'
                         ' assume relative to ~/.gnupg. [default: %default]')
  parser.add_option('-s', '--signer', dest='signer', nargs=1,
                    type='keyid',
                    help='The keyid to sign with (required).')
  parser.add_option('-S', '--mail-tls', action='store_true', dest='mail_tls',
                    help='Use STARTTLS when talking to the SMTP server.')
  parser.add_option('-t', '--tmp-dir', dest='tmp_dir', nargs=1,
                    type='not_another_opt',
                    help='Directory to put temporary stuff in. [default:'
                         ' %default]')
  parser.add_option('-T', '--print-default-email', dest='print_default_email',
                    action='store_true', help='Print the default email.')
  parser.add_option('-u', '--mail-user', dest='mail_user', metavar='USER',
                    type='not_another_opt', nargs=1,
                    help='Authenticate to the SMTP server, and use username'
                         ' USER. You will be prompted for the password. Implies'
                         ' -S.')
  parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                    help='Be more verbose.')

  # Check for extra options in the ~/.pius file
  opts_file = os.path.join(HOME, '.pius')
  all_opts = []
  sep = re.compile(r'(?:\s*=\s*|\s*:\s*\s+)')
  try:
    fp = open(opts_file, 'r')
    for line in fp:
      parts = sep.split(line.strip())
      if not parts[0].startswith('--'):
        parts[0] = '--%s' % parts[0]
      if parser.has_option(parts[0]):
        all_opts.extend(parts)
    fp.close()
  except IOError:
    # File doesn't exist or is broken
    pass
  # Note that by putting this at the end we allow the command line to override
  # options specified in the config file, BUT if any options conflict, the first
  # wins, so the config file wins. Meh.
  all_opts.extend(sys.argv[1:])
  (options, args) = parser.parse_args(all_opts)

  print 'Welcome to PIUS, the PGP Individual UID Signer.\n'

  # The easy thing first...
  if options.print_default_email:
    print_default_email(options.mail_no_pgp_mime)
    sys.exit(0)

  # Check input to make sure users want sane things
  check_options(parser, options, args)

  # Check to see if the user wants to send email if they didn't specify
  if not options.mail:
    ans = raw_input('Would you like to automatically send the signed UIDs to'
                    ' their owners using\nPGP/Mime encryption as you sign each'
                    ' one? ')
    if ans in ('y', 'Y', 'yes', 'YES', 'Yes'):
      ans = raw_input('What email address should we send from? ')
      check_email(parser, '-m', ans)
      options.mail = ans
      print
 
  signer = uids_signer(options.signer, options.mode, options.keyring,
                       options.gpg_path, options.tmp_dir, options.out_dir,
                       options.encrypt_outfiles, options.mail, options.verbose,
                       options.mail_text, options.mail_override,
                       options.mail_host, options.mail_port,
                       options.mail_no_pgp_mime, options.mail_user,
                       options.mail_tls)

  if options.all_keys:
    key_list = signer.get_all_keyids()
    if len(key_list) == 0:
      print "Failed to find keys on this keyring\n"
      sys.exit(1)
    if args:
      key_list.extend(args)
  else:
    key_list = args

  if options.mode == MODE_CACHE_PASSPHRASE:
    print ('NOTE: See the README about security implications.')
    while True:
      signer.get_passphrase()
      if not signer.verify_passphrase():
        print 'Sorry, cannot unlock the key with that passphrase, try again.'
      else:
        break

  if options.mail_user:
    while True:
      signer.get_mail_pass()
      try:
        if not signer.verify_mail_pass():
          print ('Sorry, cannot authenticate to %s as %s with that passwword,'
                 ' try again.' % (signer.mail_host, signer.mail_user))
        else:
          break
      except MailSendError, msg:
        print ('There was a problem talking to the mail server (%s): %s'
               % (signer.mail_host, msg))
        sys.exit(1)

  # The actual signing
  for key in key_list:
    retval = signer.check_fingerprint(key)
    if retval == False:
      continue
    print 'Signing all UIDs on key %s' % key
    signer.sign_all_uids(key, retval)
    print ''

  # If the user asked, import the keys
  if options.import_keyring:
    if ((not options.keyring) or (options.keyring == DEFAULT_KEYRING)):
      print ('WARNING: Ignoring -i: Either -r wasn\'t specified, or it was'
             ' the same as the default keyring.')
    else:
      signer.import_unsigned_keys()

  signer.cleanup()

if __name__ == '__main__':
  main()
