#! /usr/bin/env ruby

# Copyright (c) 2006 Robbert Haarman
# 
# 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.
 
require 'socket'

## Defaults
nntp_port = 119
nntp_server = 'news.hnglo1.ov.home.nl'
smtp_port = 25
#smtp_server = 'mail.tilbu1.nb.home.nl'
smtp_server = 'mail.home.nl'
sender = 'inglorion@inglorion.net'
recipients = []

## Convert line ends to CRLF (if necessary)
def convert_to_crlf message
	message.gsub /([^\015])$/, "\\1\015"
end

## Strip Bcc header
def strip_bcc message
	message.gsub /^Bcc:.*\015\012/i, ''
end

## Read and return a response from the server
def read_response conn
	response = ''
	while true
		line = conn.gets
		response = response + line
		break if line =~ /^\d\d\d\s/
	end
	response
end

## Read server's response, and match it with the given regexp
## Return whether there was a match, and the response
def expect regexp, conn
	response = read_response conn
	[response =~ regexp, response]
end

## Send a request and verify the server's reply
## Returns whether the request was successful, and the reply
def request message, regexp, conn
	conn.print message + "\015\012"
	expect regexp, conn
end

## Try to send message from sender to recipients via server
def post_mail message, sender, recipients, server, port = 25
	conn = TCPSocket.new server, port
	ok, reply = expect /^220\s/, conn
	return [ok, reply] unless ok
	ok, reply = request 'EHLO ' + server, /^250\s/, conn
	return [ok, reply] unless ok
	ok, reply = request 'MAIL FROM: ' + sender, /^250\s/, conn
	return [ok, reply] unless ok
	recipients.each do |recipient|
		ok, reply = request 'RCPT TO: ' + recipient, /^250\s/, conn
		return [ok, reply] unless ok
	end
	ok, reply = request 'DATA', /^354\s/, conn
	return [ok, reply] unless ok
	ok, reply = request message + "\015\012.", /^250\s/, conn
	return [ok, reply] unless ok
	request 'QUIT', /^221\s/, conn
	conn.close
	[ok, reply]
end

## Post a message to the news server
def post_news message, server, port = 119
	conn = TCPSocket.new server, port
	ok, reply = expect /^200\s/, conn
	return [ok, reply] unless ok
	conn.print "MODE READER\015\012"
	read_response conn
	ok, reply = request 'POST', /^340\s/, conn
	return [ok, reply] unless ok
	ok, reply = request message + "\015\012.", /^240\s/, conn
	return [ok, reply] unless ok
	request 'QUIT', /^205\s/, conn
	conn.close
	[ok, reply]
end

## Parse command line
i = 0
while i < ARGV.length
	if ARGV[i] == '-f' || ARGV[i] == '--from'
		i = i + 1
		sender = ARGV[i]
	elsif ARGV[i] == '--nntp-port'
		i = i + 1
		nntp_port = ARGV[i].to_i
	elsif ARGV[i] == '-n' || ARGV[i] == '--nntp-server'
		i = i + 1
		nntp_server = ARGV[i]
	elsif ARGV[i] == '--smtp-port'
		i = i + 1
		smtp_port = ARGV[i].to_i
	elsif ARGV[i] == '-s' || ARGV[i] == '--smtp-server'
		i = i + 1
		smtp_server = ARGV[i]
	elsif ARGV[i] == '--'
		i = i + 1
		recipients = recipients + ARGV[i..-1]
		break
	elsif ARGV[i] =~ /^-/
		$stderr.puts 'Unknown option: ' + ARGV[i]
		exit 0x80
	else
		recipients = recipients << ARGV[i]
	end
	i = i + 1
end

## Read message
message = convert_to_crlf $stdin.read

## Split into headers and body
headers, body = message.split "\015\012\015\012", 2
headers << "\015\012"

## Remove Bcc header
headers = strip_bcc headers

## Reassemble message
message = headers + "\015\012" + body

## Test if the message needs to be submitted as mail
mail = recipients.length > 0

## Test if the message needs to be submitted as news
news = headers =~ /^Newsgroups: .+\015/

## Exit status
status = 0

## Post message to mail server, if necessary
if mail
	begin
		ok, reply = post_mail message, sender, recipients,
			smtp_server, smtp_port
		unless ok
			$stderr.puts "Mailing message failed." +
				"Server said:\n#{reply}"
			status = status | 1
		end
	rescue SocketError, Errno::ECONNREFUSED => e
		$stdout.puts "Error connecting to " +
			"#{smtp_server}:#{smtp_port}:\n#{e}"
			status = status | 1
	end
end

## Post message to news server, if necessary
if news
	begin
		ok, reply = post_news message, nntp_server, nntp_port
		unless ok
			$stderr.puts "Posting message to news server failed." +
				"Server said:\n#{reply}"
			status = status | 2
		end
	rescue SocketError, Errno::ECONNREFUSED => e
		$stdout.puts "Error connecting to " +
			"#{nntp_server}:#{nntp_port}:\n#{e}"
			status = status | 2
	end
end

exit status
