received 0.1.0

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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc
4
+ Gemfile.lock
5
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rspec-core'
4
+ gem 'rspec-mocks'
5
+ gem 'rspec-expectations'
6
+
7
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Roman Shterenzon
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ ReceiveD
2
+ ========
3
+
4
+ ReceiveD is yet another way for receiving mail with Rails.
5
+ Why have yet another subsystem (like IMAP), when you can deliver the mail
6
+ directly to your data store?
7
+
8
+ ReceiveD is almost [RFC2033][1] compliant LMTP server built around
9
+ [eventmachine][2] and as such should be quite fast.
10
+
11
+ The receive daemon will listen on TCP or UNIX socket, and write the mail
12
+ to the backend storage.
13
+
14
+ Currently only [MongoDB][3] is supported, but writing another backend
15
+ (MySQL, Redis, etc.) is trivial.
16
+
17
+
18
+ Installation
19
+ ------------
20
+ `sudo gem install received`
21
+
22
+ Modify your [Postfix][4] configuration to deliver mail via LMTP to TCP or UNIX socket.
23
+
24
+ Example main.cf:
25
+ virtual_transport = lmtp:192.168.2.106:1111
26
+ virtual_mailbox_domains = example.com
27
+
28
+ Create a YAML configuration file which has the following parameters:
29
+ {'production'=>{'host'=>hostname, 'database'=>db, 'collection'=>col}}
30
+
31
+ The mongoid.yml will do, just add the name of collection, i.e.
32
+
33
+ production:
34
+ <<: *defaults
35
+ database: foo_production
36
+ collection: inbox
37
+
38
+ The default environment is *production*, but you can specify other environment
39
+ using RAILS_ENV environment variable.
40
+ In this case, make sure you have the relevant key in your configuration file.
41
+
42
+
43
+ Running
44
+ -------
45
+ Check -h for help, port/unix socket path and config file are required.
46
+
47
+
48
+ Bugs and missing features
49
+ -------------------------
50
+
51
+ * When using UNIX socket the permissions/ownership are not changed. Use -u and -g when running
52
+ as daemon or change the permissions/ownership manually.
53
+ * ReceiveD wasn't really tested for compliance with RFC2033
54
+ * It doesn't implement [RFC2034][5] (ENHANCEDSTATUSCODES), because Postfix doesn't seem to care
55
+ * It doesn't perform any validation of the provided input, e.g. LHLO, MAIL FROM, RCPT TO
56
+
57
+ [1]: http://tools.ietf.org/html/rfc2033
58
+ [2]: http://rubyeventmachine.com/
59
+ [3]: http://www.mongodb.org/
60
+ [4]: http://www.postfix.org/
61
+ [5]: http://tools.ietf.org/html/rfc2034
62
+
63
+ Copyright (c) 2011 Roman Shterenzon, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Default: run specs.'
5
+ task :default => :spec
6
+
7
+ desc "Run specs"
8
+ RSpec::Core::RakeTask.new
9
+
10
+ Bundler::GemHelper.install_tasks
data/bin/received ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'logger'
4
+ require 'optparse'
5
+ $:<< File.expand_path('../../lib', __FILE__)
6
+ require 'received'
7
+
8
+ options = {}
9
+ OptionParser.new do |opts|
10
+ opts.banner = 'Usage: received [options]'
11
+ opts.on('-c', '--config FILE', 'Config file name (required)') {|v| options[:config] = v}
12
+ opts.on('-b', '--backend BACKEND', [:mongodb], 'Backend (default: mongodb)') {|v| options[:backend] = v}
13
+ opts.on('-d', '--daemonize', 'Become a daemon') {|v| options[:daemon] = v}
14
+ opts.on('-s', '--unix-socket PATH', 'Use UNIX socket') {|v| options[:unix_socket] = v}
15
+ opts.on('-p', '--port NUM', 'Listen to TCP port') {|v| options[:port] = v.to_i}
16
+ opts.on('-i', '--host NAME', 'Bind to this IP (default: 127.0.0.1)') {|v| options[:host] = v}
17
+ opts.on('-a', '--piddir PATH', 'Directory for pid file (default: /var/tmp)') {|v| options[:dir] = v}
18
+ opts.on('-l', '--log FILE', 'Log file name (default: piddir/received.log)') {|v| options[:logfile] = v}
19
+ opts.on('-u', '--user NAME', 'Effective user when daemon (default: nobody)') {|v| options[:user] = v}
20
+ opts.on('-g', '--group NAME', 'Effective group when daemon (default: nobody)') {|v| options[:group] = v}
21
+ opts.on('-v', '--verbose', 'Verbose logging') {options[:level] = Logger::DEBUG}
22
+ opts.on_tail('-h', '--help', 'Show this message') do
23
+ puts opts
24
+ exit
25
+ end
26
+ end.parse!
27
+
28
+ raise "Config file is required, please provide with -c config.yml" unless options[:config]
29
+
30
+ # Default backend
31
+ options[:backend] ||= 'mongodb'
32
+
33
+ options[:logger] = Logger.new(options[:logfile] || $stderr).tap do |logger|
34
+ logger.level = options[:level] || Logger::INFO
35
+ end
36
+
37
+ if options.delete(:daemon)
38
+ require 'daemons'
39
+ # Monkey patch to rename log file
40
+ class Daemons::Application
41
+ def output_logfile
42
+ (options[:log_output] && logdir) ? File.join(logdir, @group.app_name + '.log') : nil
43
+ end
44
+ end
45
+ params = {:app_name => 'received', :log_output => true, :dir_mode => :normal, :dir => options[:dir] || '/var/tmp'}
46
+ # Drop privileges if started as superuser
47
+ params.merge!({:user => options[:user] || 'nobody', :group => options[:group] || 'nobody'}) if Process.uid == 0
48
+ Daemons.daemonize(params)
49
+ end
50
+
51
+ server = Received::Server.new(options)
52
+ %w(TERM INT).each do |sig|
53
+ Signal.trap(sig) {server.stop}
54
+ end
55
+ server.serve!
@@ -0,0 +1,13 @@
1
+ module Received
2
+ module Backend
3
+ class Base
4
+ # Stores the data
5
+ #
6
+ # @abstract
7
+ # @param [String] data
8
+ def store(data)
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require 'mongo'
2
+
3
+ module Received
4
+ module Backend
5
+ class Mongodb < Base
6
+
7
+ # Initialize MongoDB storage backend
8
+ #
9
+ # @param [Hash] params
10
+ # @option params [String] host
11
+ # @option params [String] database
12
+ # @option params [String] collection
13
+ def initialize(params)
14
+ @db = Mongo::Connection.new(params['host']).db(params['database'])
15
+ @coll = @db.collection(params['collection'])
16
+ end
17
+
18
+ # Store mail in MongoDB
19
+ #
20
+ # @param [Hash] mail
21
+ def store(mail)
22
+ @coll.save(mail.merge({:ts => Time.now.to_i}), :safe => true)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,52 @@
1
+ require 'eventmachine'
2
+ require 'received/lmtp'
3
+
4
+ module Received
5
+ class Connection < EM::Connection
6
+
7
+ def initialize(server, backend)
8
+ @server, @backend = server, backend
9
+ @proto = LMTP.new(self)
10
+ end
11
+
12
+ def post_init
13
+ logger.debug "new connection"
14
+ @proto.start!
15
+ end
16
+
17
+ def receive_data(data)
18
+ logger.debug {"receiving data: #{data.inspect}"}
19
+ @proto.on_data(data)
20
+ end
21
+
22
+ def send_data(data)
23
+ logger.debug {"sending data: #{data.inspect}"}
24
+ super
25
+ end
26
+
27
+ # Client disconnected
28
+ def unbind
29
+ logger.debug "connection closed"
30
+ @server.remove_connection(self)
31
+ end
32
+
33
+ # Callback, called by protocol handler
34
+ #
35
+ # @param [Hash] mail
36
+ # @option mail [String] :from
37
+ # @option mail [Array] :rcpt
38
+ # @option mail [String] :body
39
+ def mail_received(mail)
40
+ begin
41
+ @backend.store(mail)
42
+ logger.info "stored mail from: #{mail[:from]}"
43
+ rescue Exception => e
44
+ logger.error "saving failed with: #{e.message}"
45
+ end
46
+ end
47
+
48
+ def logger
49
+ @server.logger
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,127 @@
1
+ module Received
2
+ # RFC2033
3
+ class LMTP
4
+
5
+ def initialize(conn)
6
+ @conn = conn
7
+ end
8
+
9
+ def on_data(data)
10
+ @buf += data
11
+ while line = @buf.slice!(/.+\r\n/)
12
+ line.chomp! unless @state == :data
13
+ event(line)
14
+ end
15
+ end
16
+
17
+ def start!
18
+ @state = :start
19
+ @buf = ''
20
+ @from = nil
21
+ @rcpt = []
22
+ event(nil)
23
+ end
24
+
25
+ private
26
+ def event(ev)
27
+ @conn.logger.debug {"state was: #{@state.inspect}"}
28
+ @state = case @state
29
+ when :start
30
+ @body = []
31
+ banner
32
+ :banner_sent
33
+ when :banner_sent
34
+ if ev.start_with?('LHLO')
35
+ lhlo_response
36
+ extensions
37
+ :lhlo_received
38
+ else
39
+ error
40
+ end
41
+ when :lhlo_received
42
+ if ev =~ /MAIL FROM:<?([^>]+)/
43
+ @from = $1
44
+ ok
45
+ :mail_from_received
46
+ else
47
+ error
48
+ end
49
+ when :mail_from_received
50
+ if ev =~ /RCPT TO:<?([^>]+)/
51
+ @rcpt << $1
52
+ ok
53
+ :rcpt_to_received
54
+ else
55
+ error
56
+ end
57
+ when :rcpt_to_received
58
+ if ev =~ /RCPT TO:<?([^>]+)/
59
+ @rcpt << $1
60
+ ok
61
+ elsif ev == "DATA"
62
+ start_mail_input
63
+ :data
64
+ else
65
+ error
66
+ end
67
+ when :data
68
+ if ev == ".\r\n"
69
+ @rcpt.size.times {ok}
70
+ mail = {:from => @from, :rcpt => @rcpt, :body => @body.join}
71
+ @conn.mail_received(mail)
72
+ :data_received
73
+ else
74
+ @body << ev
75
+ :data
76
+ end
77
+ when :data_received
78
+ if ev == "QUIT"
79
+ closing_connection
80
+ :start
81
+ else
82
+ error
83
+ end
84
+ else
85
+ raise "Where am I? (#{@state.inspect})"
86
+ end || @state
87
+ @conn.logger.debug {"state now: #{@state.inspect}"}
88
+ end
89
+
90
+ def banner
91
+ emit "220 localhost LMTP server ready"
92
+ end
93
+
94
+ def lhlo_response
95
+ emit "250-localhost"
96
+ end
97
+
98
+ def start_mail_input
99
+ emit "354 End data with <CR><LF>.<CR><LF>"
100
+ end
101
+
102
+ def closing_connection
103
+ emit "221 Bye"
104
+ @conn.close_connection_after_writing
105
+ end
106
+
107
+ # FIXME: RFC2033 requires ENHANCEDSTATUSCODES,
108
+ # but it's not used in Postfix
109
+ def extensions
110
+ emit "250-8BITMIME\r\n250 PIPELINING"
111
+ end
112
+
113
+ def ok
114
+ emit "250 OK"
115
+ end
116
+
117
+ def error
118
+ emit "500 command unrecognized"
119
+ end
120
+
121
+ def emit(str)
122
+ @conn.send_data "#{str}\r\n"
123
+ # return nil, so there won't be implicit state transition
124
+ nil
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,104 @@
1
+ #require 'active_support/core_ext/string/inflections'
2
+ require 'yaml'
3
+ require 'eventmachine'
4
+ require 'received/connection'
5
+
6
+ module Received
7
+ class Server
8
+ attr_reader :logger, :options
9
+
10
+ def initialize(options)
11
+ @options = options
12
+ @logger = options[:logger] || Logger.new($stderr)
13
+ @connections = []
14
+ # For how long the server will wait for connections to finish
15
+ @grace_period = options[:grace_period] || 10
16
+ create_backend
17
+ end
18
+
19
+ def serve!
20
+ EventMachine.run { start }
21
+ end
22
+
23
+ def start
24
+ unless options[:unix_socket] or options[:port]
25
+ raise "No port or UNIX socket path were provided"
26
+ end
27
+ set_title
28
+ if host = options[:unix_socket]
29
+ port = nil
30
+ else
31
+ host = options[:host] || '127.0.0.1'
32
+ port = options[:port]
33
+ end
34
+ logger.info "Starting server on #{host}#{port ? ":" + port.to_s : ''}"
35
+ @signature = EventMachine.start_server(host, port, Received::Connection, self, @backend) do |conn|
36
+ add_connection(conn)
37
+ end
38
+ end
39
+
40
+ def stop
41
+ return if stopping?
42
+ logger.info "Stopping server"
43
+ EventMachine.stop_server(@signature)
44
+ @stopped_at = Time.now
45
+ unless wait_for_connections_and_stop
46
+ # Still some connections running, schedule a check later
47
+ EventMachine.add_periodic_timer(1) { wait_for_connections_and_stop }
48
+ end
49
+ end
50
+
51
+ # Checks whether the server is in stopping mode
52
+ def stopping?
53
+ !!@stopped_at
54
+ end
55
+
56
+ # Checks if the server is processing any connections
57
+ def idle?
58
+ @connections.empty?
59
+ end
60
+
61
+ def remove_connection(conn)
62
+ @connections.delete(conn)
63
+ set_title
64
+ end
65
+
66
+ private
67
+
68
+ # Sets the process title as seen in ps
69
+ def set_title
70
+ $0 = "received (#{@connections.size} connections)"
71
+ end
72
+
73
+ # Whether grace period is over
74
+ def grace_ended?
75
+ Time.now - @stopped_at > @grace_period
76
+ end
77
+
78
+ def wait_for_connections_and_stop
79
+ if idle? or grace_ended?
80
+ EventMachine.stop
81
+ true
82
+ else
83
+ logger.info "Waiting for #{@connections.size} connection(s) to finish..."
84
+ false
85
+ end
86
+ end
87
+
88
+ def add_connection(conn)
89
+ @connections << conn
90
+ set_title
91
+ end
92
+
93
+ def create_backend
94
+ backend = options[:backend].to_s
95
+ require 'received/backend/' + backend
96
+ #klass = ('Received::Backend::' + backend.camelize).constantize
97
+ klass = eval('Received::Backend::' + backend.capitalize)
98
+ env = ENV['RAILS_ENV'] || 'production'
99
+ config = YAML.load(File.read(options[:config]))[env]
100
+ @backend = klass.new(config)
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module Received
2
+ VERSION = "0.1.0"
3
+ end
data/lib/received.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Received
2
+ end
3
+
4
+ require 'received/backend/base'
5
+ require 'received/server'
data/received.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "received/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "received"
7
+ s.version = Received::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Roman Shterenzon"]
10
+ s.email = ["romanbsd@yahoo.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Receive mail from Postfix and store it somewhere}
13
+ s.description = %q{Currently stores received mail in MongoDB}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ s.add_runtime_dependency 'activesupport'
20
+ s.add_runtime_dependency 'daemons'
21
+ s.add_runtime_dependency 'eventmachine'
22
+ s.add_runtime_dependency 'mongo', '~>1.3.0'
23
+ s.add_runtime_dependency 'bson_ext', '~>1.3.0'
24
+ end
data/spec/lmtp_spec.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'logger'
3
+
4
+ describe Received::LMTP do
5
+ before :each do
6
+ @mock = mock 'conn'
7
+ @mock.should_receive(:send_data).with("220 localhost LMTP server ready\r\n")
8
+ @mock.stub!(:logger).and_return(Logger.new($stderr))
9
+ @mock.logger.debug "*** Starting test ***"
10
+ @proto = Received::LMTP.new(@mock)
11
+ @proto.start!
12
+ end
13
+
14
+ it "does full receive flow" do
15
+ @mock.should_receive(:send_data).with("250-localhost\r\n")
16
+ @mock.should_receive(:send_data).with("250-8BITMIME\r\n250 PIPELINING\r\n")
17
+ @mock.should_receive(:send_data).with("250 OK\r\n").exactly(3).times
18
+ @mock.should_receive(:send_data).with("354 End data with <CR><LF>.<CR><LF>\r\n")
19
+ @mock.should_receive(:send_data).with("250 OK\r\n").exactly(2).times
20
+ @mock.should_receive(:send_data).with("221 Bye\r\n")
21
+ body = "Subject: spec\r\nspec\r\n"
22
+ @mock.should_receive(:mail_received).with({
23
+ :from => 'spec1@example.com',
24
+ :rcpt => ['spec2@example.com', 'spec3@example.com'],
25
+ :body => body
26
+ })
27
+ @mock.should_receive(:close_connection_after_writing)
28
+
29
+ ["LHLO", "MAIL FROM:<spec1@example.com>", "RCPT TO:<spec2@example.com>",
30
+ "RCPT TO:<spec3@example.com>", "DATA", "#{body}.", "QUIT"].each do |line|
31
+ @mock.logger.debug "client: #{line}"
32
+ @proto.on_data(line + "\r\n")
33
+ end
34
+
35
+ end
36
+
37
+ it "parses multiline" do
38
+ @mock.should_receive(:send_data).with("250-localhost\r\n")
39
+ @mock.should_receive(:send_data).with("250-8BITMIME\r\n250 PIPELINING\r\n")
40
+ @mock.should_receive(:send_data).with("250 OK\r\n")
41
+ @proto.on_data("LHLO\r\nMAIL FROM:<spec@example.com>\r\n")
42
+ end
43
+
44
+ it "buffers commands up to CR/LF" do
45
+ @mock.should_receive(:send_data).with("250-localhost\r\n")
46
+ @mock.should_receive(:send_data).with("250-8BITMIME\r\n250 PIPELINING\r\n")
47
+ @mock.should_receive(:send_data).with("250 OK\r\n")
48
+ @proto.on_data("LHLO\r\nMAIL FROM")
49
+ @proto.on_data(":<spec@example.com>\r\n")
50
+ end
51
+ end
@@ -0,0 +1 @@
1
+ require 'received'
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: received
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Roman Shterenzon
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-05-01 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: daemons
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: eventmachine
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: mongo
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 1
69
+ - 3
70
+ - 0
71
+ version: 1.3.0
72
+ type: :runtime
73
+ version_requirements: *id004
74
+ - !ruby/object:Gem::Dependency
75
+ name: bson_ext
76
+ prerelease: false
77
+ requirement: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ segments:
83
+ - 1
84
+ - 3
85
+ - 0
86
+ version: 1.3.0
87
+ type: :runtime
88
+ version_requirements: *id005
89
+ description: Currently stores received mail in MongoDB
90
+ email:
91
+ - romanbsd@yahoo.com
92
+ executables:
93
+ - received
94
+ extensions: []
95
+
96
+ extra_rdoc_files: []
97
+
98
+ files:
99
+ - .gitignore
100
+ - .rspec
101
+ - Gemfile
102
+ - LICENSE
103
+ - README.md
104
+ - Rakefile
105
+ - bin/received
106
+ - lib/received.rb
107
+ - lib/received/backend/base.rb
108
+ - lib/received/backend/mongodb.rb
109
+ - lib/received/connection.rb
110
+ - lib/received/lmtp.rb
111
+ - lib/received/server.rb
112
+ - lib/received/version.rb
113
+ - received.gemspec
114
+ - spec/lmtp_spec.rb
115
+ - spec/spec_helper.rb
116
+ has_rdoc: true
117
+ homepage: ""
118
+ licenses: []
119
+
120
+ post_install_message:
121
+ rdoc_options: []
122
+
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ segments:
131
+ - 0
132
+ version: "0"
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ requirements: []
142
+
143
+ rubyforge_project:
144
+ rubygems_version: 1.3.7
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: Receive mail from Postfix and store it somewhere
148
+ test_files:
149
+ - spec/lmtp_spec.rb
150
+ - spec/spec_helper.rb