Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- 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
Add Comment
Please, Sign In to add comment