send email via telnet ruby script - ruby

I'm new with ruby and need to send an email via telnet using a relay host with no authentication. I can do it with a linux shell but I need to put it in a script so I can "simplify" its use, I know it's not the best way but I can't find other since the server where i'm working on it's severely restricted and limited.
require 'net/telnet.rb'
mail = Net::Telnet::new(
"Host" => "domain.ip", # default: "localhost"
"Port" => 25, # default: 23
"Output_log" => "output_log", # default: nil (no output)
"Dump_log" => "dump_log", # default: nil (no output)
"Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n
"Telnetmode" => true, # default: true
"Timeout" => 10, # default: 10
"Waittime" => 0, # default: 0
)
mail.cmd('helo MYDOMAIN'){ |c| print c }
mail.cmd('mail from: test#domain.com')
mail.cmd('rcpt to: test2#domain.com')
mail.cmd('data')
mail.cmd("subject: test cmd \n\n mensaje de prueba\n\n")
mail.cmd(".\n")
mail.close
I found the net/telnet.rb ruby class and this is my try... after mail.cmd('helo MYDOMAIN') I can't keep writing other commands, what I get is:
220 mail.server.com ESMTP
250 mail.server.com
After this I'm suposed to write mail from, etc. to create the mail. But I can't in the ruby script. I have try using:
mail.puts('mail from: test...')
mail.write('mail from: test...')
mail.print('mail from: test...')
mail.cmd('mail from: test...')
As written in documentation
Also I don't get the telnetmode(true|false) command maybe you could explain it to me please.
-- Edit --
Shell code trying to emulate:
telnet domain.ip 25
#=> Trying domain.ip...
#=> Connected to domain.ip.
#=> Escape character is '^]'.
#=> 220 mail.server.com ESMTP
helo MYDOMAIN
#=>250 mail.server.com
mail from:test#mydomain.com
#=> 250 2.1.0 Ok
rcpt to:test2#mydomain.com
#=> 250 2.1.0 Ok
data
#=> 354 End data with <CR><LF>.<CR><LF>
subject: test mail
test mail body
.
#=> 250 2.0.0 =k: queued as B6F08480D12
quit
#=> 221 2.0.0 Bye
#=> Connection closed by foreign host.

The telnet protocol is really, really rudimentary which is why the telnet command is useful for testing TCP/IP based services such as SMTP or HTTP. It does not mean those services actually use the telnet protocol, as they don't. They're conveniently plain-text in nature which means it's practical to use telnet for simple tests.
You should not be using the Telnet module for anything other than connecting to telnet services, though given it's 2017 it's unlikely you'll find any of those around.
You should be using something like Socket to connect. This can create a bare TCP/IP connection with full control over sending. As this is a wrapper around a regular POSIX filehandle you can use all the IO methods on it for reading, writing, and other control functions, like a proper socket shutdown.
Writing an SMTP adapter is not as easy as it seems, there's a lot of tricky things to tackle with regard to IO. You'll need to use IO.select to properly test for new data, plus that the socket is clear to write your email.
Here's a new stub:
require 'socket'
mail = TCPSocket.new("smtp.example.com", 25)
mail.write("HELO example.com\r\n")
Another note is that when you call require you should never specify the file extension. It's always handled for you.

Thanks to the help of the user ddubs how suggest the net\smtp gem (One that I didn't know) I was able to create a simple mail sender and using the mailfactory gem
Is it a strict requirement that you use telnet? Using ruby-doc.org/stdlib-2.0.0/libdoc/net/smtp/rdoc/Net/SMTP.html will turn your "difficult to maintain" script into something that is much easier to maintain. Even for someone who is completely new to Ruby. – ddubs
Here is the code sample
require 'net/smtp'
require 'mailfactory'
mail_body_HTML = '<h1> mail title</h1> your text in <b>HTML</b>'
mail_body_PLAIN = 'this is plain text'
mail_subject = 'test email'
mail_from = 'noreply#mydomain.com'
mail_to = 'user#otherdomain.com'
# mail_filePath = ''
mail = MailFactory.new()
mail.to = mail_to
mail.from = mail_from
mail.subject = mail_subject
mail.html = mail_body_HTML
# mail.text = mail_body_PLAIN
# mail.attach(mail_filePath)
relay_ip = x.x.x.x
Net::SMTP.start(relay_ip,25) do |smtp|
smtp.send_message(mail.to_s, mail_from, mail_to)
end

Related

Ruby Tor Switching Ip - "general SOCKS server failure" unless I spawn a new process to telnet into Tor control port

When trying to switch Tor IP address using telnet, I am running into SOCKSError::ServerFailure: general SOCKS server failure when trying to connect to the Tor control port via telnet. However if I spawn a new process to do the telnetting, I have no problem. I dont want to spawn a new process because its ugly. I was hoping someone could help me find why I have this problem and a more rubust solution?
To reproduce:
start tor:
tor --SocksPort 9350 --ControlPort 53500 --CookieAuthentication 0 --HashedControlPassword <passwordhash> --DataDirectory /tmp/tor_data/9350
Then run this ruby code which uses socksify to setup a socks server, then uses the tor gem Tor::Controller.connect block which connects to the Tor control port via telnet to switch Tor endpoint:
require 'socksify'
require 'terminator'
require 'tor'
TCPSocket::socks_server = "127.0.0.1"
TCPSocket::socks_port = "9350"
Tor::Controller.connect(:port => 53500) do |tor| #<- error
tor.authenticate("")
tor.signal("newnym")
end
Error at Tor::Controller.connect call:
SOCKSError::ServerFailure: general SOCKS server failure
If I replace the Tor::Controller.connect block with this (spawning a new process to do the telnetting), I have success:
telnet_pid = nil
begin
Terminator.terminate :seconds => 20 do
cmd = "bundle exec ruby -e \"require 'tor'\" -e " +
"\"Tor::Controller.connect(:port => 53500)" +
"{|tor| tor.authenticate(''); tor.signal('newnym')}\""
telnet_pid = Process.spawn(cmd)
Process.wait telnet_pid
end
rescue Terminator.error
puts 'Telnet process to switch Tor endpoint timed out!'
Process.kill('TERM', telnet_pid) if telnet_pid
end
I realised that the socks server was routing all TCP requests through the SOCKS server including my telnet requests. If I disable the socks server while telnetting then re-enable it again it works:
TCPSocket::socks_server = nil
TCPSocket::socks_port = nil
Tor::Controller.connect(:port => 53500) do |tor| #<- error
tor.authenticate("")
tor.signal("newnym")
end
TCPSocket::socks_server = "127.0.0.1"
TCPSocket::socks_port = "9350"

Parse TCP Packet and return response PHP or Ruby

I am trying to gather information from a TCP connection made to my web server to assist in our troubleshooting efforts, similar to http://speedguide.net/analyzer.php tool.
We have a simple PHP server script test page that users connect that returns their private IP to an AJAX call waiting for the response.
I would like to either build on that or prefer using Ruby. I played with the PacketFu lib and get all the information I think I need, however, I'm having trouble with the recipe to combine it all:
listen on port x,
parse the packet
respond back to client.
Using Ruby's TCPServer I can easily handle 1 and 3. With Packetfu, 2.
I've coded with PHP in the past but only HTML-based, no sockets. And I'm not really all that familiar with Ruby sockets either.
Though the packet stream and client.accept don't seem to play nice. The packets aren't always IP or TCP meeting the Packetfu::Packet.is_ip? or is_tcp?.
Could someone point me in the right direction or give me some practical example of how I might combine the two, or adjust my thinking on how I would accomplish this task?
This is the Playground code:
require 'socket'
require 'json'
require 'packetfu'
iface = ARGV[0] || "eno1"
server = TCPServer.open(31337)
cap = PacketFu::Capture.new(:iface => iface, :start => true, :promisc => true)
loop {
cap.stream.each do |p|
pkt = PacketFu::Packet.parse(p)
if pkt.is_ip? || pkt.is_tcp?
if pkt.tcp_dport == 31337
print "Source Addr: #{pkt.ip_saddr}\n"
print "Source Port: #{pkt.tcp_src}\n"
print "Destination Addr: #{pkt.ip_daddr}\n"
print "Destination Port: #{pkt.tcp_dport}\n"
print "TCP Options: #{pkt.tcp_options.inspect}\n"
print "TCP Win: #{pkt.tcp_win}\n"
print "TCP SYN?: #{pkt.tcp_flags.syn}\n"
print "TCP ACK?: #{pkt.tcp_flags.ack}\n"
print "TCP FLAGS ALL: #{pkt.tcp_flags.inspect}\n"
print "TTL: #{pkt.ip_ttl}\n"
print "IP FRAG: #{pkt.ip_frag}\n"
end
end
client = server.accept # Wait for a client to connect
h = { ipaddress: client.peeraddr[2] }
client.puts h.to_json
client.close
end
}
This is the output:
Source Addr: 172.20.0.15
Source Port: 41165
Destination Addr: 172.20.0.10
Destination Port: 31337
TCP Options: "NOP,NOP,TS:216432150;57946250"
TCP Win: 229
TCP SYN?: 0
TCP ACK?: 1
TCP FLAGS ALL: #<struct PacketFu::TcpFlags urg=0, ack=1, psh=0, rst=0, syn=0, fin=0>
TTL: 61
IP FRAG: 16384
This is the browser response:
{"ipaddress":"172.20.0.15"}

Ruby, Telnet, read multiline response without timeout

I need some hints/help, how can I read multiline response into variable.
My current command results me multiline response but after that I get timeout.
Here's how my connection is setup:
connection = Net::Telnet.new('Host' => host,'Port' => 4800, 'Telnetmode' => false, 'Timeout' => 1)
Here's my request and how I save it:
puts "Weather request\n"
connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"
And here's the error:
/usr/lib/ruby/1.9.1/net/telnet.rb:558:in `waitfor': timed out while waiting for more data (Timeout::Error)
from /usr/lib/ruby/1.9.1/net/telnet.rb:695:in `cmd'
from ruby_check.rb:37:in `<main>'
My response is multiple JSON lines, like this:
{"City":"Tallinn", "Degrees":"23"}
{"City":"Berlin", "Degrees":"23"}
{"City":"Helsinki", "Degrees":"23"}
{"City":"Stockholm", "Degrees":"23"}
Why the timeout?
The Net::Telnet documentation says:
For some protocols, it will be possible to specify the Prompt option once when you create the Telnet object and use cmd() calls; for others, you will have to specify the response sequence to look for as the Match option to every cmd() call, or call puts() and waitfor() directly; for yet others, you will have to use sysread() instead of waitfor() and parse server responses yourself.
This makes more sense when combined with the Net::Telnet#cmd method's documentation, which says that the method:
sends a string to the host, and reads in all received data until is sees the prompt or other matched sequence.
You're not specifying a custom Prompt or Match option, so #cmd is waiting for something from the server that matches the default Net::Telnet prompt (/[$%#>] \z/n) to indicate the end of the message.
If the message doesn't end with that kind of prompt, then it'll be waiting forever.
Possible solutions
Match the server's prompt
If the server does send some kind of prompt to indicate it's finished sending data and you should type the next command, you can pass a regular expression that matches it to the Net::Telnet initialiser. For example, if the server prompted you with command:, you could use:
connection = Net::Telnet.new(
"Prompt" => /command: \z/,
# …
)
Match the end of the response
If there's no prompt, but the response you're waiting for ends with a specific character sequence, you could explicitly specify the Match option when you call #cmd. For example, if your response was a single JSON array it would end with ], so you might be able to use this:
connection.cmd("String" => "{weather}", "Match" => "]") { |c| print c }
Give up on Net::Telnet and use a TCPSocket
If there's no prompt and no known ending, you could try to use the Net::Telnet object's underlying TCPSocket to read the data without using #cmd:
connection.puts("{weather}")
connection.sock.readline
At this point, there might not be much benefit to using Net::Telnet over a plain TCPSocket.
You are setting the timeout to one second and do not specify what str is. You can try increasing the timeout value or even setting it to false. Believieng it is the result from .cmd, try this:
connection = Net::Telnet.new(
"Host" => host, "Port" => 4800,
"Telnetmode" => false, "Timeout" => false)
puts "Weather request...\n"
str = connection.cmd("{weather}"){ |c| print c }
parsed = JSON.parse(str)
puts "#{parsed}\n\n"

Connecting to Yahoo! mail from Ruby

I try to connect to mail Yahoo! account from Ruby using both net/imap and net/pop. But I randomly get error EOFile (from IMAP) or Connection Refused/Reset by peer (from POP). Has anybody tried to connect to Yahoo! Mail and had some experiences about it?
There's a bug in ruby's net/imap library that is exposed when connecting to Yahoo.
The fix is straightforward and described here:
http://redmine.ruby-lang.org/issues/4509
Basically, edit imap.rb and change the inner loop of search_response method from:
token = lookahead
case token.symbol
when T_CRLF
break
when T_SPACE
shift_token
end
data.push(number)
to:
token = lookahead
case token.symbol
when T_CRLF
break
when T_SPACE
shift_token
else
data.push(number)
end
then test with the following code:
require 'net/imap'
Net::IMAP.debug = true
conn = Net::IMAP.new('imap.mail.yahoo.com', 143, false)
conn.instance_eval { send_command('ID ("GUID" "1")') }
conn.authenticate('LOGIN', ARGV[0], ARGV[1] )
conn.select("INBOX")
uids = conn.uid_search(['ALL'])
puts uids.join(',')
conn.logout
conn.disconnect

Is there a way to attach Ruby Net::HTTP request to a specific IP address / network interface?

Im looking a way to use different IP addresses for each GET request with standard Net::HTTP library. Server has 5 ip addresses and assuming that some API`s are blocking access when request limit per IP is reached. So, only way to do it - use another server. I cant find anything about it in ruby docs.
For example, curl allows you to attach it to specific ip address (in PHP):
$req = curl_init($url)
curl_setopt($req, CURLOPT_INTERFACE, 'ip.address.goes.here';
$result = curl_exec($req);
Is there any way to do it with Net::HTTP library? As alternative - CURB (ruby curl binding). But it will be the last thing i`ll try.
Suggestions / Ideas?
P.S. The solution with CURB (with dirty tests, ip`s being replaced):
require 'rubygems'
require 'curb'
ip_addresses = [
'1.1.1.1',
'2.2.2.2',
'3.3.3.3',
'4.4.4.4',
'5.5.5.5'
]
ip_addresses.each do |address|
url = 'http://www.ip-adress.com/'
c = Curl::Easy.new(url)
c.interface = address
c.perform
ip = c.body_str.scan(/<h2>My IP address is: ([\d\.]{1,})<\/h2>/).first
puts "for #{address} got response: #{ip}"
end
I know this is old, but hopefully someone else finds this useful, as I needed this today. You can do the following:
http = Net::HTTP.new(uri.host, uri.port)
http.local_host = ip
response = http.request(request)
Note that you I don't believe you can use Net::HTTP.start, as it doesn't accept local_host as an option.
There is in fact a way to do this if you monkey patch TCPSocket:
https://gist.github.com/800214
Curb is awesome but won't work with Jruby so I've been looking into alternatives...
Doesn't look like you can do it with Net:HTTP. Here's the source
http://github.com/ruby/ruby/blob/trunk/lib/net/http.rb
Line 644 is where the connection is opened
s = timeout(#open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
The third and fourth arguments to TCPSocket.open are local_address and local_port, and since they're not specified, it's not possible. Looks like you'll have to go with curb.
Of course you can. I did as below:
# remote_host can be IP or hostname
uri = URI.parse( "http://" + remote_host )
http = Net::HTTP.new( uri.host, uri.port )
request = Net::HTTP::Get.new(uri.request_uri)
request.initialize_http_header( { "Host" => domain })
response = http.request( request )

Resources