Created
January 4, 2009 03:29
-
-
Save epitron/42996 to your computer and use it in GitHub Desktop.
A 77 line SMTP server, and a 253 line POP3 server.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'gserver' | |
require 'rubygems' | |
require 'active_record' | |
dbconfig = YAML::load_file(File.dirname(__FILE__) + '/config/database.yml') | |
ActiveRecord::Base.establish_connection(dbconfig['development']) | |
# CREATE TABLE emails (id integer primary key autoincrement, mail_from, rcpt_to, subject, email, user_id integer); | |
# CREATE TABLE users (id integer primary key autoincrement, username, password, email); | |
class Email < ActiveRecord::Base | |
end | |
class User < ActiveRecord::Base | |
end | |
class SMTPServer < GServer | |
def serve(io) | |
@data_mode = false | |
@email_message = "" | |
puts "Connected" | |
io.print "220 hello\r\n" | |
loop do | |
if IO.select([io], nil, nil, 0.1) | |
data = io.readpartial(4096) | |
puts ">>" + data | |
@email_message << data | |
ok, op = process_line(data) | |
break unless ok | |
puts "<<" + op | |
io.print op | |
end | |
break if io.closed? | |
end | |
db_insert(@email_message) | |
io.print "221 bye\r\n" | |
io.close | |
end | |
def process_line(line) | |
if (@data_mode) && (line.chomp =~ /^\.$/) | |
@data_mode = false | |
return true, "220 OK\r\n" | |
elsif @data_mode | |
@email_body += line | |
return true, "" | |
elsif (line =~ /^(HELO|EHLO)/) | |
return true, "220 and..?\r\n" | |
elsif (line =~ /^QUIT/) | |
return false, "bye\r\n" | |
elsif (line =~ /^MAIL FROM\:/) | |
@mail_from = (/^MAIL FROM\:<(.+)>.*$/).match(line)[1] | |
return true, "220 OK\r\n" | |
elsif (line =~ /^RCPT TO\:/) | |
@rcpt_to = (/^RCPT TO\:<(.+)>.*$/).match(line)[1] | |
return true, "220 OK\r\n" | |
elsif (line =~ /^DATA/) | |
@data_mode = true | |
@email_body = '' | |
return true, "354 Enter message, ending with \".\" on a line by itself\r\n" | |
else | |
return true, "500 ERROR\r\n" | |
end | |
end | |
def db_insert(email) | |
subject = (/^Subject\: (.+)$/).match(@email_body)[1] | |
u = User.find(:first, :conditions => { :email => @rcpt_to }) | |
if u and @mail_from and @rcpt_to | |
Email.create(:mail_from => @mail_from, :rcpt_to => @rcpt_to, :subject => subject, :email => @email_body, :user_id => u.id) | |
end | |
end | |
end | |
a = SMTPServer.new(2225) | |
a.start | |
a.join |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'gserver' | |
require 'digest/md5' | |
require 'rubygems' | |
require 'active_record' | |
dbconfig = YAML::load_file(File.dirname(__FILE__) + '/config/database.yml') | |
ActiveRecord::Base.establish_connection(dbconfig['development']) | |
# CREATE TABLE emails (id integer primary key autoincrement, mail_from, rcpt_to, subject, email, user_id integer); | |
# CREATE TABLE users (id integer primary key autoincrement, username, password, email); | |
class Email < ActiveRecord::Base | |
def initialize(*kvars) | |
@deleted = false | |
super(kvars) | |
end | |
def deleted? | |
@deleted | |
end | |
def deleted=(deleted) | |
@deleted = deleted | |
end | |
end | |
class User < ActiveRecord::Base | |
end | |
class POP3Server < GServer | |
attr_writer :hostname | |
def serve(io) | |
@state = 'auth' | |
@failed = 0 | |
@apop_challenge = "<#{rand(10**4 - 1)}.#{rand(10**9 - 1)}@#{@hostname}>" | |
io.print("+OK POP3 server ready #{@apop_challenge}\r\n") | |
loop do | |
if IO.select([io], nil, nil, 0.1) | |
begin | |
data = io.readpartial(4096) | |
puts ">> #{data.chomp}" | |
ok, op = process_line(data) | |
break unless ok | |
puts "<< #{op.chomp}" | |
io.print op | |
rescue Exception | |
end | |
end | |
break if io.closed? | |
end | |
io.close unless io.closed? | |
end | |
def user(username) | |
@user = User.find(:first, :conditions => { :username => username }) | |
end | |
def pass(password) | |
return false unless @user | |
return false unless @user.password == password | |
true | |
end | |
def emails | |
@emails = [ Email.find_all_by_user_id(@user.id) ].flatten | |
end | |
def stat | |
msgs = bytes = 0 | |
@emails.each do |e| | |
next if e.deleted? | |
msgs += 1 | |
bytes += e.email.length | |
end | |
return msgs, bytes | |
end | |
def list(msgid = nil) | |
msgid = msgid.to_i if msgid | |
if msgid | |
return false if msgid > @emails.length or @emails[msgid-1].deleted? | |
return [ [msgid, @emails[msgid].email.length] ] | |
else | |
msgs = [] | |
@emails.each_with_index do |e,i| | |
msgs << [ i + 1, e.email.length ] | |
end | |
msgs | |
end | |
end | |
def retr(msgid) | |
msgid = msgid.to_i | |
return false if msgid > @emails.length or @emails[msgid-1].deleted? | |
@emails[msgid-1].email | |
end | |
def dele(msgid) | |
msgid = msgid.to_i | |
return false if msgid > @emails.length | |
@emails[msgid-1].deleted = true | |
end | |
def rset | |
@emails.each do |e| | |
e.deleted = false | |
end | |
end | |
def quit | |
@emails.each do |e| | |
if e.deleted? | |
Email.delete(e.id) | |
end | |
end | |
end | |
def apop(username, hash) | |
user(username) | |
return false unless @user | |
if Digest::MD5.new.update("#{@apop_challenge}#{@user.password}").hexdigest == hash | |
return true | |
end | |
false | |
end | |
def process_line(line) | |
line.chomp! | |
case @state | |
when 'auth' | |
case line | |
when /^QUIT$/ | |
return false, "+OK dewey POP3 server signing off\r\n" | |
when /^USER (.+)$/ | |
user($1) | |
if @user | |
return true, "+OK #{@user.username} is most welcome here\r\n" | |
else | |
@failed += 1 | |
if @failed > 2 | |
return false, "-ERR you're out!\r\n" | |
end | |
return true, "-ERR sorry, no mailbox for #{$1} here\r\n" | |
end | |
when /^PASS (.+)$/ | |
if pass($1) | |
@state = 'trans' | |
emails | |
msgs, bytes = stat | |
return true, "+OK #{@user.username}'s maildrop has #{msgs} messages (#{bytes} octets)\r\n" | |
else | |
@failed += 1 | |
if @failed > 2 | |
return false, "-ERR you're out!\r\n" | |
end | |
return true, "-ERR no dope.\r\n" | |
end | |
when /^APOP ([^\s]+) (.+)$/ | |
if apop($1,$2) | |
@state = 'trans' | |
emails | |
return true, "+OK #{@user.username} is most welcome here\r\n" | |
else | |
@failed += 1 | |
if @failed > 2 | |
return false, "-ERR you're out!\r\n" | |
end | |
return true, "-ERR sorry, no mailbox for #{$1} here\r\n" | |
end | |
end | |
when 'trans' | |
case line | |
when /^NOOP$/ | |
return true, "+OK\r\n" | |
when /^STAT$/ | |
msgs, bytes = stat | |
return true, "+OK #{msgs} #{bytes}\r\n" | |
when /^LIST$/ | |
msgs, bytes = stat | |
msg = "+OK #{msgs} messages (#{bytes} octets)\r\n" | |
list.each do |num, bytes| | |
msg += "#{num} #{bytes}\r\n" | |
end | |
msg += ".\r\n" | |
return true, msg | |
when /^LIST (\d+)$/ | |
msgs, bytes = stat | |
num, bytes = list($1) | |
if num | |
return true, "+OK #{num} #{bytes}\r\n" | |
else | |
return true, "-ERR no such message, only #{msgs} messages in maildrop\r\n" | |
end | |
when /^RETR (\d+)$/ | |
msg = retr($1) | |
if msg | |
msg = "+OK #{msg.length} octets\r\n" + msg | |
msg += "\r\n.\r\n" | |
else | |
msg = "-ERR no such message\r\n" | |
end | |
return true, msg | |
when /^DELE (\d+)$/ | |
if dele($1) | |
return true, "+OK message #{$1} deleted\r\n" | |
else | |
return true, "-ERR message #{$1} already deleted\r\n" | |
end | |
when /^RSET$/ | |
rset | |
msgs, bytes = stat | |
return true, "+OK maildrop has #{msgs} messages (#{bytes} octets)\r\n" | |
when /^QUIT$/ | |
@state = 'update' | |
quit | |
msgs, bytes = stat | |
if msgs > 0 | |
return true, "+OK dewey POP3 server signing off (#{msgs} messages left)\r\n" | |
else | |
return true, "+OK dewey POP3 server signing off (maildrop empty)\r\n" | |
end | |
when /^TOP (\d+) (\d+)$/ | |
lines = $2 | |
msg = retr($1) | |
unless msg | |
return true, "-ERR no such message\r\n" | |
end | |
cnt = nil | |
final = "" | |
msg.split(/\n/).each do |l| | |
final += l+"\n" | |
if cnt | |
cnt += 1 | |
break if cnt > lines | |
end | |
if l !~ /\w/ | |
cnt = 0 | |
end | |
end | |
return true, "+OK\r\n"+final+".\r\n" | |
when /^UIDL$/ | |
msgid = 0 | |
msg = '' | |
@email.each do |e| | |
msgid += 1 | |
next if e.deleted? | |
msg += "#{msgid} #{Digest::MD5.new.update(msg).hexdigest}\r\n" | |
end | |
return true, "+OK\r\n#{msg}.\r\n"; | |
end | |
when 'update' | |
case line | |
when /^QUIT$/ | |
return true, "+OK dewey POP3 server signing off\r\n" | |
end | |
end | |
return true, "-ERR unknown command\r\n" | |
end | |
end | |
a = POP3Server.new(2226,'',4,$stderr,true,true) | |
a.hostname = "localhost" | |
a.start | |
a.join |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment