#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
gong (go ng) - Command-line client to log in to network devices using TACACS credentials.

An optional .gorc file may be used to specify user preferences.

Partially adapted from conch.  See twisted.conch.scripts.conch and
http://twistedmatrix.com/projects/conch/documentation/howto/conch_client.html
"""

__author__ = 'Jathan McCollum, Eileen Tschetter, Michael Shields'
__maintainer__ = 'Jathan McCollum'
__email__ = 'jathan.mccollum@teamaol.com'
__copyright__ = 'Copyright 2006-2012, AOL Inc.'
__version__ = '1.3'

from optparse import OptionParser
import os
import sys
import tty
from twisted.internet import reactor
from twisted.python import log
from trigger.conf import settings
from trigger.netdevices import NetDevices, device_match
from trigger.twister import pty_connect, Interactor, LoginFailure
from trigger.utils.cli import yesno
from trigger import tacacsrc, gorc

def stop_reactor():
    """Stop the reactor if it's already running."""
    if reactor.running:
        reactor.stop()

def parse_args(argv):
    parser = OptionParser(usage='%prog [options] [device]', description='''\
Automatically log into network devices using cached TACACS credentials.
''')

    parser.add_option('-o', '--oob', action='store_true',
        help='Connect to device out of band first.')

    opts, args = parser.parse_args(argv)

    if len(args) != 2:
        parser.print_help()
        sys.exit(2)

    return opts, args

def connect_to_oob(device):
    """Lookup out-of-band info and try to connect to the console."""
    dev = NetDevices(production_only=False)
    f = dev.find(device)

    tn = "telnet %s %s" % (f.OOBTerminalServerFQDN, f.OOBTerminalServerTCPPort)

    if f.adminStatus != 'PRODUCTION':
        print 'WARNING: You are connecting to a non-production device.'
    print "OOB Information for %s" % f.nodeName
    print tn
    print 'Connecting you now...'
    tn = "telnet %s %s" % (f.OOBTerminalServerFQDN, f.OOBTerminalServerTCPPort)

    os.system(tn)

login_failed = None
def handle_login_failure(failure):
    """An errback to try detect a login failure."""
    global login_failed
    if failure.type == LoginFailure:
        login_failed = True

def main():
    global opts
    opts, args = parse_args(sys.argv)

    if os.getenv('DEBUG') is not None:
        log.startLogging(sys.stdout, setStdout=False)

    if opts.oob:
        connect_to_oob(args[1].lower())
        sys.exit(0)

    # Exception handling is done in device_match, returns None if no match.
    dev = device_match(args[1].lower(), production_only=False)
    if dev is None:
        sys.exit(2)

    if dev.adminStatus != 'PRODUCTION':
        print 'WARNING: You are connecting to a non-production device.'

    # Need to pass ^C through to the router so we can abort traceroute, etc.
    print 'Connecting to %s.  Use ^X to exit.' % dev

    # Fetch the initial commands for the device
    init_commands = gorc.get_init_commands(dev.manufacturer)

    try:
        d = pty_connect(dev, Interactor(), init_commands=init_commands)
        d.addErrback(handle_login_failure)
        d.addErrback(log.err)
        d.addCallback(lambda x: stop_reactor())
    except AttributeError, err:
        sys.stderr.write('Could not connect to %s.\n' % dev)
        sys.exit(2)

    # Preserve original tty settings
    stdin_fileno = sys.stdin.fileno()
    old_ttyattr = tty.tcgetattr(stdin_fileno)

    try:
        # Enter raw mode on the local tty.
        tty.setraw(stdin_fileno)
        raw_ta = tty.tcgetattr(stdin_fileno)
        raw_ta[tty.LFLAG] |= tty.ISIG
        raw_ta[tty.OFLAG] |= tty.OPOST | tty.ONLCR
        raw_ta[tty.CC][tty.VINTR] = '\x18'  # ^X is the new ^C
        raw_ta[tty.CC][tty.VSUSP] = 0       # disable ^Z
        tty.tcsetattr(stdin_fileno, tty.TCSANOW, raw_ta)

        reactor.run()

    finally:
        # Restore original tty settings
        tty.tcsetattr(stdin_fileno, tty.TCSANOW, old_ttyattr)

    # If there is a login failure stop the reactor so we can take raw_input(),
    # ask the user if they, awant to update their cached credentials, and
    # prompt them to connect.
    if login_failed:
        stop_reactor()
        if yesno('Login failed, would you like to update your password?', default=True):
            tacacsrc.update_credentials(dev.nodeName)
            if yesno('\nReconnect to %s?' % dev, default=True):
                # Replaces the current process w/ same pid
                os.execl(sys.executable, sys.executable, *sys.argv)
        print 'BYE'

    print '\n' # Return cursor to beginning of line

if __name__ == '__main__':
    main()
