module Msf

require 'msf/core/exploit/tcp'

###
#
# This module exposes methods that may be useful to exploits that deal with
# servers that speak the telnet protocol.
#
###
module Exploit::Remote::Telnet

	include Exploit::Remote::Tcp

	# Borrowing constants from Ruby's Net::Telnet class (ruby license)
	IAC   = 255.chr # "\377" # "\xff" # interpret as command
	DONT  = 254.chr # "\376" # "\xfe" # you are not to use option
	DO    = 253.chr # "\375" # "\xfd" # please, you use option
	WONT  = 252.chr # "\374" # "\xfc" # I won't use option
	WILL  = 251.chr # "\373" # "\xfb" # I will use option
	SB    = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
	GA    = 249.chr # "\371" # "\xf9" # you may reverse the line
	EL    = 248.chr # "\370" # "\xf8" # erase the current line
	EC    = 247.chr # "\367" # "\xf7" # erase the current character
	AYT   = 246.chr # "\366" # "\xf6" # are you there
	AO    = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
	IP    = 244.chr # "\364" # "\xf4" # interrupt process--permanently
	BREAK = 243.chr # "\363" # "\xf3" # break
	DM    = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
	NOP   = 241.chr # "\361" # "\xf1" # nop
	SE    = 240.chr # "\360" # "\xf0" # end sub negotiation
	EOR   = 239.chr # "\357" # "\xef" # end of record (transparent mode)
	ABORT = 238.chr # "\356" # "\xee" # Abort process
	SUSP  = 237.chr # "\355" # "\xed" # Suspend process
	EOF   = 236.chr # "\354" # "\xec" # End of file
	SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls

	OPT_BINARY         =   0.chr # "\000" # "\x00" # Binary Transmission
	OPT_ECHO           =   1.chr # "\001" # "\x01" # Echo
	OPT_RCP            =   2.chr # "\002" # "\x02" # Reconnection
	OPT_SGA            =   3.chr # "\003" # "\x03" # Suppress Go Ahead
	OPT_NAMS           =   4.chr # "\004" # "\x04" # Approx Message Size Negotiation
	OPT_STATUS         =   5.chr # "\005" # "\x05" # Status
	OPT_TM             =   6.chr # "\006" # "\x06" # Timing Mark
	OPT_RCTE           =   7.chr # "\a"   # "\x07" # Remote Controlled Trans and Echo
	OPT_NAOL           =   8.chr # "\010" # "\x08" # Output Line Width
	OPT_NAOP           =   9.chr # "\t"   # "\x09" # Output Page Size
	OPT_NAOCRD         =  10.chr # "\n"   # "\x0a" # Output Carriage-Return Disposition
	OPT_NAOHTS         =  11.chr # "\v"   # "\x0b" # Output Horizontal Tab Stops
	OPT_NAOHTD         =  12.chr # "\f"   # "\x0c" # Output Horizontal Tab Disposition
	OPT_NAOFFD         =  13.chr # "\r"   # "\x0d" # Output Formfeed Disposition
	OPT_NAOVTS         =  14.chr # "\016" # "\x0e" # Output Vertical Tabstops
	OPT_NAOVTD         =  15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
	OPT_NAOLFD         =  16.chr # "\020" # "\x10" # Output Linefeed Disposition
	OPT_XASCII         =  17.chr # "\021" # "\x11" # Extended ASCII
	OPT_LOGOUT         =  18.chr # "\022" # "\x12" # Logout
	OPT_BM             =  19.chr # "\023" # "\x13" # Byte Macro
	OPT_DET            =  20.chr # "\024" # "\x14" # Data Entry Terminal
	OPT_SUPDUP         =  21.chr # "\025" # "\x15" # SUPDUP
	OPT_SUPDUPOUTPUT   =  22.chr # "\026" # "\x16" # SUPDUP Output
	OPT_SNDLOC         =  23.chr # "\027" # "\x17" # Send Location
	OPT_TTYPE          =  24.chr # "\030" # "\x18" # Terminal Type
	OPT_EOR            =  25.chr # "\031" # "\x19" # End of Record
	OPT_TUID           =  26.chr # "\032" # "\x1a" # TACACS User Identification
	OPT_OUTMRK         =  27.chr # "\e"   # "\x1b" # Output Marking
	OPT_TTYLOC         =  28.chr # "\034" # "\x1c" # Terminal Location Number
	OPT_3270REGIME     =  29.chr # "\035" # "\x1d" # Telnet 3270 Regime
	OPT_X3PAD          =  30.chr # "\036" # "\x1e" # X.3 PAD
	OPT_NAWS           =  31.chr # "\037" # "\x1f" # Negotiate About Window Size
	OPT_TSPEED         =  32.chr # " "    # "\x20" # Terminal Speed
	OPT_LFLOW          =  33.chr # "!"    # "\x21" # Remote Flow Control
	OPT_LINEMODE       =  34.chr # "\""   # "\x22" # Linemode
	OPT_XDISPLOC       =  35.chr # "#"    # "\x23" # X Display Location
	OPT_OLD_ENVIRON    =  36.chr # "$"    # "\x24" # Environment Option
	OPT_AUTHENTICATION =  37.chr # "%"    # "\x25" # Authentication Option
	OPT_ENCRYPT        =  38.chr # "&"    # "\x26" # Encryption Option
	OPT_NEW_ENVIRON    =  39.chr # "'"    # "\x27" # New Environment Option
	OPT_EXOPL          = 255.chr # "\377" # "\xff" # Extended-Options-List

	NULL = "\000"
	CR   = "\015"
	LF   = "\012"
	EOL  = CR + LF


	#
	# Creates an instance of a Telnet exploit module.
	#
	def initialize(info = {})
		super

		# Register the options that all Telnet exploits may make use of.
		register_options(
			[
				Opt::RHOST,
				Opt::RPORT(23),
				OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
				OptString.new('PASSWORD', [ false, 'The password for the specified username' ])
			], Msf::Exploit::Remote::Telnet)

		register_advanced_options(
			[
				OptInt.new('TelnetTimeout', [ true, 'The number of seconds to wait for a reply from a Telnet command', 10]),
				OptInt.new('TelnetBannerTimeout', [ true, 'The number of seconds to wait for the initial banner', 25])
			], Msf::Exploit::Remote::Telnet)

		register_autofilter_ports([ 23 ])
		register_autofilter_services(%W{ telnet })

		# Appended to by each read and gets reset after each send.  Doing it
		# this way lets us deal with partial reads in the middle of expect
		# strings, e.g., the first recv returns "Pa" and the second returns
		# "ssword: "
		@recvd = ''

		#
		#  Some of these regexes borrowed from NeXpose, others added from datasets
		#
		@login_regex = /(?:log[io]n( name|)|user(name|id|))\s*\:/i
		@password_regex = /(?:password|passwd)\s*\:/i
		@false_failure_regex = /(?:(^\s*last)\ login *\:|allows only\ .*\ Telnet\ Client\ License)/i
		@failure_regex = /(?:
				Incorrect | Unknown  | Fail      | Invalid  |
				Login     | Password | Passwd    | Username |
				Unable    | Error    | Denied    | Reject   |
				Refuse    | Close    | Closing   | %\ Bad   |
				Sorry     |
				Not\ on\ system\ console |
				Enter\ username\ and\ password |
				Auto\ Apply\ On |
				YOU\ LOGGED\ IN\ USING\ ALL\ UPPERCASE\ CHARACTERS|
				\n\*$ |
				(Login ?|User ?)(name|): |
				^\s*\<[a-f0-9]+\>\s*$ |
				^\s*220.*FTP
			)/mix

		@waiting_regex = /(?:
			.*please\ wait.* |
			.*one\ minute.*
		)/mix

		@busy_regex = /(?:
			Another\ telnet\ session\ is\ in\ progress | Disconnecting\.\.\.
		)/mix

		@success_regex = /(?:
				list\ of\ built-in     |
				sh.*[\#\$]\s*$         |
				\[\/\]\s*$             |
				or\ the\ MENU\ system  |
				Password\ is\ not\ set |
				logging\ in\ as\ visitor |
				Login\ successful
			)/mix
	end

	#
	# This method establishes an Telnet connection to host and port specified by
	# the RHOST and RPORT options, respectively.  After connecting, the banner
	# message is read in and stored in the 'banner' attribute. This method has the
	# benefit of handling telnet option negotiation.
	#
	def connect(global = true, verbose = true)
		@trace = ''
		@recvd = ''
		fd = super(global)
		banner_timeout = (datastore['TelnetBannerTimeout'] || 25).to_i

		self.banner = ''
		# Wait for a banner to arrive...
		begin
		Timeout.timeout(banner_timeout) do
			while(true)
				buff = recv_telnet(fd)
				self.banner << buff if buff
				if(self.banner =~ @login_regex or self.banner =~ @password_regex)
					break
				elsif self.banner =~ @busy_regex
					# It's about to drop connection anyway -- seen on HP JetDirect telnet server
					break
				end
			end
		end
		rescue ::Timeout::Error
		end

		self.banner.strip!

		# Return the file descriptor to the caller
		fd
	end


	#
	# Handle telnet option negotiation
	#
	# Appends to the @recvd buffer which is used to tell us whether we're at a
	# login prompt, a password prompt, or a working shell.
	#
	def recv_telnet(fd=self.sock, timeout=datastore['TelnetTimeout'])

		data = ''

		begin
		data = fd.get_once(-1, timeout.to_i)
		return nil if not data or data.length == 0

		# combine CR+NULL into CR
		data.gsub!(/#{CR}#{NULL}/no, CR)

		# combine EOL into "\n"
		data.gsub!(/#{EOL}/no, "\n")

		data.gsub!(/#{IAC}(
				[#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|[#{DO}#{DONT}#{WILL}#{WONT}]
				[#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|#{SB}[^#{IAC}]*#{IAC}#{SE}
				)/xno) do
			m = $1

			if m == IAC
				IAC
			elsif m == AYT
				fd.write("YES" + EOL)
				''
			elsif m[0,1] == DO
				if(m[1,1] == OPT_BINARY)
					fd.write(IAC + WILL + OPT_BINARY)
				else
					fd.write(IAC + WONT + m[1,1])
				end
				''
			elsif m[0,1] == DONT
				fd.write(IAC + WONT + m[1,1])
				''
			elsif m[0,1] == WILL
				if m[1,1] == OPT_BINARY
					fd.write(IAC + DO + OPT_BINARY)
				# Disable Echo
				elsif m[1,1] == OPT_ECHO
					fd.write(IAC + DONT + OPT_ECHO)
				elsif m[1,1] == OPT_SGA
					fd.write(IAC + DO + OPT_SGA)
				else
					fd.write(IAC + DONT + m[1,1])
				end
				''
			elsif m[0,1] == WONT
				fd.write(IAC + DONT + m[1,1])
				''
			else
				''
			end
		end
		@trace << data
		@recvd << data
		fd.flush

		rescue ::EOFError, ::Errno::EPIPE
		end

		data
	end

	def login_prompt?
		return true if @recvd =~ @login_regex
		return false
	end

	def command_echo?(cmd)
		recvn = @recvd.gsub(/^(\s*#{cmd}\r?\n\s*|\s*\*+\s*)/, '')
		if(recvn != @recvd)
			@recvd = recvn
			return true
		end
		false
	end

	def waiting_message?
		recvn = @recvd.gsub(@waiting_regex, '')
		if(recvn != @recvd)
			@recvd = recvn.strip
			return true
		end
		false
	end

	def busy_message?
		recvn = @recvd.gsub(@busy_regex, '')
		if(recvn != @recvd)
			@recvd = recvn.strip
			return true
		end
		false
	end

	def password_prompt?
		return true if @recvd =~ @password_regex
		return false
	end

	def login_failed?
		# Naively, failure means matching the failure regex.
		#
		# However, this leads to problems with false positives in the case of
		# "login:" because unix systems commonly show "Last login: Sat Jan  3
		# 20:22:52" upon successful login, so check against a false-positive
		# regex, also.
		#


		# Empty strings should not count
		if @recvd.strip.length == 0
			return true
		end

		# If we have not seen a newline, this is likely an echo'd prompt
		if ! @recvd.index("\n")
			return true
		end

		# We do have a set of highly-accurate success patterns
		if (@recvd =~ @success_regex)
			return false
		end

		if @recvd =~ @failure_regex
			if @recvd !~ @false_failure_regex
				return true
			end
		end
		return false
	end
	def login_succeeded?
		# Much easier to test for failure than success because a few key words
		# mean failure whereas all kinds of crap is used for success, much of
		# which also shows up in failure messages.
		return (not login_failed?)
	end

	def user
		datastore["USERNAME"]
	end

	def pass
		datastore["PASSWORD"]
	end

	#
	# This method logs in as the supplied user by transmitting the username
	#
	def send_user(user, nsock = self.sock)
		got_prompt = wait_for(@login_regex)
		if not got_prompt
			print_error("#{rhost} - Something is wrong, didn't get a login prompt")
		end
		return send_recv("#{user}\r\n")
	end

	#
	# This method completes user authentication by sending the supplied password
	#
	def send_pass(pass, nsock = self.sock)
		got_prompt = wait_for(@password_regex)
		if not got_prompt
			print_error("#{rhost} - Something is wrong, didn't get a password prompt")
		end
		return send_recv("#{pass}\r\n")
	end

	def send_recv(msg, nsock = self.sock)
		raw_send(msg, nsock)
		recv_all(nsock)
		return @recvd
	end

	def recv_all(nsock = self.sock, timeout = tel_timeout)
		# Make sure we read something in
		wait_for(/./)
	end

	#
	# This method transmits a telnet command and does not wait for a response
	#
	# Resets the @recvd buffer
	#
	def raw_send(cmd, nsock = self.sock)
		@recvd = ''
		@trace << cmd
		nsock.put(cmd)
	end

	#
	# Wait for the supplied string (or Regexp) to show up on the socket, or a
	# timeout
	#
	def wait_for(expect, nsock = self.sock)
		if expect.kind_of? Regexp
			regx = expect
		else
			regx = /#{Regexp.quote(expect)}/i
		end
		return true if @recvd =~ regx

		resp = ''
		while (resp and not @recvd =~ regx)
			resp = recv_telnet(nsock)
		end

		return (@recvd =~ regx)
	end

	##
	#
	# Wrappers for getters
	#
	##

	#
	# Returns the number of seconds to wait for a telnet reply
	#
	def tel_timeout
		(datastore['TelnetTimeout'] || 10).to_i
	end

protected

	#
	# This attribute holds the banner that was read in after a successful call
	# to connect or connect_login.
	#
	attr_accessor :banner

end

end

