text: #! /usr/bin/env python
  
  import binascii
  import datetime
  import getpass
  import mcrypt
  import os
  import random
  import sqlite3
  import stat
  import struct
  import sys
  import textwrap
  import time
  
  class ANSI(object):
      ESC     = '\x1b['
      # Colors
      RED     = ESC + '1;31m'
      GREEN   = ESC + '1;32m'
      YELLOW  = ESC + '1;33m'
      BLUE    = ESC + '1;34m'
      BOLD    = ESC + '1;37m'
      NORMAL  = ESC + '0m'
      # Cursor
      SAVE    = ESC + 's'
      LOAD    = ESC + 'u'
      UP      = ESC + 'A'
      CLREOL  = ESC + 'K'
  
  class Vault(object):
      STRUCT_ITEM = '<L64p'
      PASSWORD_CHARS = ''.join([
          'abcdefghijklmnopqrstuvwxyz',
          'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
          '0123456789',
      ])
      PASSWORD_CHARS_SPECIAL = '!@#$%^&*()_-+='
  
      def __init__(self, options):
          self.options = options
          self.filename = os.path.expanduser(options.filename)
          if options.keyfile:
              self.secret = file(options.keyfile).read().strip()
          self.check_perms()
          self.check_vault()
  
      # checks
  
      def check_perms(self):
          self.debug('Checking file permissions of %s' % (self.filename,))
  
          if not os.path.isfile(self.filename):
              return
  
          info = os.stat(self.filename)
          ifmt = stat.S_IFMT(info.st_mode)
          good = True
          if ifmt & stat.S_IRGRP or ifmt & stat.S_IROTH:
              self.warn('%s: group or other readable' % (self.filename,))
              good = False
          if ifmt & stat.S_IWGRP or ifmt & stat.S_IWOTH:
              self.warn('%s: group or other writable' % (self.filename,))
              good = False
          if not good:
              self.info('%s: fixed permissions' % (self.filename,))
              os.chmod(self.filename, 0600)
          else:
              self.debug('Permissions OK')
  
      def check_vault(self):
          self.debug('Checking vault status')
          umask = os.umask(0077)
          self.db = sqlite3.connect(self.filename, isolation_level=None)
          os.umask(umask)
          cursor = self.db.cursor()
          try:
              self.db.execute('''
              CREATE TABLE IF NOT EXISTS vault (
                  uuid TEXT
              );
              ''')
              self.db.execute('''
              CREATE TABLE IF NOT EXISTS section (
                  name TEXT
              );
              ''')
              self.db.execute('''
              CREATE TABLE IF NOT EXISTS item (
                  section_name TEXT,
                  name         TEXT,
                  password     TEXT,
                  description  TEXT,
                  date_created FLOAT,
                  date_updated FLOAT
              );
              ''')
              self.db.commit()
          finally:
              cursor.close()
  
      def destroy_vault(self):
          '''
          Remove the vault, this will remove the vault database completely
          and render this instance useless! Be warned!
          '''
          self.debug('Destorying vault file %s' % (self.filename,))
          self.db.close()
          os.unlink(self.filename)
  
      # main stuff
  
      def run(self, args):
          hook = getattr(self, '_'.join(['do', args[0]]), None)
          if hook:
              try:
                  return hook(*args[1:])
              except TypeError:
                  raise
                  return self.do_help(args[0])
          else:
              self.fail('No such command: %s' % (args[0],))
  
      def ask_password(self, prompt):
          sys.stdout.flush()
          password = getpass.getpass(prompt)
          sys.stdout.flush()
          sys.stdout.write(ANSI.UP)
          sys.stdout.write(ANSI.CLREOL)
          sys.stdout.flush()
          return password
  
      def do_help(self, command=None):
          '''
          help [<command>] 
  
          Shows help on the given command, its syntax and its purpose.
          '''
          if not command:
              self.do_help('help')
              sys.stdout.write('\nAvailable commands:\n\n')
              for func in [f.replace('do_', '', 1) for f in dir(self) if f.startswith('do_')]:
                  sys.stdout.write('\t%s\n' % (func,))
              sys.stdout.flush()
              return
  
          hook = getattr(self, '_'.join(['do', command]), None)
          if hook:
              if hook.__doc__:
                  sys.stdout.write('Usage: %s ' % (sys.argv[0],))
                  sys.stdout.write(textwrap.dedent(hook.__doc__.strip('\r\n')))
              else:
                  sys.stdout.write('Usage: %s <args>' % (sys.argv[0],))
                  sys.stdout.write('\nNo more help available\n')
              sys.stdout.flush()
          else:
              self.fail('%s: no such command' % (command,))
  
      def do_del(self, section, name):
          '''
          del <section> <name>
  
          Removes <name> from <section> in the vault.
          '''
          self.check_crypt()
          self.db_del(section, name)
          self.info('Done')
  
      def do_get(self, section, *args):
          '''
          get <section> [<name>]
  
          Get the password(s) for a section. If you specify the name you also get
          the details about the entry.
          '''
          self.check_crypt()
          if args:
              name = args[0]
              select = '''
                  SELECT name, password, description, date_created, date_updated
                    FROM item
                  WHERE section_name = ?
                    AND name = ?;
                  '''
              result = self.db_query_one(select, self.encrypt(section), self.encrypt(name))
              if result:
                  headings = ['name', 'password', 'description', 'date_created', 'date_updated']
                  for x in xrange(0, len(headings)):
                      sys.stdout.write(headings[x].replace('_', ' ').ljust(12, '.'))
                      sys.stdout.write(': ')
                      if headings[x].startswith('date_'):
                          sys.stdout.write(datetime.datetime.utcfromtimestamp(result[x]).strftime('%D %T'))
                      elif x < 3:
                          sys.stdout.write(self.decrypt(result[x]))
                      elif result[x]:
                          sys.stdout.write(result[x])
                      sys.stdout.write('\n')
              else:
                  self.fail('No entries found')
          else:
              select = '''
                  SELECT name, password
                    FROM item
                   WHERE section_name = ?;
                   '''
              result = self.db_query_all(select, self.encrypt(section))
              if result:
                  result = [map(self.decrypt, (name, password)) for name, password in result]
                  result.sort()
                  self.table(result, 'name', 'password', color=0)
              else:
                  self.fail('No entries found')
  
      def do_list(self, section=None):
          '''
          list [<section>]
  
          Show all sections and their entries. If the section is omitted
          all section names will be displayed.
          '''
          self.check_crypt()
          if section:
              select = '''
                  SELECT section_name, name, date_created
                    FROM item
                   WHERE section_name = ?
                  '''
              result = self.db_query_all(select, self.encrypt(section))
              if result:
                  result = [(self.decrypt(section), self.decrypt(name), 
                      datetime.datetime.utcfromtimestamp(date).strftime('%D %T')) \
                      for section, name, date in result]
                  result.sort()
                  self.table(result, 'section', 'name', 'date_created', color=1)
                  return 0
          else:
              select = '''
                  SELECT name
                    FROM section;
                  '''
              result = self.db_query_all(select)
              if result:
                  result = [(self.decrypt(section),) for section, in result]
                  result.sort()
                  self.table(result, 'section', color=0)
                  return 0
          self.fail('No data found')
  
      def do_pwgen(self, length=8, special=0):
          '''
          pwgen [<length>] [<special>]
  
          Generate a secure random password of size <length>, defaults
          to 8 characters. Optionally you can supply the required number
          of special (non alpha-numeric) characters in the password, this
          defaults to 0.
          '''
          try:
              length = int(length)
              special = int(special)
          except ValueError:
              return self.do_help('pwgen')
          else:
              if os.path.isfile('/dev/urandom'):
                  def random_char(chars):
                      return chars[ord(os.urandom(1)) % len(chars)]
              else:
                  def random_char(chars):
                      return random.choice(chars)
  
              password = []
              specials = []
              while len(password) < length:
                  password.append(random_char(self.PASSWORD_CHARS))
  
              while len(specials) < special and len(specials) != len(password):
                  replace = None
                  while replace is None or replace in specials:
                      replace = random.randint(0, len(password) - 1)
                  specials.append(replace)
                  password[replace] = random_char(self.PASSWORD_CHARS_SPECIAL)
  
              password.append('\n')
              sys.stdout.write(''.join(password))
              sys.stdout.flush()
  
      def do_set(self, section, name, *description):
          '''
          set <section> <name> [<description>]
  
          Set the password for <name> in <section>. For example:
  
              set personal weblog
  
          This will add the given item to the database.
          '''
          self.check_crypt()
          self.db_check_section(section)
          p1 = self.ask_password('Password: ')
          if not p1:
              self.fail('No password received, did you mean to use %d del?' % (sys.argv[0],))
          p2 = self.ask_password('Password (confirm): ')
          if p1 != p2:
              self.fail('Password confirmation failed')
          self.db_set(section, name, p1, ' '.join(description))
          self.info('Password set')
  
      # database stuff
  
      def db_query(self, query, *args):
          self.debug('SQL: %s (args=%r)' % (query, args))
          result = self.db.execute(query, args)
          self.debug('SQL: Got %d rows' % (result.rowcount,))
          return result
  
      def db_query_all(self, query, *args):
          return self.db_query(query, *args).fetchall()
  
      def db_query_one(self, query, *args):
          return self.db_query(query, *args).fetchone()
  
      def db_check_section(self, section):
          cursor = self.db.cursor()
          try:
              select = '''
                  SELECT *
                    FROM section
                   WHERE name = ?;
                  '''
              result = self.db.execute(select, (self.encrypt(section),))
              if result.fetchone() is None:
                  insert = '''
                      INSERT INTO section
                          (name)
                      VALUES
                          (?);
                      '''
                  self.db.execute(insert, (self.encrypt(section),))
                  self.db.commit()
                  return False
              else:
                  return True
          finally:
              cursor.close()
  
      def db_del(self, section, name):
          delete = '''
              DELETE FROM item
               WHERE section_name = ?
                 AND name = ?;
              '''
          self.db_query(delete, self.encrypt(section), self.encrypt(name))
          self.db.commit()
  
      def db_set(self, section, name, password, description):
          cursor = self.db.cursor()
          try:
              select = '''
                  SELECT rowid
                    FROM item
                   WHERE section_name = ?
                     AND name = ?;
                  '''
              result = self.db.execute(select, (self.encrypt(section), self.encrypt(name)))
              exists = result.fetchone()
              if exists is None:
                  insert = '''
                      INSERT INTO item
                          (section_name, name, password, description, date_created, date_updated)
                      VALUES
                          (?, ?, ?, ?, ?, ?);
                      '''
                  self.db.execute(insert, (self.encrypt(section), self.encrypt(name), 
                      self.encrypt(password), self.encrypt(description),
                      time.time(), time.time()))
              else:
                  update_id = exists[0]
                  update = '''
                      UPDATE item
                         SET password = ?,
                             date_updated = ?
                       WHERE rowid = ?;
                      '''
                  self.db.execute(update, (self.encrypt(password), time.time(), update_id))
              self.db.commit()
          finally:
              cursor.close()
  
      # crypt stuff
  
      def encrypt(self, data):
          try:
              # We can only store 63 bytes of data (+1 for nullbyte)
              data = str(data)[:63]
              pack = struct.pack(self.STRUCT_ITEM, len(data), data)
              try:
                  null = i = 0
                  while null < 5:
                      null = pack.index('\x00', i) + 1
                      i += 1
                  if null < len(pack):
                      size = len(pack)
                      pack = pack[:null]
                      pads = len(pack)
                      rots = sum(map(ord, pack))
                      for x in xrange(pads, size):
                          pack = pack + self.padding[(x - pads + rots) % len(self.padding)]
              except ValueError:
                  pass
              return self.crypto.encrypt(pack, fixlength=1).encode('base64').replace('\n', '')
          finally:
              self.crypto.reinit()
  
      def decrypt(self, data):
          try:
              try:
                  data = data.decode('base64')
                  pack = self.crypto.decrypt(data, fixlength=1)
                  size, data = struct.unpack(self.STRUCT_ITEM, pack)
                  if len(data) != size:
                      self.debug('Decrypted size differs (%d <> %d)' % (len(data), size))
                      self.fail('Invalid key')
                  else:
                      return data.split('\x00')[0]
              except (binascii.Error, struct.error, SystemError), e:
                  self.debug('Exception: %s' % (str(e),))
                  self.fail('Invalid key')
          finally:
              self.crypto.reinit()
  
      def check_crypt(self):
          if not hasattr(self, 'secret'):
              self.debug('No secret found, asking the user')
              self.secret = self.ask_password('Encryption key: ')
          if not hasattr(self, 'crypto'):
              self.debug('No crypto found, initialising mcrypt')
              self.crypto = mcrypt.MCRYPT('rijndael-256', 'ecb')
              if len(self.secret) < self.crypto.get_key_size():
                  self.crypto.init(self.secret.ljust(self.crypto.get_key_size(), '\x00'))
              else:
                  self.crypto.init(self.secret[self.crypto.get_key_size():])
              # Check the credentials
              self.check_uuid()
  
      def check_uuid(self):
          self.debug('Checking the vault uuid')
          select = '''
              SELECT uuid
                FROM vault;
              '''
          result = self.db_query_one(select)
          if result is None:
              import uuid
              insert = '''
                  INSERT INTO vault
                      (uuid)
                  VALUES
                      (?);
                  '''
              self.padding = ''.join(map(str, [uuid.uuid4(), uuid.uuid4()])).replace('-', '')
              check = self.encrypt(self.padding)
              #cursor.execute(insert, (check,))
              self.db_query(insert, check)
          else:
              self.padding = self.decrypt(result[0])
              self.debug('Got %d bytes of padding data' % (len(self.padding,)))
  
      # help stuff
  
      def echo(self, color, section, message):
          sys.stderr.write(''.join([color, section + ' ', ANSI.NORMAL,
              textwrap.dedent(message), '\n']))
          sys.stderr.flush()
  
      def debug(self, message):
          if self.options.verbose:
              self.echo(ANSI.BLUE, 'DBUG', message)
  
      def info(self, message):
          self.echo(ANSI.YELLOW, 'INFO', message)
  
      def warn(self, message):
          self.echo(ANSI.RED, 'WARN', message)
  
      def fail(self, message):
          self.echo(ANSI.RED, 'FAIL', message)
          sys.exit(1)
  
      def table(self, result, *headings, **kwargs):
          if self.options.machine:
              return self.table_machine(result, *headings, **kwargs)
  
          if 'color' in kwargs:
              color = kwargs.pop('color')
          else:
              color = None
  
              if color:
                  self.debug('Dumping %d rows, colorizing column %d' % (len(result), color))
              else:
                  self.debug('Dumping %d rows' % (len(result),))
  
          # Insert headings
          result.insert(0, [h.title().replace('_', ' ') for h in headings])
  
          # Calculate heading sizes
          sizes = [1] * len(headings)
          for row in result:
              for x in xrange(0, len(sizes)):
                  if len(row[x]) > sizes[x]:
                      sizes[x] = len(row[x])
  
          # Insert heading borders
          result.insert(1, ['-' * sizes[x] for x in xrange(0, len(sizes))])
  
          # Create formatting strings
          sizes = ['%%-%ds' % (size,) for size in sizes]
  
          # Output rows
          for y in xrange(0, len(result)):
              row = result[y]
              for x in xrange(0, len(sizes)):
                  if (y == 0):
                      sys.stdout.write(ANSI.BOLD)
                      sys.stdout.write(sizes[x] % row[x])
                      sys.stdout.write(ANSI.NORMAL)
                  elif (color == x and y != 1):
                      sys.stdout.write(ANSI.YELLOW)
                      sys.stdout.write(sizes[x] % row[x])
                      sys.stdout.write(ANSI.NORMAL)
                  else:
                      sys.stdout.write(sizes[x] % row[x])
                  if (x + 1) < len(sizes):
                      if y == 1:
                          sys.stdout.write('-+-')
                      else:
                          sys.stdout.write(' | ')
              sys.stdout.write('\n')
          sys.stdout.flush()
  
      def table_machine(self, result, *headings, **kwargs):
          for row in [list(headings)] + list(result):
              sys.stdout.write(self.options.machine_sep.join(row))
              sys.stdout.write('\n')
          sys.stdout.flush()
  
  def run():
      import optparse
  
      parser = optparse.OptionParser(usage='%prog [<options>] <command> [<args>]')
      parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
          help='be verbose (default: no)')
      parser.add_option('-f', '--file', dest='filename', default='~/.vault',
          help='vault filename (default: ~/.vault)')
      parser.add_option('-k', '--keyfile', dest='keyfile', default='',
          help='vault keyfile filename (default: no file)')
      parser.add_option('-m', '--machine', dest='machine', action='store_true',
          help='produce machine-parsable output (default: no)')
      parser.add_option('-s', '--machine-sep', dest='machine_sep', default='\t',
          help='machine-parsable output seperator (default: \\t)')
  
      options, args = parser.parse_args()
  
      vault = Vault(options)
      if not options.keyfile and 'VAULT_SECRET' in os.environ:
          vault.secret = os.environ['VAULT_SECRET']
  
      if args:
          return vault.run(args)
      else:
          return vault.run(('help',))
  
  if __name__ == '__main__':
      sys.exit(run())
  
  