boomerang-mocksmtpd 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +17 -0
- data/README +59 -0
- data/Rakefile +144 -0
- data/bin/mocksmtpd +6 -0
- data/lib/mocksmtpd.rb +312 -0
- data/lib/smtpserver.rb +257 -0
- data/templates/html/index.erb +46 -0
- data/templates/html/index_entry.erb +8 -0
- data/templates/html/mail.erb +16 -0
- data/templates/mocksmtpd.conf.erb +10 -0
- data/test/mocksmtpd_test.rb +5 -0
- data/test/test_helper.rb +3 -0
- metadata +78 -0
    
        data/ChangeLog
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            == 0.0.3 / 2008-11-08
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * rescue Process.eid NotImplementedError.
         | 
| 4 | 
            +
            * warn when Process.eid can't be changed.
         | 
| 5 | 
            +
            * read log level from config file.
         | 
| 6 | 
            +
            * change param name from Loglevel to LogLevel
         | 
| 7 | 
            +
            * add debug log.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            == 0.0.2 / 2008-11-03
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * release gem version.
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            == 0.0.1 / 2008-11-03
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            * moved into github
         | 
| 16 | 
            +
            * initial release
         | 
| 17 | 
            +
             | 
    
        data/README
    ADDED
    
    | @@ -0,0 +1,59 @@ | |
| 1 | 
            +
             | 
| 2 | 
            +
            = mocksmtpd
         | 
| 3 | 
            +
             | 
| 4 | 
            +
             | 
| 5 | 
            +
            == Description
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Mocksmtpd is a SMTP server for developping and testing web application. This SMTP server does not send mail anywhere. Otherwise, save all mails as HTML format.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            You can test mail using browser testing tools like Selenium.
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            === Screenshot
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            http://koseki2.tumblr.com/post/57148631
         | 
| 14 | 
            +
            http://koseki2.tumblr.com/post/57148564
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            == Installation
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            === Archive Installation
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            	rake install
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            === Gem Installation
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            	gem sources -a http://gems.github.com
         | 
| 25 | 
            +
            	gem install koseki-mocksmtpd
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            == Quick Start
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            	$ mocksmtpd init
         | 
| 30 | 
            +
            	$ cd ./mocksmtpd
         | 
| 31 | 
            +
            	$ sudo mocksmtpd
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            == Usaage
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            	mocksmtpd init [dirname] ... create log,inbox dir and config file.
         | 
| 36 | 
            +
            	mocksmtpd                ... start as console mode.
         | 
| 37 | 
            +
            	mocksmtpd start          ... start as daemon.
         | 
| 38 | 
            +
            	mocksmtpd stop           ... stop running daemon.
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            === Options
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            The default config file is ./mocksmtpd.conf, but you can specify any other file using -f option.
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            	-f / --config=FILE       ... Specify config file.
         | 
| 45 | 
            +
            	--help                   ... Show help.
         | 
| 46 | 
            +
            	--silent                 ... Disable console output.
         | 
| 47 | 
            +
            	--version                ... Show version.
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            == Features/Problems
         | 
| 50 | 
            +
             | 
| 51 | 
            +
             | 
| 52 | 
            +
            == Synopsis
         | 
| 53 | 
            +
             | 
| 54 | 
            +
             | 
| 55 | 
            +
            == Copyright
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            Author:: KOSEKI Kengo <koseki@gmail.com>
         | 
| 58 | 
            +
            Copyright:: Copyright (c) 2008 
         | 
| 59 | 
            +
            License:: Ruby Licence
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,144 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'rake'
         | 
| 3 | 
            +
            require 'rake/clean'
         | 
| 4 | 
            +
            require 'rake/testtask'
         | 
| 5 | 
            +
            require 'rake/packagetask'
         | 
| 6 | 
            +
            require 'rake/gempackagetask'
         | 
| 7 | 
            +
            require 'rake/rdoctask'
         | 
| 8 | 
            +
            require 'rake/contrib/rubyforgepublisher'
         | 
| 9 | 
            +
            require 'rake/contrib/sshpublisher'
         | 
| 10 | 
            +
            require 'fileutils'
         | 
| 11 | 
            +
            require 'lib/mocksmtpd'
         | 
| 12 | 
            +
            include FileUtils
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            NAME              = "mocksmtpd"
         | 
| 15 | 
            +
            AUTHOR            = "KOSEKI Kengo"
         | 
| 16 | 
            +
            EMAIL             = "koseki@gmail.com"
         | 
| 17 | 
            +
            DESCRIPTION       = "Mock SMTP server for development/testing."
         | 
| 18 | 
            +
            RUBYFORGE_PROJECT = "mocksmtpd"
         | 
| 19 | 
            +
            HOMEPATH          = "http://github.com/koseki/mocksmtpd/"
         | 
| 20 | 
            +
            BIN_FILES         = %w(mocksmtpd)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            VERS              = Mocksmtpd::VERSION
         | 
| 23 | 
            +
            REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
         | 
| 24 | 
            +
            CLEAN.include ['**/.*.sw?', '*.gem', '.config']
         | 
| 25 | 
            +
            RDOC_OPTS = [
         | 
| 26 | 
            +
            	'--title', "#{NAME} documentation",
         | 
| 27 | 
            +
            	"--charset", "utf-8",
         | 
| 28 | 
            +
            	"--opname", "index.html",
         | 
| 29 | 
            +
            	"--line-numbers",
         | 
| 30 | 
            +
            	"--main", "README",
         | 
| 31 | 
            +
            	"--inline-source",
         | 
| 32 | 
            +
            ]
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            task :default => [:test]
         | 
| 35 | 
            +
            task :package => [:clean]
         | 
| 36 | 
            +
             | 
| 37 | 
            +
            Rake::TestTask.new("test") do |t|
         | 
| 38 | 
            +
            	t.libs   << "test"
         | 
| 39 | 
            +
            	t.pattern = "test/**/*_test.rb"
         | 
| 40 | 
            +
            	t.verbose = true
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            spec = Gem::Specification.new do |s|
         | 
| 44 | 
            +
            	s.name              = NAME
         | 
| 45 | 
            +
            	s.version           = VERS
         | 
| 46 | 
            +
            	s.platform          = Gem::Platform::RUBY
         | 
| 47 | 
            +
            	s.has_rdoc          = true
         | 
| 48 | 
            +
            	s.extra_rdoc_files  = ["README", "ChangeLog"]
         | 
| 49 | 
            +
            	s.rdoc_options     += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
         | 
| 50 | 
            +
            	s.summary           = DESCRIPTION
         | 
| 51 | 
            +
            	s.description       = DESCRIPTION
         | 
| 52 | 
            +
            	s.author            = AUTHOR
         | 
| 53 | 
            +
            	s.email             = EMAIL
         | 
| 54 | 
            +
            	s.homepage          = HOMEPATH
         | 
| 55 | 
            +
            	s.executables       = BIN_FILES
         | 
| 56 | 
            +
            	s.rubyforge_project = RUBYFORGE_PROJECT
         | 
| 57 | 
            +
            	s.bindir            = "bin"
         | 
| 58 | 
            +
            	s.require_path      = "lib"
         | 
| 59 | 
            +
            	#s.autorequire       = ""
         | 
| 60 | 
            +
            	s.test_files        = Dir["test/*_test.rb"]
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            	#s.add_dependency('activesupport', '>=1.3.1')
         | 
| 63 | 
            +
            	#s.required_ruby_version = '>= 1.8.2'
         | 
| 64 | 
            +
             | 
| 65 | 
            +
            	s.files = %w(README ChangeLog Rakefile) +
         | 
| 66 | 
            +
            		Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") + 
         | 
| 67 | 
            +
            		Dir.glob("ext/**/*.{h,c,rb}") +
         | 
| 68 | 
            +
            		Dir.glob("examples/**/*.rb") +
         | 
| 69 | 
            +
            		Dir.glob("tools/*.rb") +
         | 
| 70 | 
            +
            		Dir.glob("rails/*.rb")
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            	s.extensions = FileList["ext/**/extconf.rb"].to_a
         | 
| 73 | 
            +
            end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            Rake::GemPackageTask.new(spec) do |p|
         | 
| 76 | 
            +
            	p.need_tar = true
         | 
| 77 | 
            +
            	p.gem_spec = spec
         | 
| 78 | 
            +
            end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
            task :install do
         | 
| 81 | 
            +
            	name = "#{NAME}-#{VERS}.gem"
         | 
| 82 | 
            +
            	sh %{rake package}
         | 
| 83 | 
            +
            	sh %{sudo gem install pkg/#{name}}
         | 
| 84 | 
            +
            end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            task :uninstall => [:clean] do
         | 
| 87 | 
            +
            	sh %{sudo gem uninstall #{NAME}}
         | 
| 88 | 
            +
            end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
             | 
| 91 | 
            +
            Rake::RDocTask.new do |rdoc|
         | 
| 92 | 
            +
            	rdoc.rdoc_dir = 'html'
         | 
| 93 | 
            +
            	rdoc.options += RDOC_OPTS
         | 
| 94 | 
            +
            	rdoc.template = "resh"
         | 
| 95 | 
            +
            	#rdoc.template = "#{ENV['template']}.rb" if ENV['template']
         | 
| 96 | 
            +
            	if ENV['DOC_FILES']
         | 
| 97 | 
            +
            		rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
         | 
| 98 | 
            +
            	else
         | 
| 99 | 
            +
            		rdoc.rdoc_files.include('README', 'ChangeLog')
         | 
| 100 | 
            +
            		rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 101 | 
            +
            		rdoc.rdoc_files.include('ext/**/*.c')
         | 
| 102 | 
            +
            	end
         | 
| 103 | 
            +
            end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            desc "Publish to RubyForge"
         | 
| 106 | 
            +
            task :rubyforge => [:rdoc, :package] do
         | 
| 107 | 
            +
            	require 'rubyforge'
         | 
| 108 | 
            +
            	Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, '').upload
         | 
| 109 | 
            +
            end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            desc 'Package and upload the release to rubyforge.'
         | 
| 112 | 
            +
            task :release => [:clean, :package] do |t|
         | 
| 113 | 
            +
            	v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
         | 
| 114 | 
            +
            	abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
         | 
| 115 | 
            +
            	pkg = "pkg/#{NAME}-#{VERS}"
         | 
| 116 | 
            +
             | 
| 117 | 
            +
            	require 'rubyforge'
         | 
| 118 | 
            +
            	rf = RubyForge.new.configure
         | 
| 119 | 
            +
            	puts "Logging in"
         | 
| 120 | 
            +
            	rf.login
         | 
| 121 | 
            +
             | 
| 122 | 
            +
            	c = rf.userconfig
         | 
| 123 | 
            +
            #	c["release_notes"] = description if description
         | 
| 124 | 
            +
            #	c["release_changes"] = changes if changes
         | 
| 125 | 
            +
            	c["preformatted"] = true
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            	files = [
         | 
| 128 | 
            +
            		"#{pkg}.tgz",
         | 
| 129 | 
            +
            		"#{pkg}.gem"
         | 
| 130 | 
            +
            	].compact
         | 
| 131 | 
            +
             | 
| 132 | 
            +
            	puts "Releasing #{NAME} v. #{VERS}"
         | 
| 133 | 
            +
            	rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
         | 
| 134 | 
            +
            end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
            desc 'Show information about the gem.'
         | 
| 137 | 
            +
            task :debug_gem do
         | 
| 138 | 
            +
            	puts spec.to_ruby
         | 
| 139 | 
            +
            end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
            desc 'Update gem spec'
         | 
| 142 | 
            +
            task :gemspec do
         | 
| 143 | 
            +
              open("#{NAME}.gemspec", 'w').write spec.to_ruby
         | 
| 144 | 
            +
            end
         | 
    
        data/bin/mocksmtpd
    ADDED
    
    
    
        data/lib/mocksmtpd.rb
    ADDED
    
    | @@ -0,0 +1,312 @@ | |
| 1 | 
            +
            $:.unshift File.dirname(__FILE__) # for test/development
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'optparse'
         | 
| 4 | 
            +
            require 'pathname'
         | 
| 5 | 
            +
            require 'yaml'
         | 
| 6 | 
            +
            require 'erb'
         | 
| 7 | 
            +
            require 'nkf'
         | 
| 8 | 
            +
            require 'smtpserver'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            class Mocksmtpd
         | 
| 11 | 
            +
              VERSION = '0.0.3'
         | 
| 12 | 
            +
              TEMPLATE_DIR = Pathname.new(File.dirname(__FILE__)) + "../templates"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              include ERB::Util
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              def initialize(argv)
         | 
| 17 | 
            +
                @opt = OptionParser.new
         | 
| 18 | 
            +
                @opt.banner = "Usage: #$0 [options] [start|stop|init PATH]"
         | 
| 19 | 
            +
                @opt.on("-f FILE", "--config=FILE", "Specify mocksmtpd.conf") do |v|
         | 
| 20 | 
            +
                  @conf_file = v
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                @opt.on("--version", "Show version string `#{VERSION}'") do
         | 
| 24 | 
            +
                  puts VERSION
         | 
| 25 | 
            +
                  exit
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                @opt.on("--silent", "Suppress all output") do
         | 
| 29 | 
            +
                  @silent = true
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                @opt.parse!(argv)
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                if argv.empty?
         | 
| 35 | 
            +
                  @command = "console"
         | 
| 36 | 
            +
                else
         | 
| 37 | 
            +
                  @command = argv.shift
         | 
| 38 | 
            +
                  commands = %w(start stop init)
         | 
| 39 | 
            +
                  unless commands.include? @command
         | 
| 40 | 
            +
                    opterror "No such command: #{@command}"
         | 
| 41 | 
            +
                    exit 1
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                if @command == "init"
         | 
| 46 | 
            +
                  @init_dir = argv.shift || "mocksmtpd"
         | 
| 47 | 
            +
                  if test(?e, @init_dir)
         | 
| 48 | 
            +
                    opterror("Init path already exists: #{@init_dir}")
         | 
| 49 | 
            +
                    exit 1
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              def opterror(msg)
         | 
| 55 | 
            +
                puts("Error: #{msg}")
         | 
| 56 | 
            +
                puts(@opt.help)
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              def load_conf
         | 
| 60 | 
            +
                @conf_file = Pathname.new(@conf_file || "./mocksmtpd.conf")
         | 
| 61 | 
            +
                unless @conf_file.exist? && @conf_file.readable?
         | 
| 62 | 
            +
                  opterror "Can't load config file: #{@conf_file}"
         | 
| 63 | 
            +
                  exit 1
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
                @conf_file = @conf_file.realpath
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                @conf = {}
         | 
| 68 | 
            +
                YAML.load_file(@conf_file).each do |k,v|
         | 
| 69 | 
            +
                  @conf[k.intern] = v
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                @inbox = resolve_conf_path(@conf[:InboxDir])
         | 
| 73 | 
            +
                @logfile = resolve_conf_path(@conf[:LogFile])
         | 
| 74 | 
            +
                @pidfile = resolve_conf_path(@conf[:PidFile])
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                @templates = load_templates
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def resolve_conf_path(path)
         | 
| 80 | 
            +
                result = nil
         | 
| 81 | 
            +
                if path[0] == ?/
         | 
| 82 | 
            +
                  result = Pathname.new(path)
         | 
| 83 | 
            +
                else
         | 
| 84 | 
            +
                  result = @conf_file.parent + path
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
                return result.cleanpath
         | 
| 87 | 
            +
              end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
              def run
         | 
| 90 | 
            +
                send(@command)
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              def load_templates
         | 
| 94 | 
            +
                result = {}
         | 
| 95 | 
            +
                result[:mail] = template("html/mail")
         | 
| 96 | 
            +
                result[:index] = template("html/index")
         | 
| 97 | 
            +
                result[:index_entry] = template("html/index_entry")
         | 
| 98 | 
            +
                return result
         | 
| 99 | 
            +
              end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
              def template(name)
         | 
| 102 | 
            +
                path = TEMPLATE_DIR + "#{name}.erb"
         | 
| 103 | 
            +
                src = path.read
         | 
| 104 | 
            +
                return ERB.new(src, nil, "%-")
         | 
| 105 | 
            +
              end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
              def init
         | 
| 108 | 
            +
                Dir.mkdir(@init_dir)
         | 
| 109 | 
            +
                puts "Created: #{@init_dir}/" unless @silent
         | 
| 110 | 
            +
                path = Pathname.new(@init_dir)
         | 
| 111 | 
            +
                Dir.mkdir(path + "inbox")
         | 
| 112 | 
            +
                puts "Created: #{path + 'inbox'}/" unless @silent
         | 
| 113 | 
            +
                Dir.mkdir(path + "log")
         | 
| 114 | 
            +
                puts "Created: #{path + 'log'}/" unless @silent
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                open(path + "mocksmtpd.conf", "w") do |io|
         | 
| 117 | 
            +
                  io << template("mocksmtpd.conf").result(binding)
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
                puts "Created: #{path + 'mocksmtpd.conf'}" unless @silent
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              def stop
         | 
| 123 | 
            +
                load_conf
         | 
| 124 | 
            +
                unless @pidfile.exist?
         | 
| 125 | 
            +
                  puts "ERROR: pid file does not exist: #{@pidfile}"
         | 
| 126 | 
            +
                  exit 1
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
                unless @pidfile.readable?
         | 
| 129 | 
            +
                  puts "ERROR: Can't read pid file: #{@pidfile}"
         | 
| 130 | 
            +
                  exit 1
         | 
| 131 | 
            +
                end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                pid = File.read(@pidfile)
         | 
| 134 | 
            +
                print "Stopping #{pid}..." unless @silent
         | 
| 135 | 
            +
                #system "kill -TERM #{pid}"
         | 
| 136 | 
            +
                system "taskkill /F /PID #{pid} 1>NUL"
         | 
| 137 | 
            +
                puts "done" unless @silent
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
              def create_logger(file = nil)
         | 
| 141 | 
            +
                file = file.to_s.strip
         | 
| 142 | 
            +
                file = nil if file.empty?
         | 
| 143 | 
            +
                lvstr = @conf[:LogLevel].to_s.strip
         | 
| 144 | 
            +
                lvstr = "ERROR" if @silent
         | 
| 145 | 
            +
                lvstr = "INFO" unless %w{FATAL ERROR WARN INFO DEBUG}.include?(lvstr)
         | 
| 146 | 
            +
                level = WEBrick::BasicLog.const_get(lvstr)
         | 
| 147 | 
            +
                logger = WEBrick::Log.new(file, level)
         | 
| 148 | 
            +
                logger.debug("Logger initialized")
         | 
| 149 | 
            +
                return logger
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              def start
         | 
| 153 | 
            +
                load_conf
         | 
| 154 | 
            +
                @logger = create_logger(@logfile)
         | 
| 155 | 
            +
                @daemon = true
         | 
| 156 | 
            +
                smtpd
         | 
| 157 | 
            +
              end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
              def console
         | 
| 160 | 
            +
                load_conf
         | 
| 161 | 
            +
                @logger = create_logger
         | 
| 162 | 
            +
                @daemon = false
         | 
| 163 | 
            +
                smtpd
         | 
| 164 | 
            +
              end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
              def create_pid_file
         | 
| 167 | 
            +
                if @pidfile.exist?
         | 
| 168 | 
            +
                  pid = @pidfile.read
         | 
| 169 | 
            +
                  @logger.warn("pid file already exists: pid=#{pid}")
         | 
| 170 | 
            +
                  exit 1
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
                pid = Process.pid
         | 
| 173 | 
            +
                open(@pidfile, "w") do |io|
         | 
| 174 | 
            +
                  io << pid
         | 
| 175 | 
            +
                end
         | 
| 176 | 
            +
                @logger.debug("pid file saved: pid=#{pid} file=#{@pidfile}")
         | 
| 177 | 
            +
              end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
              def delete_pid_file
         | 
| 180 | 
            +
                File.delete(@pidfile)
         | 
| 181 | 
            +
                @logger.debug("pid file deleted: file=#{@pidfile}")
         | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
              def init_permission
         | 
| 185 | 
            +
                File.umask(@conf[:Umask]) unless @conf[:Umask].nil?
         | 
| 186 | 
            +
                stat = File::Stat.new(@conf_file)
         | 
| 187 | 
            +
                uid = stat.uid
         | 
| 188 | 
            +
                gid = stat.gid
         | 
| 189 | 
            +
                begin
         | 
| 190 | 
            +
                  Process.egid = gid
         | 
| 191 | 
            +
                  Process.euid = uid
         | 
| 192 | 
            +
                rescue NotImplementedError => e
         | 
| 193 | 
            +
                  @logger.debug("Process.euid= not implemented.")
         | 
| 194 | 
            +
                rescue Errno::EPERM => e
         | 
| 195 | 
            +
                  @logger.warn("could not change euid/egid. #{e}")
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
              end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
              def smtpd
         | 
| 200 | 
            +
                start_cb = Proc.new do
         | 
| 201 | 
            +
                  @logger.info("Inbox: #{@inbox}")
         | 
| 202 | 
            +
                  @logger.debug("LogFile: #{@logfile}")
         | 
| 203 | 
            +
                  @logger.debug("PidFile: #{@pidfile}")
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  begin
         | 
| 206 | 
            +
                    init_permission
         | 
| 207 | 
            +
                    create_pid_file #if @daemon
         | 
| 208 | 
            +
                  rescue => e
         | 
| 209 | 
            +
                    @logger.error("Start: #{e}")
         | 
| 210 | 
            +
                    raise e
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                stop_cb = Proc.new do
         | 
| 215 | 
            +
                  begin
         | 
| 216 | 
            +
                    delete_pid_file #if @daemon
         | 
| 217 | 
            +
                  rescue => e
         | 
| 218 | 
            +
                    @logger.error("Stop: #{e}")
         | 
| 219 | 
            +
                    raise e
         | 
| 220 | 
            +
                  end
         | 
| 221 | 
            +
                end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                data_cb = Proc.new do |src, sender, recipients|
         | 
| 224 | 
            +
                  recieve_mail(src, sender, recipients)
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                @conf[:ServerType] = @daemon ? WEBrick::Daemon : nil
         | 
| 228 | 
            +
                @conf[:Logger] = @logger
         | 
| 229 | 
            +
                @conf[:StartCallback] = start_cb
         | 
| 230 | 
            +
                @conf[:StopCallback] = stop_cb
         | 
| 231 | 
            +
                @conf[:DataHook] = data_cb
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                server = SMTPServer.new(@conf)
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                [:INT, :TERM].each do |signal|
         | 
| 236 | 
            +
                  Signal.trap(signal) { server.shutdown }
         | 
| 237 | 
            +
                end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                server.start
         | 
| 240 | 
            +
              end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
              def recieve_mail(src, sender, recipients)
         | 
| 243 | 
            +
                @logger.info "mail recieved from #{sender}"
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                mail = parse_mail(src, sender, recipients)
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                save_mail(mail)
         | 
| 248 | 
            +
                save_index(mail)
         | 
| 249 | 
            +
              end
         | 
| 250 | 
            +
             | 
| 251 | 
            +
              def parse_mail(src, sender, recipients)
         | 
| 252 | 
            +
                src = NKF.nkf("-wm", src)
         | 
| 253 | 
            +
                subject = src.match(/^Subject:\s*(.+)/i).to_a[1].to_s.strip
         | 
| 254 | 
            +
                date = src.match(/^Date:\s*(.+)/i).to_a[1].to_s.strip
         | 
| 255 | 
            +
             | 
| 256 | 
            +
                src = ERB::Util.h(src)
         | 
| 257 | 
            +
                src = src.gsub(%r{https?://[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+},'<a href="\0">\0</a>')
         | 
| 258 | 
            +
                src = src.gsub(/(?:\r\n|\r|\n)/, "<br />\n")
         | 
| 259 | 
            +
              
         | 
| 260 | 
            +
                if date.empty?
         | 
| 261 | 
            +
                  date = Time.now
         | 
| 262 | 
            +
                else
         | 
| 263 | 
            +
                  date = Time.parse(date)
         | 
| 264 | 
            +
                end
         | 
| 265 | 
            +
              
         | 
| 266 | 
            +
                mail = {
         | 
| 267 | 
            +
                  :source => src,
         | 
| 268 | 
            +
                  :sender => sender,
         | 
| 269 | 
            +
                  :recipients => recipients,
         | 
| 270 | 
            +
                  :subject => subject,
         | 
| 271 | 
            +
                  :date => date,
         | 
| 272 | 
            +
                }
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                format = "%Y%m%d%H%M%S"
         | 
| 275 | 
            +
                fname = date.strftime(format) + ".html"
         | 
| 276 | 
            +
                while @inbox.join(fname).exist?
         | 
| 277 | 
            +
                  date += 1
         | 
| 278 | 
            +
                  fname = date.strftime(format) + ".html"
         | 
| 279 | 
            +
                end
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                mail[:file] = fname
         | 
| 282 | 
            +
                mail[:path] = @inbox.join(fname)
         | 
| 283 | 
            +
             | 
| 284 | 
            +
                return mail
         | 
| 285 | 
            +
              end
         | 
| 286 | 
            +
             | 
| 287 | 
            +
              def save_mail(mail)
         | 
| 288 | 
            +
                open(mail[:path], "w") do |io|
         | 
| 289 | 
            +
                  io << @templates[:mail].result(binding)
         | 
| 290 | 
            +
                end
         | 
| 291 | 
            +
                @logger.debug("mail saved: #{mail[:path]}")
         | 
| 292 | 
            +
              end
         | 
| 293 | 
            +
             | 
| 294 | 
            +
              def save_index(mail)
         | 
| 295 | 
            +
                path = @inbox + "index.html"
         | 
| 296 | 
            +
                unless File.exist?(path)
         | 
| 297 | 
            +
                  open(path, "w") do |io|
         | 
| 298 | 
            +
                    io << @templates[:index].result(binding)
         | 
| 299 | 
            +
                  end
         | 
| 300 | 
            +
                end
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                htmlsrc = File.read(path)
         | 
| 303 | 
            +
                add = @templates[:index_entry].result(binding)
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                htmlsrc.sub!(/<!-- ADD -->/, add)
         | 
| 306 | 
            +
                open(path, "w") do |io|
         | 
| 307 | 
            +
                  io << htmlsrc
         | 
| 308 | 
            +
                end
         | 
| 309 | 
            +
                @logger.debug("index saved: #{path}")
         | 
| 310 | 
            +
              end
         | 
| 311 | 
            +
             | 
| 312 | 
            +
            end
         | 
    
        data/lib/smtpserver.rb
    ADDED
    
    | @@ -0,0 +1,257 @@ | |
| 1 | 
            +
            # http://tmtm.org/ja/ruby/smtpd/
         | 
| 2 | 
            +
            # http://rubyist.g.hatena.ne.jp/muscovyduck/20070707/p1
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'webrick'
         | 
| 5 | 
            +
            require 'tempfile'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module GetsSafe
         | 
| 8 | 
            +
              def gets_safe(rs = nil, timeout = @timeout, maxlength = @maxlength)
         | 
| 9 | 
            +
                rs = $/ unless rs
         | 
| 10 | 
            +
                f = self.kind_of?(IO) ? self : STDIN
         | 
| 11 | 
            +
                @gets_safe_buf = '' unless @gets_safe_buf
         | 
| 12 | 
            +
                until @gets_safe_buf.include? rs do
         | 
| 13 | 
            +
                  if maxlength and @gets_safe_buf.length > maxlength then
         | 
| 14 | 
            +
                    raise Errno::E2BIG, 'too long'
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                  if IO.select([f], nil, nil, timeout) == nil then
         | 
| 17 | 
            +
                    raise Errno::ETIMEDOUT, 'timeout exceeded'
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                  begin
         | 
| 20 | 
            +
                    @gets_safe_buf << f.sysread(4096)
         | 
| 21 | 
            +
                  rescue EOFError, Errno::ECONNRESET
         | 
| 22 | 
            +
                    return @gets_safe_buf.empty? ? nil : @gets_safe_buf.slice!(0..-1)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
                p = @gets_safe_buf.index rs
         | 
| 26 | 
            +
                if maxlength and p > maxlength then
         | 
| 27 | 
            +
                  raise Errno::E2BIG, 'too long'
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                return @gets_safe_buf.slice!(0, p+rs.length)
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
              attr_accessor :timeout, :maxlength
         | 
| 32 | 
            +
            end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            class SMTPD
         | 
| 35 | 
            +
              class Error < StandardError; end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              def initialize(sock, domain)
         | 
| 38 | 
            +
                @sock = sock
         | 
| 39 | 
            +
                @domain = domain
         | 
| 40 | 
            +
                @error_interval = 5
         | 
| 41 | 
            +
                class << @sock
         | 
| 42 | 
            +
                  include GetsSafe
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
                @helo_hook = nil
         | 
| 45 | 
            +
                @mail_hook = nil
         | 
| 46 | 
            +
                @rcpt_hook = nil
         | 
| 47 | 
            +
                @data_hook = nil
         | 
| 48 | 
            +
                @data_each_line = nil
         | 
| 49 | 
            +
                @rset_hook = nil
         | 
| 50 | 
            +
                @noop_hook = nil
         | 
| 51 | 
            +
                @quit_hook = nil
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
              attr_writer :helo_hook, :mail_hook, :rcpt_hook, :data_hook,
         | 
| 54 | 
            +
                          :data_each_line, :rset_hook, :noop_hook, :quit_hook
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              def start
         | 
| 57 | 
            +
                @helo_name = nil
         | 
| 58 | 
            +
                @sender = nil
         | 
| 59 | 
            +
                @recipients = []
         | 
| 60 | 
            +
                catch(:close) do
         | 
| 61 | 
            +
                  puts_safe "220 #{@domain} service ready"
         | 
| 62 | 
            +
                  while comm = @sock.gets_safe do
         | 
| 63 | 
            +
              catch :next_comm do
         | 
| 64 | 
            +
                comm.sub!(/\r?\n/, '')
         | 
| 65 | 
            +
                comm, arg = comm.split(/\s+/,2)
         | 
| 66 | 
            +
                      break if comm == nil
         | 
| 67 | 
            +
                case comm.upcase
         | 
| 68 | 
            +
                when 'EHLO' then comm_helo arg
         | 
| 69 | 
            +
                when 'HELO' then comm_helo arg
         | 
| 70 | 
            +
                when 'MAIL' then comm_mail arg
         | 
| 71 | 
            +
                when 'RCPT' then comm_rcpt arg
         | 
| 72 | 
            +
                when 'DATA' then comm_data arg
         | 
| 73 | 
            +
                when 'RSET' then comm_rset arg
         | 
| 74 | 
            +
                when 'NOOP' then comm_noop arg
         | 
| 75 | 
            +
                when 'QUIT' then comm_quit arg
         | 
| 76 | 
            +
                else
         | 
| 77 | 
            +
                  error '502 Error: command not implemented'
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
              def line_length_limit=(n)
         | 
| 85 | 
            +
                @sock.maxlength = n
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              def input_timeout=(n)
         | 
| 89 | 
            +
                @sock.timeout = n
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              attr_reader :line_length_limit, :input_timeout
         | 
| 93 | 
            +
              attr_accessor :error_interval
         | 
| 94 | 
            +
              attr_accessor :use_file, :max_size
         | 
| 95 | 
            +
             | 
| 96 | 
            +
              private
         | 
| 97 | 
            +
              def comm_helo(arg)
         | 
| 98 | 
            +
                if arg == nil or arg.split.size != 1 then
         | 
| 99 | 
            +
                  error '501 Syntax: HELO hostname'
         | 
| 100 | 
            +
                end
         | 
| 101 | 
            +
                @helo_hook.call(arg) if @helo_hook
         | 
| 102 | 
            +
                @helo_name = arg
         | 
| 103 | 
            +
                reply "250 #{@domain}"
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
              def comm_mail(arg)
         | 
| 107 | 
            +
                if @sender != nil then
         | 
| 108 | 
            +
                  error '503 Error: nested MAIL command'
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
                if arg !~ /^FROM:/i then
         | 
| 111 | 
            +
                  error '501 Syntax: MAIL FROM: <address>'
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
                sender = parse_addr $'
         | 
| 114 | 
            +
                if sender == nil then
         | 
| 115 | 
            +
                  error '501 Syntax: MAIL FROM: <address>'
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
                @mail_hook.call(sender) if @mail_hook
         | 
| 118 | 
            +
                @sender = sender
         | 
| 119 | 
            +
                reply '250 Ok'
         | 
| 120 | 
            +
              end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
              def comm_rcpt(arg)
         | 
| 123 | 
            +
                if @sender == nil then
         | 
| 124 | 
            +
                  error '503 Error: need MAIL command'
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
                if arg !~ /^TO:/i then
         | 
| 127 | 
            +
                  error '501 Syntax: RCPT TO: <address>'
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
                rcpt = parse_addr $'
         | 
| 130 | 
            +
                if rcpt == nil then
         | 
| 131 | 
            +
                  error '501 Syntax: RCPT TO: <address>'
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
                @rcpt_hook.call(rcpt) if @rcpt_hook
         | 
| 134 | 
            +
                @recipients << rcpt
         | 
| 135 | 
            +
                reply '250 Ok'
         | 
| 136 | 
            +
              end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
              def comm_data(arg)
         | 
| 139 | 
            +
                if @recipients.size == 0 then
         | 
| 140 | 
            +
                  error '503 Error: need RCPT command'
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
                if arg != nil then
         | 
| 143 | 
            +
                  error '501 Syntax: DATA'
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
                reply '354 End data with <CR><LF>.<CR><LF>'
         | 
| 146 | 
            +
                if @data_hook
         | 
| 147 | 
            +
                  tmpf = @use_file ? Tempfile.new('smtpd') : ''
         | 
| 148 | 
            +
                end
         | 
| 149 | 
            +
                size = 0
         | 
| 150 | 
            +
                loop do
         | 
| 151 | 
            +
                  l = @sock.gets_safe
         | 
| 152 | 
            +
                  if l == nil then
         | 
| 153 | 
            +
              raise SMTPD::Error, 'unexpected EOF'
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                  if l.chomp == '.' then break end
         | 
| 156 | 
            +
                  if l[0] == ?. then
         | 
| 157 | 
            +
              l[0,1] = ''
         | 
| 158 | 
            +
                  end
         | 
| 159 | 
            +
                  size += l.size
         | 
| 160 | 
            +
                  if @max_size and @max_size < size then
         | 
| 161 | 
            +
              error '552 Error: message too large'
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
                  @data_each_line.call(l) if @data_each_line
         | 
| 164 | 
            +
                  tmpf << l if @data_hook
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
                if @data_hook then
         | 
| 167 | 
            +
                  if @use_file then
         | 
| 168 | 
            +
              tmpf.pos = 0
         | 
| 169 | 
            +
                    @data_hook.call(tmpf, @sender, @recipients)
         | 
| 170 | 
            +
                    tmpf.close(true)
         | 
| 171 | 
            +
                  else
         | 
| 172 | 
            +
                    @data_hook.call(tmpf, @sender, @recipients)
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
                reply '250 Ok'
         | 
| 176 | 
            +
                @sender = nil
         | 
| 177 | 
            +
                @recipients = []
         | 
| 178 | 
            +
              end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
              def comm_rset(arg)
         | 
| 181 | 
            +
                if arg != nil then
         | 
| 182 | 
            +
                  error '501 Syntax: RSET'
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
                @rset_hook.call(@sender, @recipients) if @rset_hook
         | 
| 185 | 
            +
                reply '250 Ok'
         | 
| 186 | 
            +
                @sender = nil
         | 
| 187 | 
            +
                @recipients = []
         | 
| 188 | 
            +
              end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
              def comm_noop(arg)
         | 
| 191 | 
            +
                if arg != nil then
         | 
| 192 | 
            +
                  error '501 Syntax: NOOP'
         | 
| 193 | 
            +
                end
         | 
| 194 | 
            +
                @noop_hook.call(@sender, @recipients) if @noop_hook
         | 
| 195 | 
            +
                reply '250 Ok'
         | 
| 196 | 
            +
              end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
              def comm_quit(arg)
         | 
| 199 | 
            +
                if arg != nil then
         | 
| 200 | 
            +
                  error '501 Syntax: QUIT'
         | 
| 201 | 
            +
                end
         | 
| 202 | 
            +
                @quit_hook.call(@sender, @recipients) if @quit_hook
         | 
| 203 | 
            +
                reply '221 Bye'
         | 
| 204 | 
            +
                throw :close
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
              def parse_addr(str)
         | 
| 208 | 
            +
                str = str.strip
         | 
| 209 | 
            +
                if str == '' then
         | 
| 210 | 
            +
                  return nil
         | 
| 211 | 
            +
                end
         | 
| 212 | 
            +
                if str =~ /^<(.*)>$/ then
         | 
| 213 | 
            +
                  return $1.gsub(/\s+/, '')
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
                if str =~ /\s/ then
         | 
| 216 | 
            +
                  return nil
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
                str
         | 
| 219 | 
            +
              end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
              def reply(msg)
         | 
| 222 | 
            +
                puts_safe msg
         | 
| 223 | 
            +
              end
         | 
| 224 | 
            +
             | 
| 225 | 
            +
              def error(msg)
         | 
| 226 | 
            +
                sleep @error_interval if @error_interval
         | 
| 227 | 
            +
                puts_safe msg
         | 
| 228 | 
            +
                throw :next_comm
         | 
| 229 | 
            +
              end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
              def puts_safe(str)
         | 
| 232 | 
            +
                begin
         | 
| 233 | 
            +
                  @sock.puts str + "\r\n"
         | 
| 234 | 
            +
                rescue
         | 
| 235 | 
            +
                  raise SMTPD::Error, "cannot send to client: '#{str.gsub(/\s+/," ")}': #{$!.to_s}"
         | 
| 236 | 
            +
                end
         | 
| 237 | 
            +
              end
         | 
| 238 | 
            +
            end
         | 
| 239 | 
            +
             | 
| 240 | 
            +
            SMTPDError = SMTPD::Error
         | 
| 241 | 
            +
             | 
| 242 | 
            +
            class SMTPServer < WEBrick::GenericServer
         | 
| 243 | 
            +
              def run(sock)
         | 
| 244 | 
            +
                server = SMTPD.new(sock, @config[:ServerName])
         | 
| 245 | 
            +
                server.input_timeout = @config[:RequestTimeout]
         | 
| 246 | 
            +
                server.line_length_limit = @config[:LineLengthLimit]
         | 
| 247 | 
            +
                server.helo_hook = @config[:HeloHook]
         | 
| 248 | 
            +
                server.mail_hook = @config[:MailHook]
         | 
| 249 | 
            +
                server.rcpt_hook = @config[:RcptHook]
         | 
| 250 | 
            +
                server.data_hook = @config[:DataHook]
         | 
| 251 | 
            +
                server.data_each_line = @config[:DataEachLine]
         | 
| 252 | 
            +
                server.rset_hook = @config[:RsetHook]
         | 
| 253 | 
            +
                server.noop_hook = @config[:NoopHook]
         | 
| 254 | 
            +
                server.quit_hook = @config[:QuitHook]
         | 
| 255 | 
            +
                server.start
         | 
| 256 | 
            +
              end
         | 
| 257 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            <?xml version="1.0" encoding="UTF-8"?>
         | 
| 2 | 
            +
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
         | 
| 3 | 
            +
            <html xmlns="http://www.w3.org/1999/xhtml">
         | 
| 4 | 
            +
            <head>
         | 
| 5 | 
            +
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
         | 
| 6 | 
            +
            <link rel="index" href="./index.html" />
         | 
| 7 | 
            +
            <title>Inbox</title>
         | 
| 8 | 
            +
            <style type="text/css">
         | 
| 9 | 
            +
            body {
         | 
| 10 | 
            +
              background:#eee;
         | 
| 11 | 
            +
            }
         | 
| 12 | 
            +
            table {
         | 
| 13 | 
            +
              border: 1px #999 solid;
         | 
| 14 | 
            +
              border-collapse: collapse;
         | 
| 15 | 
            +
            }
         | 
| 16 | 
            +
            th, td {
         | 
| 17 | 
            +
              border: 1px #999 solid;
         | 
| 18 | 
            +
              padding: 6px 12px;
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
            th {
         | 
| 21 | 
            +
              background: #ccc;
         | 
| 22 | 
            +
            }
         | 
| 23 | 
            +
            td {
         | 
| 24 | 
            +
              background: white;  
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
            </style>
         | 
| 27 | 
            +
            </head>
         | 
| 28 | 
            +
            <body>
         | 
| 29 | 
            +
            <h1>Inbox</h1>
         | 
| 30 | 
            +
            <table>
         | 
| 31 | 
            +
            <thead>
         | 
| 32 | 
            +
            <tr>
         | 
| 33 | 
            +
            <th>Date</th>
         | 
| 34 | 
            +
            <th>Subject</th>
         | 
| 35 | 
            +
            <th>From</th>
         | 
| 36 | 
            +
            <th>To</th>
         | 
| 37 | 
            +
            </tr>
         | 
| 38 | 
            +
            </thead>
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            <tbody>
         | 
| 41 | 
            +
            <!-- ADD -->
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            </tbody>
         | 
| 44 | 
            +
            </table>
         | 
| 45 | 
            +
            </body>
         | 
| 46 | 
            +
            </html>
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            <?xml version="1.0" encoding="UTF-8"?>
         | 
| 2 | 
            +
            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
         | 
| 3 | 
            +
            <html xmlns="http://www.w3.org/1999/xhtml">
         | 
| 4 | 
            +
            <head>
         | 
| 5 | 
            +
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
         | 
| 6 | 
            +
            <link rel="index" href="./index.html" />
         | 
| 7 | 
            +
            <title><%=h mail[:subject] %> (<%= mail[:date].to_s %>)</title>
         | 
| 8 | 
            +
            </head>
         | 
| 9 | 
            +
            <body style="background:#eee">
         | 
| 10 | 
            +
            <h1 id="subject"><%=h mail[:subject] %></h1>
         | 
| 11 | 
            +
            <div><p id="date" style="font-size:0.8em;"><%= mail[:date].to_s %></div>
         | 
| 12 | 
            +
            <div id="source" style="border: solid 1px #666; background:white; padding:2em;">
         | 
| 13 | 
            +
            <p><%= mail[:source] %></p>
         | 
| 14 | 
            +
            </div>
         | 
| 15 | 
            +
            </body>
         | 
| 16 | 
            +
            </html>
         | 
    
        data/test/test_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,78 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: boomerang-mocksmtpd
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              version: 0.0.3
         | 
| 5 | 
            +
            platform: ruby
         | 
| 6 | 
            +
            authors: 
         | 
| 7 | 
            +
              - BB
         | 
| 8 | 
            +
            autorequire: 
         | 
| 9 | 
            +
            bindir: bin
         | 
| 10 | 
            +
            cert_chain: []
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            date: 2008-11-08 00:00:00 +01:00
         | 
| 13 | 
            +
            default_executable: mocksmtpd
         | 
| 14 | 
            +
            dependencies: []
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            description: Mock SMTP server for development/testing.
         | 
| 17 | 
            +
            email: b@gmail.com
         | 
| 18 | 
            +
            executables: 
         | 
| 19 | 
            +
              - mocksmtpd
         | 
| 20 | 
            +
            extensions: []
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            extra_rdoc_files: 
         | 
| 23 | 
            +
              - README
         | 
| 24 | 
            +
              - ChangeLog
         | 
| 25 | 
            +
            files: 
         | 
| 26 | 
            +
              - README
         | 
| 27 | 
            +
              - ChangeLog
         | 
| 28 | 
            +
              - Rakefile
         | 
| 29 | 
            +
              - bin/mocksmtpd
         | 
| 30 | 
            +
              - test/mocksmtpd_test.rb
         | 
| 31 | 
            +
              - test/test_helper.rb
         | 
| 32 | 
            +
              - lib/mocksmtpd.rb
         | 
| 33 | 
            +
              - lib/smtpserver.rb
         | 
| 34 | 
            +
              - templates/html/index.erb
         | 
| 35 | 
            +
              - templates/html/index_entry.erb
         | 
| 36 | 
            +
              - templates/html/mail.erb
         | 
| 37 | 
            +
              - templates/mocksmtpd.conf.erb
         | 
| 38 | 
            +
            has_rdoc: true
         | 
| 39 | 
            +
            homepage: http://github.com/boomerang/mocksmtpd/
         | 
| 40 | 
            +
            licenses: []
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            post_install_message: 
         | 
| 43 | 
            +
            rdoc_options: 
         | 
| 44 | 
            +
              - --title
         | 
| 45 | 
            +
              - mocksmtpd documentation
         | 
| 46 | 
            +
              - --charset
         | 
| 47 | 
            +
              - utf-8
         | 
| 48 | 
            +
              - --opname
         | 
| 49 | 
            +
              - index.html
         | 
| 50 | 
            +
              - --line-numbers
         | 
| 51 | 
            +
              - --main
         | 
| 52 | 
            +
              - README
         | 
| 53 | 
            +
              - --inline-source
         | 
| 54 | 
            +
              - --exclude
         | 
| 55 | 
            +
              - ^(examples|extras)/
         | 
| 56 | 
            +
            require_paths: 
         | 
| 57 | 
            +
              - lib
         | 
| 58 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 59 | 
            +
              requirements: 
         | 
| 60 | 
            +
                - - ">="
         | 
| 61 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 62 | 
            +
                    version: "0"
         | 
| 63 | 
            +
              version: 
         | 
| 64 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 65 | 
            +
              requirements: 
         | 
| 66 | 
            +
                - - ">="
         | 
| 67 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 68 | 
            +
                    version: "0"
         | 
| 69 | 
            +
              version: 
         | 
| 70 | 
            +
            requirements: []
         | 
| 71 | 
            +
             | 
| 72 | 
            +
            rubyforge_project: mocksmtpd
         | 
| 73 | 
            +
            rubygems_version: 1.3.5
         | 
| 74 | 
            +
            signing_key: 
         | 
| 75 | 
            +
            specification_version: 2
         | 
| 76 | 
            +
            summary: Mock SMTP server for development/testing.
         | 
| 77 | 
            +
            test_files: 
         | 
| 78 | 
            +
              - test/mocksmtpd_test.rb
         |