# Copyright (c) 2009 Paul Gebheim
# 
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import sys
import socket
import array
from optparse import OptionParser
from Crypto.Cipher import Blowfish
from Crypto.Hash import MD5

TELNET_PORT = 23

# The version of Blowfish supplied for the telenetenable.c implementation
# assumes Big-Endian data, but the code does nothing to convert the
# little-endian stuff it's getting on intel to Big-Endian
#
# So, since Crypto.Cipher.Blowfish seems to assume native endianness, we need
# to byteswap our buffer before and after encrypting it
#
# This helper does the byteswapping on the string buffer
def ByteSwap(data):
  a = array.array('i')
  if(a.itemsize < 4):
    a = array.array('L')
  
  if(a.itemsize != 4):
    print "Need a type that is 4 bytes on your platform so we can fix the data!"
    exit(1)

  a.fromstring(data)
  a.byteswap()
  return a.tostring()

def GeneratePayload(mac, username, password=""):
  # eventually reformat mac
  mac = mac.replace(":","").upper()

  # Pad the input correctly
  assert(len(mac) < 0x10)
  just_mac = mac.ljust(0x10, "\x00")

  assert(len(username) <= 0x10)
  just_username = username.ljust(0x10, "\x00")
  
  assert(len(password) <= 0x21)
  just_password = password.ljust(0x21, "\x00")

  cleartext = (just_mac + just_username + just_password).ljust(0x70, '\x00')
  md5_key = MD5.new(cleartext).digest()

  payload = ByteSwap((md5_key + cleartext).ljust(0x80, "\x00"))
  
  secret_key = "AMBIT_TELNET_ENABLE+" + password

  return ByteSwap(Blowfish.new(secret_key, 1).encrypt(payload))


def SendPayload(ip, payload):
  for res in socket.getaddrinfo(ip, TELNET_PORT, socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP):
    af, socktype, proto, canonname, sa = res
    try:
      s = socket.socket(af, socktype, proto)
    except socket.error, msg:
      s = None
      continue

    try:
      s.connect(sa)
    except socket.error, msg:
      s.close()
      s= None
      continue
    break

  if s is None:
    print "Could not connect to '%s:%d'" % (ip, TELNET_PORT)
  else:
    s.send(payload)
    s.close()
    print "Sent telnet enable payload to '%s:%d'" % (ip, TELNET_PORT)
  
def main():
  args = sys.argv[1:]
  if len(args) < 3 or len(args) > 4:
    print "usage: python telnetenable.py <ip> <mac> <username> [<password>]"

  ip = args[0]
  mac = args[1]
  username = args[2]

  password = ""
  if len(args) == 4:
    password = args[3]

  payload = GeneratePayload(mac, username, password)
  SendPayload(ip, payload)

main()