# Copyright 2002-2003 Nick Mathewson.  See LICENSE for licensing information.
# $Id: DirMain.py,v 1.21 2005/06/04 13:55:04 nickm Exp $

"""mixminion.directory.DirMain

   CLI for mixminion directory generation.
"""

__all__ = [ ]

import gzip
import os
import shutil
import sys
import time
from mixminion.Common import createPrivateDir, formatTime, iterFileLines, LOG, \
     UIError
from mixminion.Config import ConfigError
from mixminion.Crypto import init_crypto, pk_fingerprint, pk_generate, \
     pk_PEM_load, pk_PEM_save
from mixminion.directory.Directory import Directory, DirectoryConfig

USAGE = """\
Usage: mixminion dir <command>
   Where 'command' is one of:
      initialize               [Set up a new set of directory files]
      import-new <nickname>    [Import a descriptor for a new server]
      list                     [List descriptors waiting to be imported]
      update                   [Process updates for currently known servers]
      generate                 [Generate and sign a new directory]
      fingerprint              [Return the fingerprint of this directory's pk]
      rebuildcache             [Rebuild a corrupted or removed identity cache]
""".strip()

def getDirectory():
    """Return the Directory object for this directory.  Looks for a
       configuration file first in $MINION_DIR_CONF, then in
       ~/.mixminion_dir.cf, then in /etc/mixminion_dir.cf.
    """
    fn = os.environ.get('MINION_DIR_CONF')
    if not fn:
        fn = os.path.expanduser("~/.mixminion_dir.cf")
        if not os.path.exists(fn):
            fn = None
    if not fn:
        fn = "/etc/mixminion_dir.cf"
        if not os.path.exists(fn):
            fn = None
    if not fn:
        raise UIError("No configuration file found")

    try:
        config = DirectoryConfig(filename=fn)
    except ConfigError, e:
        raise UIError("Error in %s: %s"%(fn, e))

    return Directory(config)

def usageAndExit():
    """Print a usage message and exit"""
    print USAGE
    sys.exit(0)

def cmd_init(args):
    """[Entry point] Set up a new set of directory files."""
    if args:
        raise UIError("mixminion dir initialize takes no arguments")

    d = getDirectory()
    d.setupDirectories()
    d.getServerList()
    d.getInbox()

def cmd_update(args):
    """[Entry point] Process updates for currently known servers: copies
       descriptors from the Inbox to ServerList.  This can be run automatically
       as part of a cron job."""
    if args:
        raise UIError("mixminion dir update takes no arguments")

    d = getDirectory()
    serverList = d.getServerList()
    inbox = d.getInbox()
    inbox.acceptUpdates(serverList)

def cmd_list(args):
    """[Entry point] List descriptors waiting to be imported."""
    if args:
        raise UIError("mixminion dir list takes no arguments")

    d = getDirectory()
    inbox = d.getInbox()
    inbox.listNewPendingServers(sys.stdout)

def cmd_rebuildcache(args):
    """[Entry point] Reconstruct the ID cache from the contents of the
       'servers' directory.
    """
    if args:
        raise UIError("mixminion dir rebuildcache takes no arguments")
    d = getDirectory()
    serverList = d.getServerList()
    serverList.rebuildIDCache()
    d.getIDCache().save()

def cmd_import(args):
    """[Entry point] Import descriptors for new servers, by nickname."""
    d = getDirectory()
    inbox = d.getInbox()
    serverList = d.getServerList()

    if not args:
        print "(No server names given)"

    bad, good = 0,0
    for name in args:
        print "Importing server %r..."%name
        try:
            inbox.acceptNewServer(serverList, name)
            good += 1
            print "Imported."
        except UIError, e:
            bad += 1
            print "Error importing %r: %s"%(name, e)

    print "\n%s servers imported, %s rejected." %(good,bad)

def cmd_generate(args):
    """[Entry point] generate a fresh directory.  Can be run from a cron
       job."""
    if args:
        raise UIError("mixminion dir generate takes no arguments")

    d = getDirectory()
    serverList = d.getServerList()
    key = d.getIdentity()
    serverList.clean()

    config = d.getConfig()

    badServers = config['Directory'].get('BadServer', [])[:]
    badServerFiles = config['Directory'].get('BadServerFile', [])
    for fn in badServerFiles:
        if not os.path.exists(fn):
            print "No such file %r; skipping" %fn
            continue
        f = open(fn, 'r')
        for ln in iterFileLines(f):
            ln = ln.strip()
            if ln and ln[0] != '#':
                badServers.append(ln)
        f.close()

    excludeServers = config['Directory'].get("ExcludeServer",[])[:]
    excludeServers = [ nn.strip().lower() for nn in excludeServers ]

    location = config['Publishing']['Location']
    print "(Bad servers==%r)"%badServers

    now = time.time()
    tomorrow = now+60*60*24
    twoWeeks = 60*60*24*14

    serverList.generateDirectory(startAt=now, endAt=tomorrow,
                                 extraTime=twoWeeks,
                                 identityKey=key,
                                 badServers=badServers,
                                 excludeServers=excludeServers)
    print "Directory generated; publishing."

    fname = serverList.getDirectoryFilename()

    if location.endswith(".gz"):
        fIn = open(fname)
        fOut = gzip.GzipFile(location, 'wb')
        fOut.write(fIn.read())
        fIn.close()
        fOut.close()
    else:
        shutil.copy(fname, location)

    print "Published."

def cmd_fingerprint(args):
    """[Entry point] Print the fingerprint for this directory's key."""

    if args:
        raise UIError("mixminion dir fingerprint takes no arguments")
    d = getDirectory()
    key = d.getIdentity()
    print pk_fingerprint(key)

SUBCOMMANDS = { 'initialize' : cmd_init,
                'update' : cmd_update,
                'list' : cmd_list,
                'import-new' : cmd_import,
                'generate' : cmd_generate,
                'fingerprint' : cmd_fingerprint,
                'rebuildcache' : cmd_rebuildcache
                }

def main(cmd, args):
    """[Entry point] Multiplex among subcommands."""
    if len(args)<1 or ('-h', '--help') in args:
        usageAndExit()
    command = args[0]
    args = args[1:]
    if not SUBCOMMANDS.has_key(command):
        print "Unknown command", command
        usageAndExit()
    init_crypto()
    LOG.setMinSeverity("INFO")
    SUBCOMMANDS[command](args)
