Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / net / smtp.rb
blob4cca3673607a847f8d3bcab5fe899b2098dfad83
1 # = net/smtp.rb
2
3 # Copyright (c) 1999-2003 Yukihiro Matsumoto.
5 # Copyright (c) 1999-2003 Minero Aoki.
6
7 # Written & maintained by Minero Aoki <aamine@loveruby.net>.
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 $
20 # See Net::SMTP for documentation. 
21
23 require 'net/protocol'
24 require 'digest/md5'
26 module Net
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
34   # Represents an SMTP authentication error.
35   class SMTPAuthenticationError < ProtoAuthError
36     include SMTPError
37   end
39   # Represents SMTP error code 420 or 450, a temporary error.
40   class SMTPServerBusy < ProtoServerError
41     include SMTPError
42   end
44   # Represents an SMTP command syntax error (error code 500)
45   class SMTPSyntaxError < ProtoSyntaxError
46     include SMTPError
47   end
49   # Represents a fatal SMTP error (error code 5xx, except for 500)
50   class SMTPFatalError < ProtoFatalError
51     include SMTPError
52   end
54   # Unexpected reply code returned from server.
55   class SMTPUnknownError < ProtoUnknownError
56     include SMTPError
57   end
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 <your@mail.address>
100   #     To: Destination Address <someone@example.com>
101   #     Subject: test message
102   #     Date: Sat, 23 Jun 2001 16:26:43 +0900
103   #     Message-Id: <unique.message.id.string@example.com>
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   #                         'your@mail.address',
112   #                         'his_addess@example.com'
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
166     Revision = %q$Revision: 11708 $.split[1]
168     # The default SMTP port, port 25.
169     def SMTP.default_port
170       25
171     end
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
196     # Provide human-readable stringification of class state.
197     def inspect
198       "#<#{self.class} #{@address}:#{@port} started=#{@started}>"
199     end
201     # +true+ if the SMTP object uses ESMTP (which it does by default).
202     def esmtp?
203       @esmtp
204     end
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
217     alias esmtp esmtp?
219     # The address of the SMTP server to connect to.
220     attr_reader :address
222     # The port number of the SMTP server to connect to.
223     attr_reader :port
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
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
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
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
260     #
261     # SMTP session control
262     #
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, 'from@example.com', ['dest@example.com']
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
319     # +true+ if the SMTP session has been started.
320     def started?
321       @started
322     end
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, 'from@example.com', ['dest@example.com']
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
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
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
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
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
435     #
436     # message send
437     #
439     public
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     #                         'from@example.com',
457     #                         ['dest@example.com', 'dest2@example.com']
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
477     alias send_mail send_message
478     alias sendmail send_message   # obsolete
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('from@example.com', ['dest@example.com']) do |f|
505     #         f.puts 'From: from@example.com'
506     #         f.puts 'To: dest@example.com'
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
530     alias ready open_message_stream   # obsolete
532     private
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
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
556     #
557     # auth
558     #
560     private
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
570     def authenticate( user, secret, authtype )
571       __send__("auth_#{authtype || 'cram_md5'}", user, secret)
572     end
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
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
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
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)
606         res = get_response(base64_encode(user + ' ' + tmp))
607       }
608       raise SMTPAuthenticationError, res unless /\A2../ === res
609     end
611     def base64_encode( str )
612       # expects "str" may not become too long
613       [str].pack('m').gsub(/\s+/, '')
614     end
616     #
617     # SMTP command dispatcher
618     #
620     private
622     def helo( domain )
623       getok('HELO %s', domain)
624     end
626     def ehlo( domain )
627       getok('EHLO %s', domain)
628     end
630     def mailfrom( fromaddr )
631       getok('MAIL FROM:<%s>', fromaddr)
632     end
634     def rcptto( to )
635       getok('RCPT TO:<%s>', to)
636     end
638     def quit
639       getok('QUIT')
640     end
642     #
643     # row level library
644     #
646     private
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
656     def get_response( fmt, *args )
657       @socket.writeline sprintf(fmt, *args)
658       recv_response()
659     end
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
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
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
693   end   # class SMTP
695   SMTPSession = SMTP
697 end   # module Net