eventmachine 0.12.10-x86-mswin32-60 → 1.0.0.beta.2-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +1 -0
  3. data/README +80 -81
  4. data/Rakefile +7 -370
  5. data/docs/COPYING +60 -60
  6. data/docs/ChangeLog +211 -211
  7. data/docs/DEFERRABLES +246 -133
  8. data/docs/EPOLL +141 -141
  9. data/docs/GNU +281 -281
  10. data/docs/INSTALL +13 -13
  11. data/docs/KEYBOARD +42 -38
  12. data/docs/LEGAL +25 -25
  13. data/docs/LIGHTWEIGHT_CONCURRENCY +130 -70
  14. data/docs/PURE_RUBY +75 -75
  15. data/docs/RELEASE_NOTES +94 -94
  16. data/docs/SMTP +4 -2
  17. data/docs/SPAWNED_PROCESSES +148 -89
  18. data/docs/TODO +8 -8
  19. data/eventmachine.gemspec +19 -26
  20. data/examples/ex_channel.rb +42 -42
  21. data/examples/ex_queue.rb +2 -2
  22. data/examples/ex_tick_loop_array.rb +15 -0
  23. data/examples/ex_tick_loop_counter.rb +32 -0
  24. data/examples/helper.rb +1 -1
  25. data/ext/binder.cpp +0 -1
  26. data/ext/cmain.cpp +36 -25
  27. data/ext/ed.cpp +104 -113
  28. data/ext/ed.h +24 -30
  29. data/ext/em.cpp +349 -283
  30. data/ext/em.h +25 -29
  31. data/ext/eventmachine.h +5 -4
  32. data/ext/extconf.rb +58 -49
  33. data/ext/fastfilereader/extconf.rb +5 -3
  34. data/ext/fastfilereader/mapper.cpp +214 -214
  35. data/ext/fastfilereader/mapper.h +59 -59
  36. data/ext/fastfilereader/rubymain.cpp +127 -127
  37. data/ext/kb.cpp +1 -3
  38. data/ext/page.cpp +107 -107
  39. data/ext/page.h +51 -51
  40. data/ext/pipe.cpp +9 -11
  41. data/ext/project.h +12 -8
  42. data/ext/rubymain.cpp +138 -104
  43. data/java/.classpath +8 -8
  44. data/java/.project +17 -17
  45. data/java/src/com/rubyeventmachine/EmReactor.java +1 -0
  46. data/java/src/com/rubyeventmachine/EmReactorException.java +40 -40
  47. data/lib/em/buftok.rb +138 -138
  48. data/lib/em/callback.rb +25 -25
  49. data/lib/em/channel.rb +1 -1
  50. data/lib/em/connection.rb +6 -1
  51. data/lib/em/deferrable.rb +16 -2
  52. data/lib/em/file_watch.rb +53 -53
  53. data/lib/em/future.rb +61 -61
  54. data/lib/em/iterator.rb +270 -0
  55. data/lib/em/messages.rb +66 -66
  56. data/lib/em/process_watch.rb +43 -43
  57. data/lib/em/protocols.rb +1 -1
  58. data/lib/em/protocols/header_and_content.rb +138 -138
  59. data/lib/em/protocols/httpclient.rb +267 -262
  60. data/lib/em/protocols/line_protocol.rb +28 -0
  61. data/lib/em/protocols/memcache.rb +322 -322
  62. data/lib/em/protocols/postgres3.rb +247 -247
  63. data/lib/em/protocols/saslauth.rb +175 -175
  64. data/lib/em/protocols/smtpserver.rb +640 -547
  65. data/lib/em/protocols/stomp.rb +200 -200
  66. data/lib/em/protocols/tcptest.rb +52 -52
  67. data/lib/{pr_eventmachine.rb → em/pure_ruby.rb} +1013 -1022
  68. data/lib/em/queue.rb +1 -0
  69. data/lib/em/spawnable.rb +85 -85
  70. data/lib/em/streamer.rb +130 -130
  71. data/lib/em/tick_loop.rb +85 -0
  72. data/lib/em/timers.rb +2 -1
  73. data/lib/em/version.rb +1 -1
  74. data/lib/eventmachine.rb +40 -84
  75. data/lib/jeventmachine.rb +2 -1
  76. data/lib/rubyeventmachine.rb +2 -0
  77. data/setup.rb +1585 -1585
  78. data/tasks/doc.rake +30 -0
  79. data/tasks/package.rake +85 -0
  80. data/tasks/test.rake +6 -0
  81. data/tests/client.crt +31 -31
  82. data/tests/client.key +51 -51
  83. data/tests/test_attach.rb +13 -3
  84. data/tests/test_basic.rb +60 -95
  85. data/tests/test_channel.rb +3 -2
  86. data/tests/test_defer.rb +49 -47
  87. data/tests/test_deferrable.rb +35 -0
  88. data/tests/test_error_handler.rb +35 -35
  89. data/tests/test_errors.rb +82 -82
  90. data/tests/test_exc.rb +55 -55
  91. data/tests/test_file_watch.rb +49 -49
  92. data/tests/test_futures.rb +198 -198
  93. data/tests/test_handler_check.rb +36 -36
  94. data/tests/test_hc.rb +190 -218
  95. data/tests/test_httpclient.rb +227 -218
  96. data/tests/test_httpclient2.rb +3 -2
  97. data/tests/test_inactivity_timeout.rb +3 -3
  98. data/tests/test_kb.rb +60 -60
  99. data/tests/test_ltp.rb +13 -5
  100. data/tests/test_ltp2.rb +317 -317
  101. data/tests/test_next_tick.rb +1 -1
  102. data/tests/test_object_protocol.rb +36 -36
  103. data/tests/test_pending_connect_timeout.rb +2 -2
  104. data/tests/test_process_watch.rb +50 -48
  105. data/tests/test_proxy_connection.rb +52 -0
  106. data/tests/test_pure.rb +134 -125
  107. data/tests/test_queue.rb +44 -44
  108. data/tests/test_running.rb +42 -42
  109. data/tests/test_sasl.rb +72 -72
  110. data/tests/test_send_file.rb +251 -242
  111. data/tests/test_servers.rb +76 -76
  112. data/tests/test_smtpclient.rb +83 -83
  113. data/tests/test_smtpserver.rb +85 -85
  114. data/tests/test_spawn.rb +322 -322
  115. data/tests/test_ssl_methods.rb +49 -49
  116. data/tests/test_ssl_verify.rb +82 -82
  117. data/tests/test_tick_loop.rb +59 -0
  118. data/tests/test_timers.rb +13 -15
  119. data/tests/test_ud.rb +36 -36
  120. data/tests/testem.rb +31 -31
  121. metadata +66 -51
  122. data/ext/cplusplus.cpp +0 -202
  123. data/ext/emwin.cpp +0 -300
  124. data/ext/emwin.h +0 -94
  125. data/ext/epoll.cpp +0 -26
  126. data/ext/epoll.h +0 -25
  127. data/ext/eventmachine_cpp.h +0 -96
  128. data/ext/files.cpp +0 -94
  129. data/ext/files.h +0 -65
  130. data/ext/sigs.cpp +0 -89
  131. data/ext/sigs.h +0 -32
  132. data/java/src/com/rubyeventmachine/application/Application.java +0 -194
  133. data/java/src/com/rubyeventmachine/application/Connection.java +0 -74
  134. data/java/src/com/rubyeventmachine/application/ConnectionFactory.java +0 -37
  135. data/java/src/com/rubyeventmachine/application/DefaultConnectionFactory.java +0 -46
  136. data/java/src/com/rubyeventmachine/application/PeriodicTimer.java +0 -38
  137. data/java/src/com/rubyeventmachine/application/Timer.java +0 -54
  138. data/java/src/com/rubyeventmachine/tests/ApplicationTest.java +0 -109
  139. data/java/src/com/rubyeventmachine/tests/ConnectTest.java +0 -148
  140. data/java/src/com/rubyeventmachine/tests/EMTest.java +0 -80
  141. data/java/src/com/rubyeventmachine/tests/TestDatagrams.java +0 -53
  142. data/java/src/com/rubyeventmachine/tests/TestServers.java +0 -75
  143. data/java/src/com/rubyeventmachine/tests/TestTimers.java +0 -90
  144. data/lib/evma.rb +0 -32
  145. data/lib/evma/callback.rb +0 -32
  146. data/lib/evma/container.rb +0 -75
  147. data/lib/evma/factory.rb +0 -77
  148. data/lib/evma/protocol.rb +0 -87
  149. data/lib/evma/reactor.rb +0 -48
  150. data/web/whatis +0 -7
@@ -1,175 +1,175 @@
1
- #--
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 15 November 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
- #
26
-
27
- module EventMachine
28
- module Protocols
29
-
30
- # Implements SASL authd.
31
- # This is a very, very simple protocol that mimics the one used
32
- # by saslauthd and pwcheck, two outboard daemons included in the
33
- # standard SASL library distro.
34
- # The only thing this is really suitable for is SASL PLAIN
35
- # (user+password) authentication, but the SASL libs that are
36
- # linked into standard servers (like imapd and sendmail) implement
37
- # the other ones.
38
- #
39
- # SASL-auth is intended for reasonably fast operation inside a
40
- # single machine, so it has no transport-security (although there
41
- # have been multi-machine extensions incorporating transport-layer
42
- # encryption).
43
- #
44
- # The standard saslauthd module generally runs privileged and does
45
- # its work by referring to the system-account files.
46
- #
47
- # This feature was added to EventMachine to enable the development
48
- # of custom authentication/authorization engines for standard servers.
49
- #
50
- # To use SASLauth, include it in a class that subclasses EM::Connection,
51
- # and reimplement the validate method.
52
- #
53
- # The typical way to incorporate this module into an authentication
54
- # daemon would be to set it as the handler for a UNIX-domain socket.
55
- # The code might look like this:
56
- #
57
- # EM.start_unix_domain_server( "/var/run/saslauthd/mux", MyHandler )
58
- # File.chmod( 0777, "/var/run/saslauthd/mux")
59
- #
60
- # The chmod is probably needed to ensure that unprivileged clients can
61
- # access the UNIX-domain socket.
62
- #
63
- # It's also a very good idea to drop superuser privileges (if any), after
64
- # the UNIX-domain socket has been opened.
65
- #--
66
- # Implementation details: assume the client can send us pipelined requests,
67
- # and that the client will close the connection.
68
- #
69
- # The client sends us four values, each encoded as a two-byte length field in
70
- # network order followed by the specified number of octets.
71
- # The fields specify the username, password, service name (such as imap),
72
- # and the "realm" name. We send back the barest minimum reply, a single
73
- # field also encoded as a two-octet length in network order, followed by
74
- # either "NO" or "OK" - simplicity itself.
75
- #
76
- # We enforce a maximum field size just as a sanity check.
77
- # We do NOT automatically time out the connection.
78
- #
79
- # The code we use to parse out the values is ugly and probably slow.
80
- # Improvements welcome.
81
- #
82
- module SASLauth
83
-
84
- MaxFieldSize = 128*1024
85
- def post_init
86
- super
87
- @sasl_data = ""
88
- @sasl_values = []
89
- end
90
-
91
- def receive_data data
92
- @sasl_data << data
93
- while @sasl_data.length >= 2
94
- len = (@sasl_data[0,2].unpack("n")).first
95
- raise "SASL Max Field Length exceeded" if len > MaxFieldSize
96
- if @sasl_data.length >= (len + 2)
97
- @sasl_values << @sasl_data[2,len]
98
- @sasl_data.slice!(0...(2+len))
99
- if @sasl_values.length == 4
100
- send_data( validate(*@sasl_values) ? "\0\002OK" : "\0\002NO" )
101
- @sasl_values.clear
102
- end
103
- else
104
- break
105
- end
106
- end
107
- end
108
-
109
- def validate username, psw, sysname, realm
110
- p username
111
- p psw
112
- p sysname
113
- p realm
114
- true
115
- end
116
- end
117
-
118
- # Implements the SASL authd client protocol.
119
- # This is a very, very simple protocol that mimics the one used
120
- # by saslauthd and pwcheck, two outboard daemons included in the
121
- # standard SASL library distro.
122
- # The only thing this is really suitable for is SASL PLAIN
123
- # (user+password) authentication, but the SASL libs that are
124
- # linked into standard servers (like imapd and sendmail) implement
125
- # the other ones.
126
- #
127
- # You can use this module directly as a handler for EM Connections,
128
- # or include it in a module or handler class of your own.
129
- #
130
- # First connect to a SASL server (it's probably a TCP server, or more
131
- # likely a Unix-domain socket). Then call the #validate? method,
132
- # passing at least a username and a password. #validate? returns
133
- # a Deferrable which will either succeed or fail, depending
134
- # on the status of the authentication operation.
135
- #
136
- module SASLauthclient
137
- MaxFieldSize = 128*1024
138
-
139
- def validate? username, psw, sysname=nil, realm=nil
140
-
141
- str = [username, psw, sysname, realm].map {|m|
142
- [(m || "").length, (m || "")]
143
- }.flatten.pack( "nA*" * 4 )
144
- send_data str
145
-
146
- d = EM::DefaultDeferrable.new
147
- @queries.unshift d
148
- d
149
- end
150
-
151
- def post_init
152
- @sasl_data = ""
153
- @queries = []
154
- end
155
-
156
- def receive_data data
157
- @sasl_data << data
158
-
159
- while @sasl_data.length > 2
160
- len = (@sasl_data[0,2].unpack("n")).first
161
- raise "SASL Max Field Length exceeded" if len > MaxFieldSize
162
- if @sasl_data.length >= (len + 2)
163
- val = @sasl_data[2,len]
164
- @sasl_data.slice!(0...(2+len))
165
- q = @queries.pop
166
- (val == "NO") ? q.fail : q.succeed
167
- else
168
- break
169
- end
170
- end
171
- end
172
- end
173
-
174
- end
175
- end
1
+ #--
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 15 November 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+ #
26
+
27
+ module EventMachine
28
+ module Protocols
29
+
30
+ # Implements SASL authd.
31
+ # This is a very, very simple protocol that mimics the one used
32
+ # by saslauthd and pwcheck, two outboard daemons included in the
33
+ # standard SASL library distro.
34
+ # The only thing this is really suitable for is SASL PLAIN
35
+ # (user+password) authentication, but the SASL libs that are
36
+ # linked into standard servers (like imapd and sendmail) implement
37
+ # the other ones.
38
+ #
39
+ # SASL-auth is intended for reasonably fast operation inside a
40
+ # single machine, so it has no transport-security (although there
41
+ # have been multi-machine extensions incorporating transport-layer
42
+ # encryption).
43
+ #
44
+ # The standard saslauthd module generally runs privileged and does
45
+ # its work by referring to the system-account files.
46
+ #
47
+ # This feature was added to EventMachine to enable the development
48
+ # of custom authentication/authorization engines for standard servers.
49
+ #
50
+ # To use SASLauth, include it in a class that subclasses EM::Connection,
51
+ # and reimplement the validate method.
52
+ #
53
+ # The typical way to incorporate this module into an authentication
54
+ # daemon would be to set it as the handler for a UNIX-domain socket.
55
+ # The code might look like this:
56
+ #
57
+ # EM.start_unix_domain_server( "/var/run/saslauthd/mux", MyHandler )
58
+ # File.chmod( 0777, "/var/run/saslauthd/mux")
59
+ #
60
+ # The chmod is probably needed to ensure that unprivileged clients can
61
+ # access the UNIX-domain socket.
62
+ #
63
+ # It's also a very good idea to drop superuser privileges (if any), after
64
+ # the UNIX-domain socket has been opened.
65
+ #--
66
+ # Implementation details: assume the client can send us pipelined requests,
67
+ # and that the client will close the connection.
68
+ #
69
+ # The client sends us four values, each encoded as a two-byte length field in
70
+ # network order followed by the specified number of octets.
71
+ # The fields specify the username, password, service name (such as imap),
72
+ # and the "realm" name. We send back the barest minimum reply, a single
73
+ # field also encoded as a two-octet length in network order, followed by
74
+ # either "NO" or "OK" - simplicity itself.
75
+ #
76
+ # We enforce a maximum field size just as a sanity check.
77
+ # We do NOT automatically time out the connection.
78
+ #
79
+ # The code we use to parse out the values is ugly and probably slow.
80
+ # Improvements welcome.
81
+ #
82
+ module SASLauth
83
+
84
+ MaxFieldSize = 128*1024
85
+ def post_init
86
+ super
87
+ @sasl_data = ""
88
+ @sasl_values = []
89
+ end
90
+
91
+ def receive_data data
92
+ @sasl_data << data
93
+ while @sasl_data.length >= 2
94
+ len = (@sasl_data[0,2].unpack("n")).first
95
+ raise "SASL Max Field Length exceeded" if len > MaxFieldSize
96
+ if @sasl_data.length >= (len + 2)
97
+ @sasl_values << @sasl_data[2,len]
98
+ @sasl_data.slice!(0...(2+len))
99
+ if @sasl_values.length == 4
100
+ send_data( validate(*@sasl_values) ? "\0\002OK" : "\0\002NO" )
101
+ @sasl_values.clear
102
+ end
103
+ else
104
+ break
105
+ end
106
+ end
107
+ end
108
+
109
+ def validate username, psw, sysname, realm
110
+ p username
111
+ p psw
112
+ p sysname
113
+ p realm
114
+ true
115
+ end
116
+ end
117
+
118
+ # Implements the SASL authd client protocol.
119
+ # This is a very, very simple protocol that mimics the one used
120
+ # by saslauthd and pwcheck, two outboard daemons included in the
121
+ # standard SASL library distro.
122
+ # The only thing this is really suitable for is SASL PLAIN
123
+ # (user+password) authentication, but the SASL libs that are
124
+ # linked into standard servers (like imapd and sendmail) implement
125
+ # the other ones.
126
+ #
127
+ # You can use this module directly as a handler for EM Connections,
128
+ # or include it in a module or handler class of your own.
129
+ #
130
+ # First connect to a SASL server (it's probably a TCP server, or more
131
+ # likely a Unix-domain socket). Then call the #validate? method,
132
+ # passing at least a username and a password. #validate? returns
133
+ # a Deferrable which will either succeed or fail, depending
134
+ # on the status of the authentication operation.
135
+ #
136
+ module SASLauthclient
137
+ MaxFieldSize = 128*1024
138
+
139
+ def validate? username, psw, sysname=nil, realm=nil
140
+
141
+ str = [username, psw, sysname, realm].map {|m|
142
+ [(m || "").length, (m || "")]
143
+ }.flatten.pack( "nA*" * 4 )
144
+ send_data str
145
+
146
+ d = EM::DefaultDeferrable.new
147
+ @queries.unshift d
148
+ d
149
+ end
150
+
151
+ def post_init
152
+ @sasl_data = ""
153
+ @queries = []
154
+ end
155
+
156
+ def receive_data data
157
+ @sasl_data << data
158
+
159
+ while @sasl_data.length > 2
160
+ len = (@sasl_data[0,2].unpack("n")).first
161
+ raise "SASL Max Field Length exceeded" if len > MaxFieldSize
162
+ if @sasl_data.length >= (len + 2)
163
+ val = @sasl_data[2,len]
164
+ @sasl_data.slice!(0...(2+len))
165
+ q = @queries.pop
166
+ (val == "NO") ? q.fail : q.succeed
167
+ else
168
+ break
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ end
175
+ end
@@ -1,547 +1,640 @@
1
- #--
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 16 July 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
12
- # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
- # Gmail: blackhedd
14
- #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
19
- #
20
- # See the file COPYING for complete licensing information.
21
- #
22
- #---------------------------------------------------------------------------
23
- #
24
- #
25
-
26
- module EventMachine
27
- module Protocols
28
-
29
- # This is a protocol handler for the server side of SMTP.
30
- # It's NOT a complete SMTP server obeying all the semantics of servers conforming to
31
- # RFC2821. Rather, it uses overridable method stubs to communicate protocol states
32
- # and data to user code. User code is responsible for doing the right things with the
33
- # data in order to get complete and correct SMTP server behavior.
34
- #
35
- #--
36
- # Useful paragraphs in RFC-2821:
37
- # 4.3.2: Concise list of command-reply sequences, in essence a text representation
38
- # of the command state-machine.
39
- #
40
- # STARTTLS is defined in RFC2487.
41
- # Observe that there are important rules governing whether a publicly-referenced server
42
- # (meaning one whose Internet address appears in public MX records) may require the
43
- # non-optional use of TLS.
44
- # Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS.
45
- class SmtpServer < EventMachine::Connection
46
- include Protocols::LineText2
47
-
48
- HeloRegex = /\AHELO\s*/i
49
- EhloRegex = /\AEHLO\s*/i
50
- QuitRegex = /\AQUIT/i
51
- MailFromRegex = /\AMAIL FROM:\s*/i
52
- RcptToRegex = /\ARCPT TO:\s*/i
53
- DataRegex = /\ADATA/i
54
- NoopRegex = /\ANOOP/i
55
- RsetRegex = /\ARSET/i
56
- VrfyRegex = /\AVRFY\s+/i
57
- ExpnRegex = /\AEXPN\s+/i
58
- HelpRegex = /\AHELP/i
59
- StarttlsRegex = /\ASTARTTLS/i
60
- AuthRegex = /\AAUTH\s+/i
61
-
62
-
63
- # Class variable containing default parameters that can be overridden
64
- # in application code.
65
- # Individual objects of this class will make an instance-local copy of
66
- # the class variable, so that they can be reconfigured on a per-instance
67
- # basis.
68
- #
69
- # Chunksize is the number of data lines we'll buffer before
70
- # sending them to the application. TODO, make this user-configurable.
71
- #
72
- @@parms = {
73
- :chunksize => 4000,
74
- :verbose => false
75
- }
76
- def self.parms= parms={}
77
- @@parms.merge!(parms)
78
- end
79
-
80
-
81
-
82
- def initialize *args
83
- super
84
- @parms = @@parms
85
- init_protocol_state
86
- end
87
-
88
- def parms= parms={}
89
- @parms.merge!(parms)
90
- end
91
-
92
- # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM,
93
- # #post_init will execute BEFORE the block passed to #start_server, for any
94
- # given accepted connection. Since in this class we'll probably be getting
95
- # a lot of initialization parameters, we want the guts of post_init to
96
- # run AFTER the application has initialized the connection object. So we
97
- # use a spawn to schedule the post_init to run later.
98
- # It's a little weird, I admit. A reasonable alternative would be to set
99
- # parameters as a class variable and to do that before accepting any connections.
100
- #
101
- # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.
102
- #
103
- def post_init
104
- #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
105
- #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
106
- (EM.spawn {|x| x.send_server_greeting}).notify(self)
107
- end
108
-
109
- def send_server_greeting
110
- send_data "220 #{get_server_greeting}\r\n"
111
- end
112
-
113
- def receive_line ln
114
- @@parms[:verbose] and $>.puts ">>> #{ln}"
115
-
116
- return process_data_line(ln) if @state.include?(:data)
117
-
118
- case ln
119
- when EhloRegex
120
- process_ehlo $'.dup
121
- when HeloRegex
122
- process_helo $'.dup
123
- when MailFromRegex
124
- process_mail_from $'.dup
125
- when RcptToRegex
126
- process_rcpt_to $'.dup
127
- when DataRegex
128
- process_data
129
- when RsetRegex
130
- process_rset
131
- when VrfyRegex
132
- process_vrfy
133
- when ExpnRegex
134
- process_expn
135
- when HelpRegex
136
- process_help
137
- when NoopRegex
138
- process_noop
139
- when QuitRegex
140
- process_quit
141
- when StarttlsRegex
142
- process_starttls
143
- when AuthRegex
144
- process_auth $'.dup
145
- else
146
- process_unknown
147
- end
148
- end
149
-
150
- # TODO - implement this properly, the implementation is a stub!
151
- def process_vrfy
152
- send_data "250 Ok, but unimplemented\r\n"
153
- end
154
- # TODO - implement this properly, the implementation is a stub!
155
- def process_help
156
- send_data "250 Ok, but unimplemented\r\n"
157
- end
158
- # TODO - implement this properly, the implementation is a stub!
159
- def process_expn
160
- send_data "250 Ok, but unimplemented\r\n"
161
- end
162
-
163
- #--
164
- # This is called at several points to restore the protocol state
165
- # to a pre-transaction state. In essence, we "forget" having seen
166
- # any valid command except EHLO and STARTTLS.
167
- # We also have to callback user code, in case they're keeping track
168
- # of senders, recipients, and whatnot.
169
- #
170
- # We try to follow the convention of avoiding the verb "receive" for
171
- # internal method names except receive_line (which we inherit), and
172
- # using only receive_xxx for user-overridable stubs.
173
- #
174
- # init_protocol_state is called when we initialize the connection as
175
- # well as during reset_protocol_state. It does NOT call the user
176
- # override method. This enables us to promise the users that they
177
- # won't see the overridable fire except after EHLO and RSET, and
178
- # after a message has been received. Although the latter may be wrong.
179
- # The standard may allow multiple DATA segments with the same set of
180
- # senders and recipients.
181
- #
182
- def reset_protocol_state
183
- init_protocol_state
184
- s,@state = @state,[]
185
- @state << :starttls if s.include?(:starttls)
186
- @state << :ehlo if s.include?(:ehlo)
187
- receive_transaction
188
- end
189
- def init_protocol_state
190
- @state ||= []
191
- end
192
-
193
-
194
- #--
195
- # EHLO/HELO is always legal, per the standard. On success
196
- # it always clears buffers and initiates a mail "transaction."
197
- # Which means that a MAIL FROM must follow.
198
- #
199
- # Per the standard, an EHLO/HELO or a RSET "initiates" an email
200
- # transaction. Thereafter, MAIL FROM must be received before
201
- # RCPT TO, before DATA. Not sure what this specific ordering
202
- # achieves semantically, but it does make it easier to
203
- # implement. We also support user-specified requirements for
204
- # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
205
- # without fulfilling tls and/or auth, if the user specified either
206
- # or both as required. We need to check the extension standard
207
- # for auth to see if a credential is discarded after a RSET along
208
- # with all the rest of the state. We'll behave as if it is.
209
- # Now clearly, we can't discard tls after its been negotiated
210
- # without dropping the connection, so that flag doesn't get cleared.
211
- #
212
- def process_ehlo domain
213
- if receive_ehlo_domain domain
214
- send_data "250-#{get_server_domain}\r\n"
215
- if @@parms[:starttls]
216
- send_data "250-STARTTLS\r\n"
217
- end
218
- if @@parms[:auth]
219
- send_data "250-AUTH PLAIN LOGIN\r\n"
220
- end
221
- send_data "250-NO-SOLICITING\r\n"
222
- # TODO, size needs to be configurable.
223
- send_data "250 SIZE 20000000\r\n"
224
- reset_protocol_state
225
- @state << :ehlo
226
- else
227
- send_data "550 Requested action not taken\r\n"
228
- end
229
- end
230
-
231
- def process_helo domain
232
- if receive_ehlo_domain domain.dup
233
- send_data "250 #{get_server_domain}\r\n"
234
- reset_protocol_state
235
- @state << :ehlo
236
- else
237
- send_data "550 Requested action not taken\r\n"
238
- end
239
- end
240
-
241
- def process_quit
242
- send_data "221 Ok\r\n"
243
- close_connection_after_writing
244
- end
245
-
246
- def process_noop
247
- send_data "250 Ok\r\n"
248
- end
249
-
250
- def process_unknown
251
- send_data "500 Unknown command\r\n"
252
- end
253
-
254
- #--
255
- # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
256
- # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
257
- # response and a continuation of the auth conversation.
258
- #
259
- def process_auth str
260
- if @state.include?(:auth)
261
- send_data "503 auth already issued\r\n"
262
- elsif str =~ /\APLAIN\s+/i
263
- plain = ($'.dup).unpack("m").first # Base64::decode64($'.dup)
264
- discard,user,psw = plain.split("\000")
265
- if receive_plain_auth user,psw
266
- send_data "235 authentication ok\r\n"
267
- @state << :auth
268
- else
269
- send_data "535 invalid authentication\r\n"
270
- end
271
- #elsif str =~ /\ALOGIN\s+/i
272
- else
273
- send_data "504 auth mechanism not available\r\n"
274
- end
275
- end
276
-
277
- #--
278
- # Unusually, we can deal with a Deferrable returned from the user application.
279
- # This was added to deal with a special case in a particular application, but
280
- # it would be a nice idea to add it to the other user-code callbacks.
281
- #
282
- def process_data
283
- unless @state.include?(:rcpt)
284
- send_data "503 Operation sequence error\r\n"
285
- else
286
- succeeded = proc {
287
- send_data "354 Send it\r\n"
288
- @state << :data
289
- @databuffer = []
290
- }
291
- failed = proc {
292
- send_data "550 Operation failed\r\n"
293
- }
294
-
295
- d = receive_data_command
296
-
297
- if d.respond_to?(:callback)
298
- d.callback(&succeeded)
299
- d.errback(&failed)
300
- else
301
- (d ? succeeded : failed).call
302
- end
303
- end
304
- end
305
-
306
- def process_rset
307
- reset_protocol_state
308
- receive_reset
309
- send_data "250 Ok\r\n"
310
- end
311
-
312
- def unbind
313
- connection_ended
314
- end
315
-
316
- #--
317
- # STARTTLS may not be issued before EHLO, or unless the user has chosen
318
- # to support it.
319
- # TODO, must support user-supplied certificates.
320
- #
321
- def process_starttls
322
- if @@parms[:starttls]
323
- if @state.include?(:starttls)
324
- send_data "503 TLS Already negotiated\r\n"
325
- elsif ! @state.include?(:ehlo)
326
- send_data "503 EHLO required before STARTTLS\r\n"
327
- else
328
- send_data "220 Start TLS negotiation\r\n"
329
- start_tls
330
- @state << :starttls
331
- end
332
- else
333
- process_unknown
334
- end
335
- end
336
-
337
-
338
- #--
339
- # Requiring TLS is touchy, cf RFC2784.
340
- # Requiring AUTH seems to be much more reasonable.
341
- # We don't currently support any notion of deriving an authentication from the TLS
342
- # negotiation, although that would certainly be reasonable.
343
- # We DON'T allow MAIL FROM to be given twice.
344
- # We DON'T enforce all the various rules for validating the sender or
345
- # the reverse-path (like whether it should be null), and notifying the reverse
346
- # path in case of delivery problems. All of that is left to the calling application.
347
- #
348
- def process_mail_from sender
349
- if (@@parms[:starttls]==:required and !@state.include?(:starttls))
350
- send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
351
- elsif (@@parms[:auth]==:required and !@state.include?(:auth))
352
- send_data "550 This server requires authentication before MAIL FROM\r\n"
353
- elsif @state.include?(:mail_from)
354
- send_data "503 MAIL already given\r\n"
355
- else
356
- unless receive_sender sender
357
- send_data "550 sender is unacceptable\r\n"
358
- else
359
- send_data "250 Ok\r\n"
360
- @state << :mail_from
361
- end
362
- end
363
- end
364
-
365
- #--
366
- # Since we require :mail_from to have been seen before we process RCPT TO,
367
- # we don't need to repeat the tests for TLS and AUTH.
368
- # Note that we don't remember or do anything else with the recipients.
369
- # All of that is on the user code.
370
- # TODO: we should enforce user-definable limits on the total number of
371
- # recipients per transaction.
372
- # We might want to make sure that a given recipient is only seen once, but
373
- # for now we'll let that be the user's problem.
374
- #
375
- # User-written code can return a deferrable from receive_recipient.
376
- #
377
- def process_rcpt_to rcpt
378
- unless @state.include?(:mail_from)
379
- send_data "503 MAIL is required before RCPT\r\n"
380
- else
381
- succeeded = proc {
382
- send_data "250 Ok\r\n"
383
- @state << :rcpt unless @state.include?(:rcpt)
384
- }
385
- failed = proc {
386
- send_data "550 recipient is unacceptable\r\n"
387
- }
388
-
389
- d = receive_recipient rcpt
390
-
391
- if d.respond_to?(:set_deferred_status)
392
- d.callback(&succeeded)
393
- d.errback(&failed)
394
- else
395
- (d ? succeeded : failed).call
396
- end
397
-
398
- =begin
399
- unless receive_recipient rcpt
400
- send_data "550 recipient is unacceptable\r\n"
401
- else
402
- send_data "250 Ok\r\n"
403
- @state << :rcpt unless @state.include?(:rcpt)
404
- end
405
- =end
406
- end
407
- end
408
-
409
-
410
- # Send the incoming data to the application one chunk at a time, rather than
411
- # one line at a time. That lets the application be a little more flexible about
412
- # storing to disk, etc.
413
- # Since we clear the chunk array every time we submit it, the caller needs to be
414
- # aware to do things like dup it if he wants to keep it around across calls.
415
- #
416
- # DON'T reset the transaction upon disposition of the incoming message.
417
- # This means another DATA command can be accepted with the same sender and recipients.
418
- # If the client wants to reset, he can call RSET.
419
- # Not sure whether the standard requires a transaction-reset at this point, but it
420
- # appears not to.
421
- #
422
- # User-written code can return a Deferrable as a response from receive_message.
423
- #
424
- def process_data_line ln
425
- if ln == "."
426
- if @databuffer.length > 0
427
- receive_data_chunk @databuffer
428
- @databuffer.clear
429
- end
430
-
431
-
432
- succeeded = proc {
433
- send_data "250 Message accepted\r\n"
434
- }
435
- failed = proc {
436
- send_data "550 Message rejected\r\n"
437
- }
438
-
439
- d = receive_message
440
-
441
- if d.respond_to?(:set_deferred_status)
442
- d.callback(&succeeded)
443
- d.errback(&failed)
444
- else
445
- (d ? succeeded : failed).call
446
- end
447
-
448
- @state.delete :data
449
- else
450
- # slice off leading . if any
451
- ln.slice!(0...1) if ln[0] == 46
452
- @databuffer << ln
453
- if @databuffer.length > @@parms[:chunksize]
454
- receive_data_chunk @databuffer
455
- @databuffer.clear
456
- end
457
- end
458
- end
459
-
460
-
461
- #------------------------------------------
462
- # Everything from here on can be overridden in user code.
463
-
464
- # The greeting returned in the initial connection message to the client.
465
- def get_server_greeting
466
- "EventMachine SMTP Server"
467
- end
468
- # The domain name returned in the first line of the response to a
469
- # successful EHLO or HELO command.
470
- def get_server_domain
471
- "Ok EventMachine SMTP Server"
472
- end
473
-
474
- # A false response from this user-overridable method will cause a
475
- # 550 error to be returned to the remote client.
476
- #
477
- def receive_ehlo_domain domain
478
- true
479
- end
480
-
481
- # Return true or false to indicate that the authentication is acceptable.
482
- def receive_plain_auth user, password
483
- true
484
- end
485
-
486
- # Receives the argument of the MAIL FROM command. Return false to
487
- # indicate to the remote client that the sender is not accepted.
488
- # This can only be successfully called once per transaction.
489
- #
490
- def receive_sender sender
491
- true
492
- end
493
-
494
- # Receives the argument of a RCPT TO command. Can be given multiple
495
- # times per transaction. Return false to reject the recipient.
496
- #
497
- def receive_recipient rcpt
498
- true
499
- end
500
-
501
- # Sent when the remote peer issues the RSET command.
502
- # Since RSET is not allowed to fail (according to the protocol),
503
- # we ignore any return value from user overrides of this method.
504
- #
505
- def receive_reset
506
- end
507
-
508
- # Sent when the remote peer has ended the connection.
509
- #
510
- def connection_ended
511
- end
512
-
513
- # Called when the remote peer sends the DATA command.
514
- # Returning false will cause us to send a 550 error to the peer.
515
- # This can be useful for dealing with problems that arise from processing
516
- # the whole set of sender and recipients.
517
- #
518
- def receive_data_command
519
- true
520
- end
521
-
522
- # Sent when data from the remote peer is available. The size can be controlled
523
- # by setting the :chunksize parameter. This call can be made multiple times.
524
- # The goal is to strike a balance between sending the data to the application one
525
- # line at a time, and holding all of a very large message in memory.
526
- #
527
- def receive_data_chunk data
528
- @smtps_msg_size ||= 0
529
- @smtps_msg_size += data.join.length
530
- STDERR.write "<#{@smtps_msg_size}>"
531
- end
532
-
533
- # Sent after a message has been completely received. User code
534
- # must return true or false to indicate whether the message has
535
- # been accepted for delivery.
536
- def receive_message
537
- @@parms[:verbose] and $>.puts "Received complete message"
538
- true
539
- end
540
-
541
- # This is called when the protocol state is reset. It happens
542
- # when the remote client calls EHLO/HELO or RSET.
543
- def receive_transaction
544
- end
545
- end
546
- end
547
- end
1
+ #--
2
+ #
3
+ # Author:: Francis Cianfrocca (gmail: blackhedd)
4
+ # Homepage:: http://rubyeventmachine.com
5
+ # Date:: 16 July 2006
6
+ #
7
+ # See EventMachine and EventMachine::Connection for documentation and
8
+ # usage examples.
9
+ #
10
+ #----------------------------------------------------------------------------
11
+ #
12
+ # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
+ # Gmail: blackhedd
14
+ #
15
+ # This program is free software; you can redistribute it and/or modify
16
+ # it under the terms of either: 1) the GNU General Public License
17
+ # as published by the Free Software Foundation; either version 2 of the
18
+ # License, or (at your option) any later version; or 2) Ruby's License.
19
+ #
20
+ # See the file COPYING for complete licensing information.
21
+ #
22
+ #---------------------------------------------------------------------------
23
+ #
24
+ #
25
+
26
+ module EventMachine
27
+ module Protocols
28
+
29
+ # This is a protocol handler for the server side of SMTP.
30
+ # It's NOT a complete SMTP server obeying all the semantics of servers conforming to
31
+ # RFC2821. Rather, it uses overridable method stubs to communicate protocol states
32
+ # and data to user code. User code is responsible for doing the right things with the
33
+ # data in order to get complete and correct SMTP server behavior.
34
+ #
35
+ # Simple SMTP server example:
36
+ #
37
+ # class EmailServer < EM::P::SmtpServer
38
+ # def receive_plain_auth(user, pass)
39
+ # true
40
+ # end
41
+ #
42
+ # def get_server_domain
43
+ # "mock.smtp.server.local"
44
+ # end
45
+ #
46
+ # def get_server_greeting
47
+ # "mock smtp server greets you with impunity"
48
+ # end
49
+ #
50
+ # def receive_sender(sender)
51
+ # current.sender = sender
52
+ # true
53
+ # end
54
+ #
55
+ # def receive_recipient(recipient)
56
+ # current.recipient = recipient
57
+ # true
58
+ # end
59
+ #
60
+ # def receive_message
61
+ # current.received = true
62
+ # current.completed_at = Time.now
63
+ #
64
+ # p [:received_email, current]
65
+ # @current = OpenStruct.new
66
+ # true
67
+ # end
68
+ #
69
+ # def receive_ehlo_domain(domain)
70
+ # @ehlo_domain = domain
71
+ # true
72
+ # end
73
+ #
74
+ # def receive_data_command
75
+ # current.data = ""
76
+ # true
77
+ # end
78
+ #
79
+ # def receive_data_chunk(data)
80
+ # current.data << data.join("\n")
81
+ # true
82
+ # end
83
+ #
84
+ # def receive_transaction
85
+ # if @ehlo_domain
86
+ # current.ehlo_domain = @ehlo_domain
87
+ # @ehlo_domain = nil
88
+ # end
89
+ # true
90
+ # end
91
+ #
92
+ # def current
93
+ # @current ||= OpenStruct.new
94
+ # end
95
+ #
96
+ # def self.start(host = 'localhost', port = 1025)
97
+ # require 'ostruct'
98
+ # @server = EM.start_server host, port, self
99
+ # end
100
+ #
101
+ # def self.stop
102
+ # if @server
103
+ # EM.stop_server @server
104
+ # @server = nil
105
+ # end
106
+ # end
107
+ #
108
+ # def self.running?
109
+ # !!@server
110
+ # end
111
+ # end
112
+ #
113
+ # EM.run{ EmailServer.start }
114
+ #
115
+ #--
116
+ # Useful paragraphs in RFC-2821:
117
+ # 4.3.2: Concise list of command-reply sequences, in essence a text representation
118
+ # of the command state-machine.
119
+ #
120
+ # STARTTLS is defined in RFC2487.
121
+ # Observe that there are important rules governing whether a publicly-referenced server
122
+ # (meaning one whose Internet address appears in public MX records) may require the
123
+ # non-optional use of TLS.
124
+ # Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS.
125
+ class SmtpServer < EventMachine::Connection
126
+ include Protocols::LineText2
127
+
128
+ HeloRegex = /\AHELO\s*/i
129
+ EhloRegex = /\AEHLO\s*/i
130
+ QuitRegex = /\AQUIT/i
131
+ MailFromRegex = /\AMAIL FROM:\s*/i
132
+ RcptToRegex = /\ARCPT TO:\s*/i
133
+ DataRegex = /\ADATA/i
134
+ NoopRegex = /\ANOOP/i
135
+ RsetRegex = /\ARSET/i
136
+ VrfyRegex = /\AVRFY\s+/i
137
+ ExpnRegex = /\AEXPN\s+/i
138
+ HelpRegex = /\AHELP/i
139
+ StarttlsRegex = /\ASTARTTLS/i
140
+ AuthRegex = /\AAUTH\s+/i
141
+
142
+
143
+ # Class variable containing default parameters that can be overridden
144
+ # in application code.
145
+ # Individual objects of this class will make an instance-local copy of
146
+ # the class variable, so that they can be reconfigured on a per-instance
147
+ # basis.
148
+ #
149
+ # Chunksize is the number of data lines we'll buffer before
150
+ # sending them to the application. TODO, make this user-configurable.
151
+ #
152
+ @@parms = {
153
+ :chunksize => 4000,
154
+ :verbose => false
155
+ }
156
+ def self.parms= parms={}
157
+ @@parms.merge!(parms)
158
+ end
159
+
160
+
161
+
162
+ def initialize *args
163
+ super
164
+ @parms = @@parms
165
+ init_protocol_state
166
+ end
167
+
168
+ def parms= parms={}
169
+ @parms.merge!(parms)
170
+ end
171
+
172
+ # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM,
173
+ # #post_init will execute BEFORE the block passed to #start_server, for any
174
+ # given accepted connection. Since in this class we'll probably be getting
175
+ # a lot of initialization parameters, we want the guts of post_init to
176
+ # run AFTER the application has initialized the connection object. So we
177
+ # use a spawn to schedule the post_init to run later.
178
+ # It's a little weird, I admit. A reasonable alternative would be to set
179
+ # parameters as a class variable and to do that before accepting any connections.
180
+ #
181
+ # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.
182
+ #
183
+ def post_init
184
+ #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
185
+ #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
186
+ (EM.spawn {|x| x.send_server_greeting}).notify(self)
187
+ end
188
+
189
+ def send_server_greeting
190
+ send_data "220 #{get_server_greeting}\r\n"
191
+ end
192
+
193
+ def receive_line ln
194
+ @@parms[:verbose] and $>.puts ">>> #{ln}"
195
+
196
+ return process_data_line(ln) if @state.include?(:data)
197
+ return process_auth_line(ln) if @state.include?(:auth_incomplete)
198
+
199
+ case ln
200
+ when EhloRegex
201
+ process_ehlo $'.dup
202
+ when HeloRegex
203
+ process_helo $'.dup
204
+ when MailFromRegex
205
+ process_mail_from $'.dup
206
+ when RcptToRegex
207
+ process_rcpt_to $'.dup
208
+ when DataRegex
209
+ process_data
210
+ when RsetRegex
211
+ process_rset
212
+ when VrfyRegex
213
+ process_vrfy
214
+ when ExpnRegex
215
+ process_expn
216
+ when HelpRegex
217
+ process_help
218
+ when NoopRegex
219
+ process_noop
220
+ when QuitRegex
221
+ process_quit
222
+ when StarttlsRegex
223
+ process_starttls
224
+ when AuthRegex
225
+ process_auth $'.dup
226
+ else
227
+ process_unknown
228
+ end
229
+ end
230
+
231
+ # TODO - implement this properly, the implementation is a stub!
232
+ def process_vrfy
233
+ send_data "250 Ok, but unimplemented\r\n"
234
+ end
235
+ # TODO - implement this properly, the implementation is a stub!
236
+ def process_help
237
+ send_data "250 Ok, but unimplemented\r\n"
238
+ end
239
+ # TODO - implement this properly, the implementation is a stub!
240
+ def process_expn
241
+ send_data "250 Ok, but unimplemented\r\n"
242
+ end
243
+
244
+ #--
245
+ # This is called at several points to restore the protocol state
246
+ # to a pre-transaction state. In essence, we "forget" having seen
247
+ # any valid command except EHLO and STARTTLS.
248
+ # We also have to callback user code, in case they're keeping track
249
+ # of senders, recipients, and whatnot.
250
+ #
251
+ # We try to follow the convention of avoiding the verb "receive" for
252
+ # internal method names except receive_line (which we inherit), and
253
+ # using only receive_xxx for user-overridable stubs.
254
+ #
255
+ # init_protocol_state is called when we initialize the connection as
256
+ # well as during reset_protocol_state. It does NOT call the user
257
+ # override method. This enables us to promise the users that they
258
+ # won't see the overridable fire except after EHLO and RSET, and
259
+ # after a message has been received. Although the latter may be wrong.
260
+ # The standard may allow multiple DATA segments with the same set of
261
+ # senders and recipients.
262
+ #
263
+ def reset_protocol_state
264
+ init_protocol_state
265
+ s,@state = @state,[]
266
+ @state << :starttls if s.include?(:starttls)
267
+ @state << :ehlo if s.include?(:ehlo)
268
+ receive_transaction
269
+ end
270
+ def init_protocol_state
271
+ @state ||= []
272
+ end
273
+
274
+
275
+ #--
276
+ # EHLO/HELO is always legal, per the standard. On success
277
+ # it always clears buffers and initiates a mail "transaction."
278
+ # Which means that a MAIL FROM must follow.
279
+ #
280
+ # Per the standard, an EHLO/HELO or a RSET "initiates" an email
281
+ # transaction. Thereafter, MAIL FROM must be received before
282
+ # RCPT TO, before DATA. Not sure what this specific ordering
283
+ # achieves semantically, but it does make it easier to
284
+ # implement. We also support user-specified requirements for
285
+ # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
286
+ # without fulfilling tls and/or auth, if the user specified either
287
+ # or both as required. We need to check the extension standard
288
+ # for auth to see if a credential is discarded after a RSET along
289
+ # with all the rest of the state. We'll behave as if it is.
290
+ # Now clearly, we can't discard tls after its been negotiated
291
+ # without dropping the connection, so that flag doesn't get cleared.
292
+ #
293
+ def process_ehlo domain
294
+ if receive_ehlo_domain domain
295
+ send_data "250-#{get_server_domain}\r\n"
296
+ if @@parms[:starttls]
297
+ send_data "250-STARTTLS\r\n"
298
+ end
299
+ if @@parms[:auth]
300
+ send_data "250-AUTH PLAIN\r\n"
301
+ end
302
+ send_data "250-NO-SOLICITING\r\n"
303
+ # TODO, size needs to be configurable.
304
+ send_data "250 SIZE 20000000\r\n"
305
+ reset_protocol_state
306
+ @state << :ehlo
307
+ else
308
+ send_data "550 Requested action not taken\r\n"
309
+ end
310
+ end
311
+
312
+ def process_helo domain
313
+ if receive_ehlo_domain domain.dup
314
+ send_data "250 #{get_server_domain}\r\n"
315
+ reset_protocol_state
316
+ @state << :ehlo
317
+ else
318
+ send_data "550 Requested action not taken\r\n"
319
+ end
320
+ end
321
+
322
+ def process_quit
323
+ send_data "221 Ok\r\n"
324
+ close_connection_after_writing
325
+ end
326
+
327
+ def process_noop
328
+ send_data "250 Ok\r\n"
329
+ end
330
+
331
+ def process_unknown
332
+ send_data "500 Unknown command\r\n"
333
+ end
334
+
335
+ #--
336
+ # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
337
+ # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
338
+ # response and a continuation of the auth conversation.
339
+ #
340
+ def process_auth str
341
+ if @state.include?(:auth)
342
+ send_data "503 auth already issued\r\n"
343
+ elsif str =~ /\APLAIN\s?/i
344
+ if $'.length == 0
345
+ # we got a partial response, so let the client know to send the rest
346
+ @state << :auth_incomplete
347
+ send_data("334 \r\n")
348
+ else
349
+ # we got the initial response, so go ahead & process it
350
+ process_auth_line($')
351
+ end
352
+ #elsif str =~ /\ALOGIN\s+/i
353
+ else
354
+ send_data "504 auth mechanism not available\r\n"
355
+ end
356
+ end
357
+
358
+ def process_auth_line(line)
359
+ plain = line.unpack("m").first
360
+ discard,user,psw = plain.split("\000")
361
+ if receive_plain_auth user,psw
362
+ send_data "235 authentication ok\r\n"
363
+ @state << :auth
364
+ else
365
+ send_data "535 invalid authentication\r\n"
366
+ end
367
+ @state.delete :auth_incomplete
368
+ end
369
+
370
+ #--
371
+ # Unusually, we can deal with a Deferrable returned from the user application.
372
+ # This was added to deal with a special case in a particular application, but
373
+ # it would be a nice idea to add it to the other user-code callbacks.
374
+ #
375
+ def process_data
376
+ unless @state.include?(:rcpt)
377
+ send_data "503 Operation sequence error\r\n"
378
+ else
379
+ succeeded = proc {
380
+ send_data "354 Send it\r\n"
381
+ @state << :data
382
+ @databuffer = []
383
+ }
384
+ failed = proc {
385
+ send_data "550 Operation failed\r\n"
386
+ }
387
+
388
+ d = receive_data_command
389
+
390
+ if d.respond_to?(:callback)
391
+ d.callback(&succeeded)
392
+ d.errback(&failed)
393
+ else
394
+ (d ? succeeded : failed).call
395
+ end
396
+ end
397
+ end
398
+
399
+ def process_rset
400
+ reset_protocol_state
401
+ receive_reset
402
+ send_data "250 Ok\r\n"
403
+ end
404
+
405
+ def unbind
406
+ connection_ended
407
+ end
408
+
409
+ #--
410
+ # STARTTLS may not be issued before EHLO, or unless the user has chosen
411
+ # to support it.
412
+ # TODO, must support user-supplied certificates.
413
+ #
414
+ def process_starttls
415
+ if @@parms[:starttls]
416
+ if @state.include?(:starttls)
417
+ send_data "503 TLS Already negotiated\r\n"
418
+ elsif ! @state.include?(:ehlo)
419
+ send_data "503 EHLO required before STARTTLS\r\n"
420
+ else
421
+ send_data "220 Start TLS negotiation\r\n"
422
+ start_tls
423
+ @state << :starttls
424
+ end
425
+ else
426
+ process_unknown
427
+ end
428
+ end
429
+
430
+
431
+ #--
432
+ # Requiring TLS is touchy, cf RFC2784.
433
+ # Requiring AUTH seems to be much more reasonable.
434
+ # We don't currently support any notion of deriving an authentication from the TLS
435
+ # negotiation, although that would certainly be reasonable.
436
+ # We DON'T allow MAIL FROM to be given twice.
437
+ # We DON'T enforce all the various rules for validating the sender or
438
+ # the reverse-path (like whether it should be null), and notifying the reverse
439
+ # path in case of delivery problems. All of that is left to the calling application.
440
+ #
441
+ def process_mail_from sender
442
+ if (@@parms[:starttls]==:required and !@state.include?(:starttls))
443
+ send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
444
+ elsif (@@parms[:auth]==:required and !@state.include?(:auth))
445
+ send_data "550 This server requires authentication before MAIL FROM\r\n"
446
+ elsif @state.include?(:mail_from)
447
+ send_data "503 MAIL already given\r\n"
448
+ else
449
+ unless receive_sender sender
450
+ send_data "550 sender is unacceptable\r\n"
451
+ else
452
+ send_data "250 Ok\r\n"
453
+ @state << :mail_from
454
+ end
455
+ end
456
+ end
457
+
458
+ #--
459
+ # Since we require :mail_from to have been seen before we process RCPT TO,
460
+ # we don't need to repeat the tests for TLS and AUTH.
461
+ # Note that we don't remember or do anything else with the recipients.
462
+ # All of that is on the user code.
463
+ # TODO: we should enforce user-definable limits on the total number of
464
+ # recipients per transaction.
465
+ # We might want to make sure that a given recipient is only seen once, but
466
+ # for now we'll let that be the user's problem.
467
+ #
468
+ # User-written code can return a deferrable from receive_recipient.
469
+ #
470
+ def process_rcpt_to rcpt
471
+ unless @state.include?(:mail_from)
472
+ send_data "503 MAIL is required before RCPT\r\n"
473
+ else
474
+ succeeded = proc {
475
+ send_data "250 Ok\r\n"
476
+ @state << :rcpt unless @state.include?(:rcpt)
477
+ }
478
+ failed = proc {
479
+ send_data "550 recipient is unacceptable\r\n"
480
+ }
481
+
482
+ d = receive_recipient rcpt
483
+
484
+ if d.respond_to?(:set_deferred_status)
485
+ d.callback(&succeeded)
486
+ d.errback(&failed)
487
+ else
488
+ (d ? succeeded : failed).call
489
+ end
490
+
491
+ =begin
492
+ unless receive_recipient rcpt
493
+ send_data "550 recipient is unacceptable\r\n"
494
+ else
495
+ send_data "250 Ok\r\n"
496
+ @state << :rcpt unless @state.include?(:rcpt)
497
+ end
498
+ =end
499
+ end
500
+ end
501
+
502
+
503
+ # Send the incoming data to the application one chunk at a time, rather than
504
+ # one line at a time. That lets the application be a little more flexible about
505
+ # storing to disk, etc.
506
+ # Since we clear the chunk array every time we submit it, the caller needs to be
507
+ # aware to do things like dup it if he wants to keep it around across calls.
508
+ #
509
+ # DON'T reset the transaction upon disposition of the incoming message.
510
+ # This means another DATA command can be accepted with the same sender and recipients.
511
+ # If the client wants to reset, he can call RSET.
512
+ # Not sure whether the standard requires a transaction-reset at this point, but it
513
+ # appears not to.
514
+ #
515
+ # User-written code can return a Deferrable as a response from receive_message.
516
+ #
517
+ def process_data_line ln
518
+ if ln == "."
519
+ if @databuffer.length > 0
520
+ receive_data_chunk @databuffer
521
+ @databuffer.clear
522
+ end
523
+
524
+
525
+ succeeded = proc {
526
+ send_data "250 Message accepted\r\n"
527
+ }
528
+ failed = proc {
529
+ send_data "550 Message rejected\r\n"
530
+ }
531
+
532
+ d = receive_message
533
+
534
+ if d.respond_to?(:set_deferred_status)
535
+ d.callback(&succeeded)
536
+ d.errback(&failed)
537
+ else
538
+ (d ? succeeded : failed).call
539
+ end
540
+
541
+ @state.delete :data
542
+ else
543
+ # slice off leading . if any
544
+ ln.slice!(0...1) if ln[0] == 46
545
+ @databuffer << ln
546
+ if @databuffer.length > @@parms[:chunksize]
547
+ receive_data_chunk @databuffer
548
+ @databuffer.clear
549
+ end
550
+ end
551
+ end
552
+
553
+
554
+ #------------------------------------------
555
+ # Everything from here on can be overridden in user code.
556
+
557
+ # The greeting returned in the initial connection message to the client.
558
+ def get_server_greeting
559
+ "EventMachine SMTP Server"
560
+ end
561
+ # The domain name returned in the first line of the response to a
562
+ # successful EHLO or HELO command.
563
+ def get_server_domain
564
+ "Ok EventMachine SMTP Server"
565
+ end
566
+
567
+ # A false response from this user-overridable method will cause a
568
+ # 550 error to be returned to the remote client.
569
+ #
570
+ def receive_ehlo_domain domain
571
+ true
572
+ end
573
+
574
+ # Return true or false to indicate that the authentication is acceptable.
575
+ def receive_plain_auth user, password
576
+ true
577
+ end
578
+
579
+ # Receives the argument of the MAIL FROM command. Return false to
580
+ # indicate to the remote client that the sender is not accepted.
581
+ # This can only be successfully called once per transaction.
582
+ #
583
+ def receive_sender sender
584
+ true
585
+ end
586
+
587
+ # Receives the argument of a RCPT TO command. Can be given multiple
588
+ # times per transaction. Return false to reject the recipient.
589
+ #
590
+ def receive_recipient rcpt
591
+ true
592
+ end
593
+
594
+ # Sent when the remote peer issues the RSET command.
595
+ # Since RSET is not allowed to fail (according to the protocol),
596
+ # we ignore any return value from user overrides of this method.
597
+ #
598
+ def receive_reset
599
+ end
600
+
601
+ # Sent when the remote peer has ended the connection.
602
+ #
603
+ def connection_ended
604
+ end
605
+
606
+ # Called when the remote peer sends the DATA command.
607
+ # Returning false will cause us to send a 550 error to the peer.
608
+ # This can be useful for dealing with problems that arise from processing
609
+ # the whole set of sender and recipients.
610
+ #
611
+ def receive_data_command
612
+ true
613
+ end
614
+
615
+ # Sent when data from the remote peer is available. The size can be controlled
616
+ # by setting the :chunksize parameter. This call can be made multiple times.
617
+ # The goal is to strike a balance between sending the data to the application one
618
+ # line at a time, and holding all of a very large message in memory.
619
+ #
620
+ def receive_data_chunk data
621
+ @smtps_msg_size ||= 0
622
+ @smtps_msg_size += data.join.length
623
+ STDERR.write "<#{@smtps_msg_size}>"
624
+ end
625
+
626
+ # Sent after a message has been completely received. User code
627
+ # must return true or false to indicate whether the message has
628
+ # been accepted for delivery.
629
+ def receive_message
630
+ @@parms[:verbose] and $>.puts "Received complete message"
631
+ true
632
+ end
633
+
634
+ # This is called when the protocol state is reset. It happens
635
+ # when the remote client calls EHLO/HELO or RSET.
636
+ def receive_transaction
637
+ end
638
+ end
639
+ end
640
+ end