ruby--ruby/lib/net/smtp.rb

307 lines
6.8 KiB
Ruby
Raw Normal View History

=begin
= net/smtp.rb version 1.2.0
Copyright (C) 1999-2001 Yukihiro Matsumoto
written & maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp>
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