=begin = net/smtp.rb version 1.2.0 Copyright (C) 1999-2001 Yukihiro Matsumoto written & maintained by Minero Aoki This program is free software. You can re-distribute and/or modify this program under the same terms as Ruby itself, GNU General Public License or Ruby License. Japanese version of this document is in "net" full package. You can get it from RAA (Ruby Application Archive). RAA is: http://www.ruby-lang.org/en/raa.html == Net::SMTP === Super Class Net::Protocol === Class Methods : new( address = 'localhost', port = 25 ) creates a new Net::SMTP object. : start( address = 'localhost', port = 25, *protoargs ) : start( address = 'localhost', port = 25, *protoargs ) {|smtp| .... } is equal to Net::SMTP.new( address, port ).start( *protoargs ) === Methods : start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) : start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... } opens TCP connection and starts SMTP session. If protocol had been started, do nothing and return false. When this methods is called with block, give a SMTP object to block and close session after block call finished. If account and password are given, is trying to get authentication by using AUTH command. "authtype" is :plain (symbol) or :cram_md5. : send_mail( mailsrc, from_addr, *to_addrs ) : sendmail( mailsrc, from_addr, *to_addrs ) This method sends 'mailsrc' as mail. SMTP read strings from 'mailsrc' by calling 'each' iterator, and convert them into "\r\n" terminated string when write. from_addr must be String. to_addrs must be a String(s) or an Array of String. Exceptions which SMTP raises are: * Net::ProtoSyntaxError: syntax error (errno.500) * Net::ProtoFatalError: fatal error (errno.550) * Net::ProtoUnknownError: unknown error * Net::ProtoServerBusy: temporary error (errno.420/450) # usage example Net::SMTP.start( 'localhost', 25 ) do |smtp| smtp.send_mail mail_string, 'from-addr@foo.or.jp', 'to-addr@bar.or.jp' end : ready( from_addr, to_addrs ) {|adapter| .... } This method stands by the SMTP object for sending mail. "adapter" object accepts only "write" method. # usage example Net::SMTP.start( 'localhost', 25 ) do |smtp| smtp.ready( from, to ) do |adapter| adapter.write str1 adapter.write str2 adapter.write str3 end end : finish finishes SMTP session. If SMTP session had not started, do nothing and return false. =end require 'net/protocol' require 'md5' module Net class SMTP < Protocol protocol_param :port, '25' protocol_param :command_type, '::Net::NetPrivate::SMTPCommand' def initialize( addr = nil, port = nil ) super @esmtp = true end attr :esmtp def send_mail( mailsrc, from_addr, *to_addrs ) do_ready from_addr, to_addrs.flatten @command.write_mail mailsrc, nil end alias sendmail send_mail def ready( from_addr, *to_addrs, &block ) do_ready from_addr, to_addrs.flatten @command.write_mail nil, block end private def do_ready( from_addr, to_addrs ) if to_addrs.empty? then raise ArgumentError, 'mail destination does not given' end @command.mailfrom from_addr @command.rcpt to_addrs @command.data end def do_start( helodom = nil, user = nil, secret = nil, authtype = nil ) helodom ||= ::Socket.gethostname unless helodom then raise ArgumentError, "cannot get localhost name; try 'smtp.start(local_host_name)'" end begin if @esmtp then @command.ehlo helodom else @command.helo helodom end rescue ProtocolError if @esmtp then @esmtp = false @command.error_ok retry else raise end end if user or secret then (user and secret) or raise ArgumentError, "both of account and password are required" mid = 'auth_' + (authtype || 'cram_md5').to_s @command.respond_to? mid or raise ArgumentError, "wrong auth type #{authtype.to_s}" @command.__send__ mid, user, secret end end end SMTPSession = SMTP module NetPrivate class SMTPCommand < Command def initialize( sock ) super critical { check_reply SuccessCode } end def helo( fromdom ) critical { getok sprintf( 'HELO %s', fromdom ) } end def ehlo( fromdom ) critical { getok sprintf( 'EHLO %s', fromdom ) } end # "PLAIN" authentication [RFC2554] def auth_plain( user, secret ) critical { getok sprintf( 'AUTH PLAIN %s', ["\0#{user}\0#{secret}"].pack('m').chomp ) } end # "CRAM-MD5" authentication [RFC2195] def auth_cram_md5( user, secret ) critical { rep = getok( 'AUTH CRAM-MD5', ContinueCode ) challenge = rep.msg.split(' ')[1].unpack('m')[0] secret = MD5.new( secret ).digest if secret.size > 64 isecret = secret + "\0" * (64 - secret.size) osecret = isecret.dup 0.upto( 63 ) do |i| isecret[i] ^= 0x36 osecret[i] ^= 0x5c end tmp = MD5.new( isecret + challenge ).digest tmp = MD5.new( osecret + tmp ).hexdigest getok [user + ' ' + tmp].pack('m').chomp } end def mailfrom( fromaddr ) critical { getok sprintf( 'MAIL FROM:<%s>', fromaddr ) } end def rcpt( toaddrs ) toaddrs.each do |i| critical { getok sprintf( 'RCPT TO:<%s>', i ) } end end def data return unless begin_critical getok 'DATA', ContinueCode end def write_mail( mailsrc, block ) @socket.write_pendstr mailsrc, block check_reply SuccessCode end_critical end def quit critical { getok 'QUIT' } end private def get_reply arr = read_reply stat = arr[0][0,3] klass = case stat[0] when ?2 then SuccessCode when ?3 then ContinueCode when ?4 then ServerErrorCode when ?5 then case stat[1] when ?0 then SyntaxErrorCode when ?3 then AuthErrorCode when ?5 then FatalErrorCode end end klass ||= UnknownCode Response.new( klass, stat, arr.join('') ) end def read_reply arr = [] while true do str = @socket.readline break unless str[3] == ?- # ex: "210-..." arr.push str end arr.push str arr end end end # module Net::NetPrivate end # module Net