brianmario-eventmachine 0.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/COPYING +60 -0
  2. data/DEFERRABLES +138 -0
  3. data/EPOLL +141 -0
  4. data/GNU +281 -0
  5. data/KEYBOARD +38 -0
  6. data/LEGAL +25 -0
  7. data/LIGHTWEIGHT_CONCURRENCY +72 -0
  8. data/PURE_RUBY +77 -0
  9. data/README +74 -0
  10. data/RELEASE_NOTES +96 -0
  11. data/SMTP +9 -0
  12. data/SPAWNED_PROCESSES +93 -0
  13. data/TODO +10 -0
  14. data/eventmachine.gemspec +15 -0
  15. data/ext/binder.cpp +126 -0
  16. data/ext/binder.h +48 -0
  17. data/ext/cmain.cpp +553 -0
  18. data/ext/cplusplus.cpp +172 -0
  19. data/ext/ed.cpp +1473 -0
  20. data/ext/ed.h +361 -0
  21. data/ext/em.cpp +1890 -0
  22. data/ext/em.h +170 -0
  23. data/ext/emwin.cpp +300 -0
  24. data/ext/emwin.h +94 -0
  25. data/ext/epoll.cpp +26 -0
  26. data/ext/epoll.h +25 -0
  27. data/ext/eventmachine.h +90 -0
  28. data/ext/eventmachine_cpp.h +94 -0
  29. data/ext/extconf.rb +203 -0
  30. data/ext/files.cpp +94 -0
  31. data/ext/files.h +65 -0
  32. data/ext/kb.cpp +368 -0
  33. data/ext/page.cpp +107 -0
  34. data/ext/page.h +51 -0
  35. data/ext/pipe.cpp +327 -0
  36. data/ext/project.h +119 -0
  37. data/ext/rubymain.cpp +678 -0
  38. data/ext/sigs.cpp +89 -0
  39. data/ext/sigs.h +32 -0
  40. data/ext/ssl.cpp +408 -0
  41. data/ext/ssl.h +86 -0
  42. data/lib/em/deferrable.rb +208 -0
  43. data/lib/em/eventable.rb +39 -0
  44. data/lib/em/future.rb +62 -0
  45. data/lib/em/messages.rb +66 -0
  46. data/lib/em/processes.rb +68 -0
  47. data/lib/em/spawnable.rb +88 -0
  48. data/lib/em/streamer.rb +112 -0
  49. data/lib/eventmachine.rb +1756 -0
  50. data/lib/eventmachine_version.rb +31 -0
  51. data/lib/evma.rb +32 -0
  52. data/lib/evma/callback.rb +32 -0
  53. data/lib/evma/container.rb +75 -0
  54. data/lib/evma/factory.rb +77 -0
  55. data/lib/evma/protocol.rb +87 -0
  56. data/lib/evma/reactor.rb +48 -0
  57. data/lib/jeventmachine.rb +132 -0
  58. data/lib/pr_eventmachine.rb +1011 -0
  59. data/lib/protocols/buftok.rb +127 -0
  60. data/lib/protocols/header_and_content.rb +129 -0
  61. data/lib/protocols/httpcli2.rb +784 -0
  62. data/lib/protocols/httpclient.rb +264 -0
  63. data/lib/protocols/line_and_text.rb +122 -0
  64. data/lib/protocols/linetext2.rb +163 -0
  65. data/lib/protocols/postgres.rb +261 -0
  66. data/lib/protocols/saslauth.rb +179 -0
  67. data/lib/protocols/smtpclient.rb +308 -0
  68. data/lib/protocols/smtpserver.rb +543 -0
  69. data/lib/protocols/stomp.rb +130 -0
  70. data/lib/protocols/tcptest.rb +57 -0
  71. data/setup.rb +1585 -0
  72. metadata +126 -0
@@ -0,0 +1,543 @@
1
+ # $Id$
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
+
27
+ #require 'base64'
28
+
29
+ module EventMachine
30
+ module Protocols
31
+
32
+
33
+ =begin
34
+ This is a protocol handler for the server side of SMTP.
35
+ It's NOT a complete SMTP server obeying all the semantics of servers conforming to
36
+ RFC2821. Rather, it uses overridable method stubs to communicate protocol states
37
+ and data to user code. User code is responsible for doing the right things with the
38
+ data in order to get complete and correct SMTP server behavior.
39
+
40
+ Useful paragraphs in RFC-2821:
41
+ 4.3.2: Concise list of command-reply sequences, in essence a text representation
42
+ of the command state-machine.
43
+
44
+ STARTTLS is defined in RFC2487.
45
+ Observe that there are important rules governing whether a publicly-referenced server
46
+ (meaning one whose Internet address appears in public MX records) may require the
47
+ non-optional use of TLS.
48
+ Non-optional TLS does not apply to EHLO, NOOP, QUIT or STARTTLS.
49
+
50
+ =end
51
+
52
+ class SmtpServer < EventMachine::Connection
53
+ include Protocols::LineText2
54
+
55
+ HeloRegex = /\AHELO\s*/i
56
+ EhloRegex = /\AEHLO\s*/i
57
+ QuitRegex = /\AQUIT/i
58
+ MailFromRegex = /\AMAIL FROM:\s*/i
59
+ RcptToRegex = /\ARCPT TO:\s*/i
60
+ DataRegex = /\ADATA/i
61
+ NoopRegex = /\ANOOP/i
62
+ RsetRegex = /\ARSET/i
63
+ VrfyRegex = /\AVRFY\s+/i
64
+ ExpnRegex = /\AEXPN\s+/i
65
+ HelpRegex = /\AHELP/i
66
+ StarttlsRegex = /\ASTARTTLS/i
67
+ AuthRegex = /\AAUTH\s+/i
68
+
69
+
70
+ # Class variable containing default parameters that can be overridden
71
+ # in application code.
72
+ # Individual objects of this class will make an instance-local copy of
73
+ # the class variable, so that they can be reconfigured on a per-instance
74
+ # basis.
75
+ #
76
+ # Chunksize is the number of data lines we'll buffer before
77
+ # sending them to the application. TODO, make this user-configurable.
78
+ #
79
+ @@parms = {
80
+ :chunksize => 4000,
81
+ :verbose => false
82
+ }
83
+ def self.parms= parms={}
84
+ @@parms.merge!(parms)
85
+ end
86
+
87
+
88
+
89
+ def initialize *args
90
+ super
91
+ @parms = @@parms
92
+ init_protocol_state
93
+ end
94
+
95
+ def parms= parms={}
96
+ @parms.merge!(parms)
97
+ end
98
+
99
+ # In SMTP, the server talks first. But by a (perhaps flawed) axiom in EM,
100
+ # #post_init will execute BEFORE the block passed to #start_server, for any
101
+ # given accepted connection. Since in this class we'll probably be getting
102
+ # a lot of initialization parameters, we want the guts of post_init to
103
+ # run AFTER the application has initialized the connection object. So we
104
+ # use a spawn to schedule the post_init to run later.
105
+ # It's a little weird, I admit. A reasonable alternative would be to set
106
+ # parameters as a class variable and to do that before accepting any connections.
107
+ #
108
+ # OBSOLETE, now we have @@parms. But the spawn is nice to keep as an illustration.
109
+ #
110
+ def post_init
111
+ #send_data "220 #{get_server_greeting}\r\n" (ORIGINAL)
112
+ #(EM.spawn {|x| x.send_data "220 #{x.get_server_greeting}\r\n"}).notify(self)
113
+ (EM.spawn {|x| x.send_server_greeting}).notify(self)
114
+ end
115
+
116
+ def send_server_greeting
117
+ send_data "220 #{get_server_greeting}\r\n"
118
+ end
119
+
120
+ def receive_line ln
121
+ @@parms[:verbose] and $>.puts ">>> #{ln}"
122
+ if @state.include?(:data)
123
+ process_data_line ln
124
+ elsif ln =~ EhloRegex
125
+ process_ehlo $'.dup
126
+ elsif ln =~ HeloRegex
127
+ process_helo $'.dup
128
+ elsif ln =~ MailFromRegex
129
+ process_mail_from $'.dup
130
+ elsif ln =~ RcptToRegex
131
+ process_rcpt_to $'.dup
132
+ elsif ln =~ DataRegex
133
+ process_data
134
+ elsif ln =~ RsetRegex
135
+ process_rset
136
+ elsif ln =~ VrfyRegex
137
+ process_vrfy
138
+ elsif ln =~ ExpnRegex
139
+ process_expn
140
+ elsif ln =~ HelpRegex
141
+ process_help
142
+ elsif ln =~ NoopRegex
143
+ process_noop
144
+ elsif ln =~ QuitRegex
145
+ process_quit
146
+ elsif ln =~ StarttlsRegex
147
+ process_starttls
148
+ elsif ln =~ AuthRegex
149
+ process_auth $'.dup
150
+ else
151
+ process_unknown
152
+ end
153
+ end
154
+
155
+
156
+
157
+ #--
158
+ # This is called at several points to restore the protocol state
159
+ # to a pre-transaction state. In essence, we "forget" having seen
160
+ # any valid command except EHLO and STARTTLS.
161
+ # We also have to callback user code, in case they're keeping track
162
+ # of senders, recipients, and whatnot.
163
+ #
164
+ # We try to follow the convention of avoiding the verb "receive" for
165
+ # internal method names except receive_line (which we inherit), and
166
+ # using only receive_xxx for user-overridable stubs.
167
+ #
168
+ # init_protocol_state is called when we initialize the connection as
169
+ # well as during reset_protocol_state. It does NOT call the user
170
+ # override method. This enables us to promise the users that they
171
+ # won't see the overridable fire except after EHLO and RSET, and
172
+ # after a message has been received. Although the latter may be wrong.
173
+ # The standard may allow multiple DATA segments with the same set of
174
+ # senders and recipients.
175
+ #
176
+ def reset_protocol_state
177
+ init_protocol_state
178
+ s,@state = @state,[]
179
+ @state << :starttls if s.include?(:starttls)
180
+ @state << :ehlo if s.include?(:ehlo)
181
+ receive_transaction
182
+ end
183
+ def init_protocol_state
184
+ @state ||= []
185
+ end
186
+
187
+
188
+ #--
189
+ # EHLO/HELO is always legal, per the standard. On success
190
+ # it always clears buffers and initiates a mail "transaction."
191
+ # Which means that a MAIL FROM must follow.
192
+ #
193
+ # Per the standard, an EHLO/HELO or a RSET "initiates" an email
194
+ # transaction. Thereafter, MAIL FROM must be received before
195
+ # RCPT TO, before DATA. Not sure what this specific ordering
196
+ # achieves semantically, but it does make it easier to
197
+ # implement. We also support user-specified requirements for
198
+ # STARTTLS and AUTH. We make it impossible to proceed to MAIL FROM
199
+ # without fulfilling tls and/or auth, if the user specified either
200
+ # or both as required. We need to check the extension standard
201
+ # for auth to see if a credential is discarded after a RSET along
202
+ # with all the rest of the state. We'll behave as if it is.
203
+ # Now clearly, we can't discard tls after its been negotiated
204
+ # without dropping the connection, so that flag doesn't get cleared.
205
+ #
206
+ def process_ehlo domain
207
+ if receive_ehlo_domain domain
208
+ send_data "250-#{get_server_domain}\r\n"
209
+ if @@parms[:starttls]
210
+ send_data "250-STARTTLS\r\n"
211
+ end
212
+ if @@parms[:auth]
213
+ send_data "250-AUTH PLAIN LOGIN\r\n"
214
+ end
215
+ send_data "250-NO-SOLICITING\r\n"
216
+ # TODO, size needs to be configurable.
217
+ send_data "250 SIZE 20000000\r\n"
218
+ reset_protocol_state
219
+ @state << :ehlo
220
+ else
221
+ send_data "550 Requested action not taken\r\n"
222
+ end
223
+ end
224
+
225
+ def process_helo domain
226
+ if receive_ehlo_domain domain.dup
227
+ send_data "250 #{get_server_domain}\r\n"
228
+ reset_protocol_state
229
+ @state << :ehlo
230
+ else
231
+ send_data "550 Requested action not taken\r\n"
232
+ end
233
+ end
234
+
235
+ def process_quit
236
+ send_data "221 Ok\r\n"
237
+ close_connection_after_writing
238
+ end
239
+
240
+ def process_noop
241
+ send_data "250 Ok\r\n"
242
+ end
243
+
244
+ def process_unknown
245
+ send_data "500 Unknown command\r\n"
246
+ end
247
+
248
+ #--
249
+ # So far, only AUTH PLAIN is supported but we should do at least LOGIN as well.
250
+ # TODO, support clients that send AUTH PLAIN with no parameter, expecting a 3xx
251
+ # response and a continuation of the auth conversation.
252
+ #
253
+ def process_auth str
254
+ if @state.include?(:auth)
255
+ send_data "503 auth already issued\r\n"
256
+ elsif str =~ /\APLAIN\s+/i
257
+ plain = ($'.dup).unpack("m").first # Base64::decode64($'.dup)
258
+ discard,user,psw = plain.split("\000")
259
+ if receive_plain_auth user,psw
260
+ send_data "235 authentication ok\r\n"
261
+ @state << :auth
262
+ else
263
+ send_data "535 invalid authentication\r\n"
264
+ end
265
+ #elsif str =~ /\ALOGIN\s+/i
266
+ else
267
+ send_data "504 auth mechanism not available\r\n"
268
+ end
269
+ end
270
+
271
+ #--
272
+ # Unusually, we can deal with a Deferrable returned from the user application.
273
+ # This was added to deal with a special case in a particular application, but
274
+ # it would be a nice idea to add it to the other user-code callbacks.
275
+ #
276
+ def process_data
277
+ unless @state.include?(:rcpt)
278
+ send_data "503 Operation sequence error\r\n"
279
+ else
280
+ succeeded = proc {
281
+ send_data "354 Send it\r\n"
282
+ @state << :data
283
+ @databuffer = []
284
+ }
285
+ failed = proc {
286
+ send_data "550 Operation failed\r\n"
287
+ }
288
+
289
+ d = receive_data_command
290
+
291
+ if d.respond_to?(:callback)
292
+ d.callback &succeeded
293
+ d.errback &failed
294
+ else
295
+ (d ? succeeded : failed).call
296
+ end
297
+ end
298
+ end
299
+
300
+ def process_rset
301
+ reset_protocol_state
302
+ receive_reset
303
+ send_data "250 Ok\r\n"
304
+ end
305
+
306
+ def unbind
307
+ connection_ended
308
+ end
309
+
310
+ #--
311
+ # STARTTLS may not be issued before EHLO, or unless the user has chosen
312
+ # to support it.
313
+ # TODO, must support user-supplied certificates.
314
+ #
315
+ def process_starttls
316
+ if @@parms[:starttls]
317
+ if @state.include?(:starttls)
318
+ send_data "503 TLS Already negotiated\r\n"
319
+ elsif ! @state.include?(:ehlo)
320
+ send_data "503 EHLO required before STARTTLS\r\n"
321
+ else
322
+ send_data "220 Start TLS negotiation\r\n"
323
+ start_tls
324
+ @state << :starttls
325
+ end
326
+ else
327
+ process_unknown
328
+ end
329
+ end
330
+
331
+
332
+ #--
333
+ # Requiring TLS is touchy, cf RFC2784.
334
+ # Requiring AUTH seems to be much more reasonable.
335
+ # We don't currently support any notion of deriving an authentication from the TLS
336
+ # negotiation, although that would certainly be reasonable.
337
+ # We DON'T allow MAIL FROM to be given twice.
338
+ # We DON'T enforce all the various rules for validating the sender or
339
+ # the reverse-path (like whether it should be null), and notifying the reverse
340
+ # path in case of delivery problems. All of that is left to the calling application.
341
+ #
342
+ def process_mail_from sender
343
+ if (@@parms[:starttls]==:required and !@state.include?(:starttls))
344
+ send_data "550 This server requires STARTTLS before MAIL FROM\r\n"
345
+ elsif (@@parms[:auth]==:required and !@state.include?(:auth))
346
+ send_data "550 This server requires authentication before MAIL FROM\r\n"
347
+ elsif @state.include?(:mail_from)
348
+ send_data "503 MAIL already given\r\n"
349
+ else
350
+ unless receive_sender sender
351
+ send_data "550 sender is unacceptable\r\n"
352
+ else
353
+ send_data "250 Ok\r\n"
354
+ @state << :mail_from
355
+ end
356
+ end
357
+ end
358
+
359
+ #--
360
+ # Since we require :mail_from to have been seen before we process RCPT TO,
361
+ # we don't need to repeat the tests for TLS and AUTH.
362
+ # Note that we don't remember or do anything else with the recipients.
363
+ # All of that is on the user code.
364
+ # TODO: we should enforce user-definable limits on the total number of
365
+ # recipients per transaction.
366
+ # We might want to make sure that a given recipient is only seen once, but
367
+ # for now we'll let that be the user's problem.
368
+ #
369
+ # User-written code can return a deferrable from receive_recipient.
370
+ #
371
+ def process_rcpt_to rcpt
372
+ unless @state.include?(:mail_from)
373
+ send_data "503 MAIL is required before RCPT\r\n"
374
+ else
375
+ succeeded = proc {
376
+ send_data "250 Ok\r\n"
377
+ @state << :rcpt unless @state.include?(:rcpt)
378
+ }
379
+ failed = proc {
380
+ send_data "550 recipient is unacceptable\r\n"
381
+ }
382
+
383
+ d = receive_recipient rcpt
384
+
385
+ if d.respond_to?(:set_deferred_status)
386
+ d.callback &succeeded
387
+ d.errback &failed
388
+ else
389
+ (d ? succeeded : failed).call
390
+ end
391
+
392
+ =begin
393
+ unless receive_recipient rcpt
394
+ send_data "550 recipient is unacceptable\r\n"
395
+ else
396
+ send_data "250 Ok\r\n"
397
+ @state << :rcpt unless @state.include?(:rcpt)
398
+ end
399
+ =end
400
+ end
401
+ end
402
+
403
+
404
+ # Send the incoming data to the application one chunk at a time, rather than
405
+ # one line at a time. That lets the application be a little more flexible about
406
+ # storing to disk, etc.
407
+ # Since we clear the chunk array every time we submit it, the caller needs to be
408
+ # aware to do things like dup it if he wants to keep it around across calls.
409
+ #
410
+ # DON'T reset the transaction upon disposition of the incoming message.
411
+ # This means another DATA command can be accepted with the same sender and recipients.
412
+ # If the client wants to reset, he can call RSET.
413
+ # Not sure whether the standard requires a transaction-reset at this point, but it
414
+ # appears not to.
415
+ #
416
+ # User-written code can return a Deferrable as a response from receive_message.
417
+ #
418
+ def process_data_line ln
419
+ if ln == "."
420
+ if @databuffer.length > 0
421
+ receive_data_chunk @databuffer
422
+ @databuffer.clear
423
+ end
424
+
425
+
426
+ succeeded = proc {
427
+ send_data "250 Message accepted\r\n"
428
+ }
429
+ failed = proc {
430
+ send_data "550 Message rejected\r\n"
431
+ }
432
+
433
+ d = receive_message
434
+
435
+ if d.respond_to?(:set_deferred_status)
436
+ d.callback &succeeded
437
+ d.errback &failed
438
+ else
439
+ (d ? succeeded : failed).call
440
+ end
441
+
442
+ @state.delete :data
443
+ else
444
+ # slice off leading . if any
445
+ ln.slice!(0...1) if ln[0] == 46
446
+ @databuffer << ln
447
+ if @databuffer.length > @@parms[:chunksize]
448
+ receive_data_chunk @databuffer
449
+ @databuffer.clear
450
+ end
451
+ end
452
+ end
453
+
454
+
455
+ #------------------------------------------
456
+ # Everything from here on can be overridden in user code.
457
+
458
+ # The greeting returned in the initial connection message to the client.
459
+ def get_server_greeting
460
+ "EventMachine SMTP Server"
461
+ end
462
+ # The domain name returned in the first line of the response to a
463
+ # successful EHLO or HELO command.
464
+ def get_server_domain
465
+ "Ok EventMachine SMTP Server"
466
+ end
467
+
468
+ # A false response from this user-overridable method will cause a
469
+ # 550 error to be returned to the remote client.
470
+ #
471
+ def receive_ehlo_domain domain
472
+ true
473
+ end
474
+
475
+ # Return true or false to indicate that the authentication is acceptable.
476
+ def receive_plain_auth user, password
477
+ true
478
+ end
479
+
480
+ # Receives the argument of the MAIL FROM command. Return false to
481
+ # indicate to the remote client that the sender is not accepted.
482
+ # This can only be successfully called once per transaction.
483
+ #
484
+ def receive_sender sender
485
+ true
486
+ end
487
+
488
+ # Receives the argument of a RCPT TO command. Can be given multiple
489
+ # times per transaction. Return false to reject the recipient.
490
+ #
491
+ def receive_recipient rcpt
492
+ true
493
+ end
494
+
495
+ # Sent when the remote peer issues the RSET command.
496
+ # Since RSET is not allowed to fail (according to the protocol),
497
+ # we ignore any return value from user overrides of this method.
498
+ #
499
+ def receive_reset
500
+ end
501
+
502
+ # Sent when the remote peer has ended the connection.
503
+ #
504
+ def connection_ended
505
+ end
506
+
507
+ # Called when the remote peer sends the DATA command.
508
+ # Returning false will cause us to send a 550 error to the peer.
509
+ # This can be useful for dealing with problems that arise from processing
510
+ # the whole set of sender and recipients.
511
+ #
512
+ def receive_data_command
513
+ true
514
+ end
515
+
516
+ # Sent when data from the remote peer is available. The size can be controlled
517
+ # by setting the :chunksize parameter. This call can be made multiple times.
518
+ # The goal is to strike a balance between sending the data to the application one
519
+ # line at a time, and holding all of a very large message in memory.
520
+ #
521
+ def receive_data_chunk data
522
+ @smtps_msg_size ||= 0
523
+ @smtps_msg_size += data.join.length
524
+ STDERR.write "<#{@smtps_msg_size}>"
525
+ end
526
+
527
+ # Sent after a message has been completely received. User code
528
+ # must return true or false to indicate whether the message has
529
+ # been accepted for delivery.
530
+ def receive_message
531
+ @@parms[:verbose] and $>.puts "Received complete message"
532
+ true
533
+ end
534
+
535
+ # This is called when the protocol state is reset. It happens
536
+ # when the remote client calls EHLO/HELO or RSET.
537
+ def receive_transaction
538
+ end
539
+ end
540
+ end
541
+ end
542
+
543
+