source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/net/smtp.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 19.9 KB
Line 
1# = net/smtp.rb
2#
3# Copyright (c) 1999-2003 Yukihiro Matsumoto.
4#
5# Copyright (c) 1999-2003 Minero Aoki.
6#
7# Written & maintained by Minero Aoki <[email protected]>.
8#
9# Documented by William Webber and Minero Aoki.
10#
11# This program is free software. You can re-distribute and/or
12# modify this program under the same terms as Ruby itself,
13# Ruby Distribute License or GNU General Public License.
14#
15# NOTE: You can find Japanese version of this document in
16# the doc/net directory of the standard ruby interpreter package.
17#
18# $Id: smtp.rb 11708 2007-02-12 23:01:19Z shyouhei $
19#
20# See Net::SMTP for documentation.
21#
22
23require 'net/protocol'
24require 'digest/md5'
25
26module Net
27
28 # Module mixed in to all SMTP error classes
29 module SMTPError
30 # This *class* is module for some reason.
31 # In ruby 1.9.x, this module becomes a class.
32 end
33
34 # Represents an SMTP authentication error.
35 class SMTPAuthenticationError < ProtoAuthError
36 include SMTPError
37 end
38
39 # Represents SMTP error code 420 or 450, a temporary error.
40 class SMTPServerBusy < ProtoServerError
41 include SMTPError
42 end
43
44 # Represents an SMTP command syntax error (error code 500)
45 class SMTPSyntaxError < ProtoSyntaxError
46 include SMTPError
47 end
48
49 # Represents a fatal SMTP error (error code 5xx, except for 500)
50 class SMTPFatalError < ProtoFatalError
51 include SMTPError
52 end
53
54 # Unexpected reply code returned from server.
55 class SMTPUnknownError < ProtoUnknownError
56 include SMTPError
57 end
58
59 #
60 # = Net::SMTP
61 #
62 # == What is This Library?
63 #
64 # This library provides functionality to send internet
65 # mail via SMTP, the Simple Mail Transfer Protocol. For details of
66 # SMTP itself, see [RFC2821] (http://www.ietf.org/rfc/rfc2821.txt).
67 #
68 # == What is This Library NOT?
69 #
70 # This library does NOT provide functions to compose internet mails.
71 # You must create them by yourself. If you want better mail support,
72 # try RubyMail or TMail. You can get both libraries from RAA.
73 # (http://www.ruby-lang.org/en/raa.html)
74 #
75 # FYI: the official documentation on internet mail is: [RFC2822] (http://www.ietf.org/rfc/rfc2822.txt).
76 #
77 # == Examples
78 #
79 # === Sending Messages
80 #
81 # You must open a connection to an SMTP server before sending messages.
82 # The first argument is the address of your SMTP server, and the second
83 # argument is the port number. Using SMTP.start with a block is the simplest
84 # way to do this. This way, the SMTP connection is closed automatically
85 # after the block is executed.
86 #
87 # require 'net/smtp'
88 # Net::SMTP.start('your.smtp.server', 25) do |smtp|
89 # # Use the SMTP object smtp only in this block.
90 # end
91 #
92 # Replace 'your.smtp.server' with your SMTP server. Normally
93 # your system manager or internet provider supplies a server
94 # for you.
95 #
96 # Then you can send messages.
97 #
98 # msgstr = <<END_OF_MESSAGE
99 # From: Your Name <[email protected]>
100 # To: Destination Address <[email protected]>
101 # Subject: test message
102 # Date: Sat, 23 Jun 2001 16:26:43 +0900
103 # Message-Id: <[email protected]>
104 #
105 # This is a test message.
106 # END_OF_MESSAGE
107 #
108 # require 'net/smtp'
109 # Net::SMTP.start('your.smtp.server', 25) do |smtp|
110 # smtp.send_message msgstr,
111 # '[email protected]',
112 # '[email protected]'
113 # end
114 #
115 # === Closing the Session
116 #
117 # You MUST close the SMTP session after sending messages, by calling
118 # the #finish method:
119 #
120 # # using SMTP#finish
121 # smtp = Net::SMTP.start('your.smtp.server', 25)
122 # smtp.send_message msgstr, 'from@address', 'to@address'
123 # smtp.finish
124 #
125 # You can also use the block form of SMTP.start/SMTP#start. This closes
126 # the SMTP session automatically:
127 #
128 # # using block form of SMTP.start
129 # Net::SMTP.start('your.smtp.server', 25) do |smtp|
130 # smtp.send_message msgstr, 'from@address', 'to@address'
131 # end
132 #
133 # I strongly recommend this scheme. This form is simpler and more robust.
134 #
135 # === HELO domain
136 #
137 # In almost all situations, you must provide a third argument
138 # to SMTP.start/SMTP#start. This is the domain name which you are on
139 # (the host to send mail from). It is called the "HELO domain".
140 # The SMTP server will judge whether it should send or reject
141 # the SMTP session by inspecting the HELO domain.
142 #
143 # Net::SMTP.start('your.smtp.server', 25,
144 # 'mail.from.domain') { |smtp| ... }
145 #
146 # === SMTP Authentication
147 #
148 # The Net::SMTP class supports three authentication schemes;
149 # PLAIN, LOGIN and CRAM MD5. (SMTP Authentication: [RFC2554])
150 # To use SMTP authentication, pass extra arguments to
151 # SMTP.start/SMTP#start.
152 #
153 # # PLAIN
154 # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
155 # 'Your Account', 'Your Password', :plain)
156 # # LOGIN
157 # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
158 # 'Your Account', 'Your Password', :login)
159 #
160 # # CRAM MD5
161 # Net::SMTP.start('your.smtp.server', 25, 'mail.from.domain',
162 # 'Your Account', 'Your Password', :cram_md5)
163 #
164 class SMTP
165
166 Revision = %q$Revision: 11708 $.split[1]
167
168 # The default SMTP port, port 25.
169 def SMTP.default_port
170 25
171 end
172
173 #
174 # Creates a new Net::SMTP object.
175 #
176 # +address+ is the hostname or ip address of your SMTP
177 # server. +port+ is the port to connect to; it defaults to
178 # port 25.
179 #
180 # This method does not open the TCP connection. You can use
181 # SMTP.start instead of SMTP.new if you want to do everything
182 # at once. Otherwise, follow SMTP.new with SMTP#start.
183 #
184 def initialize( address, port = nil )
185 @address = address
186 @port = (port || SMTP.default_port)
187 @esmtp = true
188 @socket = nil
189 @started = false
190 @open_timeout = 30
191 @read_timeout = 60
192 @error_occured = false
193 @debug_output = nil
194 end
195
196 # Provide human-readable stringification of class state.
197 def inspect
198 "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
199 end
200
201 # +true+ if the SMTP object uses ESMTP (which it does by default).
202 def esmtp?
203 @esmtp
204 end
205
206 #
207 # Set whether to use ESMTP or not. This should be done before
208 # calling #start. Note that if #start is called in ESMTP mode,
209 # and the connection fails due to a ProtocolError, the SMTP
210 # object will automatically switch to plain SMTP mode and
211 # retry (but not vice versa).
212 #
213 def esmtp=( bool )
214 @esmtp = bool
215 end
216
217 alias esmtp esmtp?
218
219 # The address of the SMTP server to connect to.
220 attr_reader :address
221
222 # The port number of the SMTP server to connect to.
223 attr_reader :port
224
225 # Seconds to wait while attempting to open a connection.
226 # If the connection cannot be opened within this time, a
227 # TimeoutError is raised.
228 attr_accessor :open_timeout
229
230 # Seconds to wait while reading one block (by one read(2) call).
231 # If the read(2) call does not complete within this time, a
232 # TimeoutError is raised.
233 attr_reader :read_timeout
234
235 # Set the number of seconds to wait until timing-out a read(2)
236 # call.
237 def read_timeout=( sec )
238 @socket.read_timeout = sec if @socket
239 @read_timeout = sec
240 end
241
242 #
243 # WARNING: This method causes serious security holes.
244 # Use this method for only debugging.
245 #
246 # Set an output stream for debug logging.
247 # You must call this before #start.
248 #
249 # # example
250 # smtp = Net::SMTP.new(addr, port)
251 # smtp.set_debug_output $stderr
252 # smtp.start do |smtp|
253 # ....
254 # end
255 #
256 def set_debug_output( arg )
257 @debug_output = arg
258 end
259
260 #
261 # SMTP session control
262 #
263
264 #
265 # Creates a new Net::SMTP object and connects to the server.
266 #
267 # This method is equivalent to:
268 #
269 # Net::SMTP.new(address, port).start(helo_domain, account, password, authtype)
270 #
271 # === Example
272 #
273 # Net::SMTP.start('your.smtp.server') do |smtp|
274 # smtp.send_message msgstr, '[email protected]', ['[email protected]']
275 # end
276 #
277 # === Block Usage
278 #
279 # If called with a block, the newly-opened Net::SMTP object is yielded
280 # to the block, and automatically closed when the block finishes. If called
281 # without a block, the newly-opened Net::SMTP object is returned to
282 # the caller, and it is the caller's responsibility to close it when
283 # finished.
284 #
285 # === Parameters
286 #
287 # +address+ is the hostname or ip address of your smtp server.
288 #
289 # +port+ is the port to connect to; it defaults to port 25.
290 #
291 # +helo+ is the _HELO_ _domain_ provided by the client to the
292 # server (see overview comments); it defaults to 'localhost.localdomain'.
293 #
294 # The remaining arguments are used for SMTP authentication, if required
295 # or desired. +user+ is the account name; +secret+ is your password
296 # or other authentication token; and +authtype+ is the authentication
297 # type, one of :plain, :login, or :cram_md5. See the discussion of
298 # SMTP Authentication in the overview notes.
299 #
300 # === Errors
301 #
302 # This method may raise:
303 #
304 # * Net::SMTPAuthenticationError
305 # * Net::SMTPServerBusy
306 # * Net::SMTPSyntaxError
307 # * Net::SMTPFatalError
308 # * Net::SMTPUnknownError
309 # * IOError
310 # * TimeoutError
311 #
312 def SMTP.start( address, port = nil,
313 helo = 'localhost.localdomain',
314 user = nil, secret = nil, authtype = nil,
315 &block) # :yield: smtp
316 new(address, port).start(helo, user, secret, authtype, &block)
317 end
318
319 # +true+ if the SMTP session has been started.
320 def started?
321 @started
322 end
323
324 #
325 # Opens a TCP connection and starts the SMTP session.
326 #
327 # === Parameters
328 #
329 # +helo+ is the _HELO_ _domain_ that you'll dispatch mails from; see
330 # the discussion in the overview notes.
331 #
332 # If both of +user+ and +secret+ are given, SMTP authentication
333 # will be attempted using the AUTH command. +authtype+ specifies
334 # the type of authentication to attempt; it must be one of
335 # :login, :plain, and :cram_md5. See the notes on SMTP Authentication
336 # in the overview.
337 #
338 # === Block Usage
339 #
340 # When this methods is called with a block, the newly-started SMTP
341 # object is yielded to the block, and automatically closed after
342 # the block call finishes. Otherwise, it is the caller's
343 # responsibility to close the session when finished.
344 #
345 # === Example
346 #
347 # This is very similar to the class method SMTP.start.
348 #
349 # require 'net/smtp'
350 # smtp = Net::SMTP.new('smtp.mail.server', 25)
351 # smtp.start(helo_domain, account, password, authtype) do |smtp|
352 # smtp.send_message msgstr, '[email protected]', ['[email protected]']
353 # end
354 #
355 # The primary use of this method (as opposed to SMTP.start)
356 # is probably to set debugging (#set_debug_output) or ESMTP
357 # (#esmtp=), which must be done before the session is
358 # started.
359 #
360 # === Errors
361 #
362 # If session has already been started, an IOError will be raised.
363 #
364 # This method may raise:
365 #
366 # * Net::SMTPAuthenticationError
367 # * Net::SMTPServerBusy
368 # * Net::SMTPSyntaxError
369 # * Net::SMTPFatalError
370 # * Net::SMTPUnknownError
371 # * IOError
372 # * TimeoutError
373 #
374 def start( helo = 'localhost.localdomain',
375 user = nil, secret = nil, authtype = nil ) # :yield: smtp
376 if block_given?
377 begin
378 do_start(helo, user, secret, authtype)
379 return yield(self)
380 ensure
381 do_finish
382 end
383 else
384 do_start(helo, user, secret, authtype)
385 return self
386 end
387 end
388
389 def do_start( helodomain, user, secret, authtype )
390 raise IOError, 'SMTP session already started' if @started
391 check_auth_args user, secret, authtype if user or secret
392
393 @socket = InternetMessageIO.old_open(@address, @port,
394 @open_timeout, @read_timeout,
395 @debug_output)
396 check_response(critical { recv_response() })
397 begin
398 if @esmtp
399 ehlo helodomain
400 else
401 helo helodomain
402 end
403 rescue ProtocolError
404 if @esmtp
405 @esmtp = false
406 @error_occured = false
407 retry
408 end
409 raise
410 end
411 authenticate user, secret, authtype if user
412 @started = true
413 ensure
414 @socket.close if not @started and @socket and not @socket.closed?
415 end
416 private :do_start
417
418 # Finishes the SMTP session and closes TCP connection.
419 # Raises IOError if not started.
420 def finish
421 raise IOError, 'not yet started' unless started?
422 do_finish
423 end
424
425 def do_finish
426 quit if @socket and not @socket.closed? and not @error_occured
427 ensure
428 @started = false
429 @error_occured = false
430 @socket.close if @socket and not @socket.closed?
431 @socket = nil
432 end
433 private :do_finish
434
435 #
436 # message send
437 #
438
439 public
440
441 #
442 # Sends +msgstr+ as a message. Single CR ("\r") and LF ("\n") found
443 # in the +msgstr+, are converted into the CR LF pair. You cannot send a
444 # binary message with this method. +msgstr+ should include both
445 # the message headers and body.
446 #
447 # +from_addr+ is a String representing the source mail address.
448 #
449 # +to_addr+ is a String or Strings or Array of Strings, representing
450 # the destination mail address or addresses.
451 #
452 # === Example
453 #
454 # Net::SMTP.start('smtp.example.com') do |smtp|
455 # smtp.send_message msgstr,
456 # '[email protected]',
457 # ['[email protected]', '[email protected]']
458 # end
459 #
460 # === Errors
461 #
462 # This method may raise:
463 #
464 # * Net::SMTPServerBusy
465 # * Net::SMTPSyntaxError
466 # * Net::SMTPFatalError
467 # * Net::SMTPUnknownError
468 # * IOError
469 # * TimeoutError
470 #
471 def send_message( msgstr, from_addr, *to_addrs )
472 send0(from_addr, to_addrs.flatten) {
473 @socket.write_message msgstr
474 }
475 end
476
477 alias send_mail send_message
478 alias sendmail send_message # obsolete
479
480 #
481 # Opens a message writer stream and gives it to the block.
482 # The stream is valid only in the block, and has these methods:
483 #
484 # puts(str = ''):: outputs STR and CR LF.
485 # print(str):: outputs STR.
486 # printf(fmt, *args):: outputs sprintf(fmt,*args).
487 # write(str):: outputs STR and returns the length of written bytes.
488 # <<(str):: outputs STR and returns self.
489 #
490 # If a single CR ("\r") or LF ("\n") is found in the message,
491 # it is converted to the CR LF pair. You cannot send a binary
492 # message with this method.
493 #
494 # === Parameters
495 #
496 # +from_addr+ is a String representing the source mail address.
497 #
498 # +to_addr+ is a String or Strings or Array of Strings, representing
499 # the destination mail address or addresses.
500 #
501 # === Example
502 #
503 # Net::SMTP.start('smtp.example.com', 25) do |smtp|
504 # smtp.open_message_stream('[email protected]', ['[email protected]']) do |f|
505 # f.puts 'From: [email protected]'
506 # f.puts 'To: [email protected]'
507 # f.puts 'Subject: test message'
508 # f.puts
509 # f.puts 'This is a test message.'
510 # end
511 # end
512 #
513 # === Errors
514 #
515 # This method may raise:
516 #
517 # * Net::SMTPServerBusy
518 # * Net::SMTPSyntaxError
519 # * Net::SMTPFatalError
520 # * Net::SMTPUnknownError
521 # * IOError
522 # * TimeoutError
523 #
524 def open_message_stream( from_addr, *to_addrs, &block ) # :yield: stream
525 send0(from_addr, to_addrs.flatten) {
526 @socket.write_message_by_block(&block)
527 }
528 end
529
530 alias ready open_message_stream # obsolete
531
532 private
533
534 def send0( from_addr, to_addrs )
535 raise IOError, 'closed session' unless @socket
536 raise ArgumentError, 'mail destination not given' if to_addrs.empty?
537 if $SAFE > 0
538 raise SecurityError, 'tainted from_addr' if from_addr.tainted?
539 to_addrs.each do |to|
540 raise SecurityError, 'tainted to_addr' if to.tainted?
541 end
542 end
543
544 mailfrom from_addr
545 to_addrs.each do |to|
546 rcptto to
547 end
548 res = critical {
549 check_response(get_response('DATA'), true)
550 yield
551 recv_response()
552 }
553 check_response(res)
554 end
555
556 #
557 # auth
558 #
559
560 private
561
562 def check_auth_args( user, secret, authtype )
563 raise ArgumentError, 'both user and secret are required'\
564 unless user and secret
565 auth_method = "auth_#{authtype || 'cram_md5'}"
566 raise ArgumentError, "wrong auth type #{authtype}"\
567 unless respond_to?(auth_method, true)
568 end
569
570 def authenticate( user, secret, authtype )
571 __send__("auth_#{authtype || 'cram_md5'}", user, secret)
572 end
573
574 def auth_plain( user, secret )
575 res = critical { get_response('AUTH PLAIN %s',
576 base64_encode("\0#{user}\0#{secret}")) }
577 raise SMTPAuthenticationError, res unless /\A2../ === res
578 end
579
580 def auth_login( user, secret )
581 res = critical {
582 check_response(get_response('AUTH LOGIN'), true)
583 check_response(get_response(base64_encode(user)), true)
584 get_response(base64_encode(secret))
585 }
586 raise SMTPAuthenticationError, res unless /\A2../ === res
587 end
588
589 def auth_cram_md5( user, secret )
590 # CRAM-MD5: [RFC2195]
591 res = nil
592 critical {
593 res = check_response(get_response('AUTH CRAM-MD5'), true)
594 challenge = res.split(/ /)[1].unpack('m')[0]
595 secret = Digest::MD5.digest(secret) if secret.size > 64
596
597 isecret = secret + "\0" * (64 - secret.size)
598 osecret = isecret.dup
599 0.upto(63) do |i|
600 isecret[i] ^= 0x36
601 osecret[i] ^= 0x5c
602 end
603 tmp = Digest::MD5.digest(isecret + challenge)
604 tmp = Digest::MD5.hexdigest(osecret + tmp)
605
606 res = get_response(base64_encode(user + ' ' + tmp))
607 }
608 raise SMTPAuthenticationError, res unless /\A2../ === res
609 end
610
611 def base64_encode( str )
612 # expects "str" may not become too long
613 [str].pack('m').gsub(/\s+/, '')
614 end
615
616 #
617 # SMTP command dispatcher
618 #
619
620 private
621
622 def helo( domain )
623 getok('HELO %s', domain)
624 end
625
626 def ehlo( domain )
627 getok('EHLO %s', domain)
628 end
629
630 def mailfrom( fromaddr )
631 getok('MAIL FROM:<%s>', fromaddr)
632 end
633
634 def rcptto( to )
635 getok('RCPT TO:<%s>', to)
636 end
637
638 def quit
639 getok('QUIT')
640 end
641
642 #
643 # row level library
644 #
645
646 private
647
648 def getok( fmt, *args )
649 res = critical {
650 @socket.writeline sprintf(fmt, *args)
651 recv_response()
652 }
653 return check_response(res)
654 end
655
656 def get_response( fmt, *args )
657 @socket.writeline sprintf(fmt, *args)
658 recv_response()
659 end
660
661 def recv_response
662 res = ''
663 while true
664 line = @socket.readline
665 res << line << "\n"
666 break unless line[3] == ?- # "210-PIPELINING"
667 end
668 res
669 end
670
671 def check_response( res, allow_continue = false )
672 return res if /\A2/ === res
673 return res if allow_continue and /\A3/ === res
674 err = case res
675 when /\A4/ then SMTPServerBusy
676 when /\A50/ then SMTPSyntaxError
677 when /\A55/ then SMTPFatalError
678 else SMTPUnknownError
679 end
680 raise err, res
681 end
682
683 def critical( &block )
684 return '200 dummy reply code' if @error_occured
685 begin
686 return yield()
687 rescue Exception
688 @error_occured = true
689 raise
690 end
691 end
692
693 end # class SMTP
694
695 SMTPSession = SMTP
696
697end # module Net
Note: See TracBrowser for help on using the repository browser.