Class: VPOPMail::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/vpopmail/message.rb

Overview


class: Message {{{ ++ The Message class represents an email message stored in a Folder

Constant Summary collapse

@@logger =

class attribute: logger {{{

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(p_folder, p_name) ⇒ Message


method: initialize {{{ ++ Creates a new Message object after stored in the Folder p_folder.

p_name contains the name of the file that contains the email. p_name follows the Maildir filename formatt as described in cr.yp.to/proto/maildir.html

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/vpopmail/message.rb', line 86

def initialize(p_folder, p_name)
	# The name analysis comes from http://cr.yp.to/proto/maildir.html
	raise ArgumentError unless p_folder.kind_of?(Folder)
	@folder   = p_folder
	@filename = p_name
	@id       = p_name
	@info     = nil
	@flags    = nil
	if p_name =~ /.*:(\d),([A-Z]*)$/ then
		@info  = $1
		@flags = $2
		@id    = p_name.gsub(/:\d,[A-Z]*$/, '')
	end
	self.load
end

Instance Attribute Details

#filenameObject (readonly)

Returns the value of attribute filename.



67
68
69
# File 'lib/vpopmail/message.rb', line 67

def filename
  @filename
end

#flagsObject (readonly)

Returns the value of attribute flags.



67
68
69
# File 'lib/vpopmail/message.rb', line 67

def flags
  @flags
end

#idObject (readonly)

Returns the value of attribute id.



67
68
69
# File 'lib/vpopmail/message.rb', line 67

def id
  @id
end

#infoObject (readonly)

Returns the value of attribute info.



67
68
69
# File 'lib/vpopmail/message.rb', line 67

def info
  @info
end

#pathObject (readonly)

Returns the value of attribute path.



67
68
69
# File 'lib/vpopmail/message.rb', line 67

def path
  @path
end

Class Method Details

.loggerObject



72
# File 'lib/vpopmail/message.rb', line 72

def self.logger ;            @@logger ; end

.logger=(p_object) ⇒ Object



73
# File 'lib/vpopmail/message.rb', line 73

def self.logger=(p_object) ; @@logger = p_object ; end

.validAddress?(p_address, p_local, p_blacklist = [], p_whitelist = []) ⇒ Boolean


class method: validAddress? {{{ ++ Tells if the RMail::Address p_address is valid or not. Uses p_local as the origin email address when querying the mail server of p_address

This methods queries the DNS of the domain contains in p_address for its MX Servers. Then it will use the SMTP protocol to query the MX servers for the validity of p_address

p_blackList contains the list of email address or domains that should always be rejected. p_whiteList contains the list of email address or domains that should always be accepted.

Possible Exceptions

  • SMTPServerBusy

Returns:

  • (Boolean)


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/vpopmail/message.rb', line 119

def self.validAddress?(p_address, p_local, p_blacklist = [], p_whitelist = [])
	p_address = RMail::Address.parse(p_address)[0] if !p_address.kind_of? RMail::Address
	p_local   = RMail::Address.parse(p_local)[0]   if !p_local.kind_of? RMail::Address

	logger.info "Checking Domain: #{p_address.domain}" if !@@logger.nil?

	if p_blacklist.include?(p_address.address)
		logger.info "Address #{p_address.address} is in the blacklist"
		return false
	end
	if p_blacklist.include?(p_address.domain)
		logger.info "Domain #{p_address.domain} is in the blacklist"
		return false
	end
	if p_whitelist.include?(p_address.address)
		logger.info "Address #{p_address.address} is in the whitelist"
		return true
	end
	if p_whitelist.include?(p_address.domain)
		logger.info "Domain #{p_address.domain} is in the whitelist"
		return true
	end
	begin
		resolver = Net::DNS::Resolver.new
		answer   = resolver.query(p_address.domain, Net::DNS::MX)
		logger.debug "#{answer.inspect}" if !@@logger.nil?
		if answer.nil? or answer.header.anCount == 0
			logger.warn "No nameserver found for domain: #{resolver.errorstring}" if !@@logger.nil?
			return false
		end
		hosts = answer.answer.sort {|x, y| x.preference <=> y.preference}
	rescue Timeout::Error
		logger.error "Timeout while querying #{p_address.domain}" if !@@logger.nil?
		return false
	rescue NameError => p_e
		logger.error "NameError while querying #{p_address.domain}, #{p_e}" if !@@logger.nil?
		return false
	end

	hosts.each { |host|
		logger.info "Connecting to MX #{host.exchange}" if !@@logger.nil?
		begin
			Net::SMTP.start(host.exchange, 25, p_local.domain) { |smtp|
				logger.debug "Validating #{p_address.address}" if !@@logger.nil?
				valid = smtp.validAddress?(p_address.address, p_local.address)
				logger.info  "#{p_address.address} valid?: #{valid}" if !@@logger.nil?
				return valid
			}
		rescue Net::SMTPServerBusy => p_e
			# That means the MX Host does not honor the answer
			logger.error "#{host.exchange} replied #{p_e}" if !@@logger.nil?
			raise p_e
		rescue Errno::ETIMEDOUT => p_e
			# That means the MX Host does not answer
			logger.warn "timeout in querying #{host.exchange}" if !@@logger.nil?
		rescue Exception => p_e
			logger.error "Unknown error: #{p_e}" if !@@logger.nil?
		end
	}
	return false
end

Instance Method Details

#bodyObject


method: body {{{ ++ Gives the body of the email

Returns a String or an array of RMail::Message if the Message is a MIME multipart



204
205
206
# File 'lib/vpopmail/message.rb', line 204

def body
	return @rmail.header
end

#copyTo(p_folder) ⇒ Object


method: copyTo {{{ ++ Copies the Message to the Folder p_folder



326
327
328
329
330
331
332
333
# File 'lib/vpopmail/message.rb', line 326

def copyTo(p_folder)
	logger.info "Copying to folder #{p_folder.name}" if !@@logger.nil?
	p_folder.imapdb.add(self)
	path = p_folder.newpath + File::SEPARATOR + @id
	File.open(path, "w") { |file| RMail::Serialize.write(file, @rmail) }
	File.chmod(0644, path)
	File.chown(@uid, @gid, path)
end

#deleteFrom(p_folder) ⇒ Object


method: deleteFrom {{{ ++ Deletes the Message from the Folder p_folder



339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/vpopmail/message.rb', line 339

def deleteFrom(p_folder)
	logger.info "Deleting from folder #{p_folder.name}" if !@@logger.nil?
	p_folder.imapdb.delete(self)
	begin
		File.delete(p_folder.curpath + File::SEPARATOR + @filename)
	rescue => p_e
		begin
			File.delete(p_folder.newpath + File::SEPARATOR + @filename)
		rescue => p_e
			logger.error "Problem while deleting: #{p_e}" if !@@logger.nil?
		end
	end
end

#learnAs(p_kind) ⇒ Object


method: learnAs {{{ ++ Learns the Message as ham or spam using spamassassin

Raises:

  • (ArgumentError)


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/vpopmail/message.rb', line 256

def learnAs(p_kind)
	raise ArgumentError unless p_kind == "spam" or p_kind == "ham"
	logger.info "Learning message as #{p_kind}" if !@@logger.nil?
	debug     = "-D bayes,learn,dns"
	debug     = ""
	cmdString = "/usr/bin/sa-learn --single #{debug} --username=vpopmail --#{p_kind}"
	cmd       = IO.popen(cmdString, "w+")
	RMail::Serialize.write(cmd, @rmail)
	cmd.close_write

	while line = cmd.gets do
		line = line.gsub(/[\r\n]*$/, "")
		next if line.empty?
		logger.debug "SA-Learn: #{line}" if !@@logger.nil?
	end
end

#loadObject


method: load {{{ ++ Loads the Message from its file in its @rmail attribute (RMail::Message)

Possible Exceptions

  • Errno::ENOENT



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/vpopmail/message.rb', line 280

def load
	if @filename == @id then
		path = @folder.newpath + File::SEPARATOR + @filename
	else
		path   = @folder.curpath + File::SEPARATOR + @filename
	end
	logger.info "Loading #{path}" if !@@logger.nil?
	begin
		@rmail = File.open(path) { |file| RMail::Parser.read(file) }
		stat   = File.stat(path)
		@uid   = stat.uid
		@gid   = stat.gid
		if !@@logger.nil? then
			logger.debug "Loaded. uid=#{@uid}, gid=#{@gid}"
			logger.info  "Message:         #{@id}"
			logger.info  "Message From:    #{@rmail.header.from.inspect}"
			logger.info  "Message Subject: #{@rmail.header.subject}"
		end
	rescue Errno::ENOENT => p_e
		logger.error "Cannot load #{path}" if !@@logger.nil?
		raise p_e
	end
end

#loggerObject



74
# File 'lib/vpopmail/message.rb', line 74

def logger ;                 @@logger ; end

#logger=(p_object) ⇒ Object



75
# File 'lib/vpopmail/message.rb', line 75

def logger=(p_object) ;      @@logger = p_object ; end

#markAs(p_kind) ⇒ Object


method: markAs {{{ ++ Marks the Message as ham or spam

Raises an ArgumentError if p_kind contains another value.



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/vpopmail/message.rb', line 224

def markAs(p_kind)
	logger.info "Marking message as #{p_kind}" if !@@logger.nil?
	field  = VPOPMail::CFG["SPAM Subject Field"]
	header = @rmail.header
	case p_kind
		when /spam/i
			if !header.field?(field) then
				header[field] = header.subject || ''
				header.subject = VPOPMail::CFG["SPAM Mark"] + (header.subject || '')
				self.save
			else
				logger.info "Message #{@id} is already marked as SPAM" if !@@logger.nil?
			end
		when /ham/i
			if header.field?(field) then
				logger.debug "There is a #{field} field,\nSubject: #{header.subject}\nSPAM:    #{header[field]}" if !@@logger.nil?
				header.subject = header[field]
				header.delete(field)
				self.save
			else
				logger.info "Message #{@id} is already marked as HAM" if !@@logger.nil?
			end
		else
			raise ArgumentError, "Bad kind: #{p_kind}", caller
	end
end

#moveTo(p_folder) ⇒ Object


method: moveTo {{{ ++ Moves the Message to the Folder p_folder



357
358
359
360
361
362
363
364
# File 'lib/vpopmail/message.rb', line 357

def moveTo(p_folder)
	logger.info "Moving to folder #{p_folder.name}" if !@@logger.nil?
	self.copyTo(p_folder)
	self.deleteFrom(@folder)
	logger.info "Reloading from #{p_folder.name}" if !@@logger.nil?
	@folder = p_folder
	self.load
end

#saveObject


method: save {{{ ++ Saves the Message to its file, by serializing its @rmail attribute.



308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/vpopmail/message.rb', line 308

def save
	@folder.imapdb.update(self)
	path = @folder.newpath + File::SEPARATOR + @id
	logger.info "Saving #{path}" if !@@logger.nil?
	File.open(path, "w") { |file| RMail::Serialize.write(file, @rmail) }
	File.chmod(0644, path)
	File.chown(@uid, @gid, path)
	File.delete(@folder.curpath + File::SEPARATOR + @filename)
	@filename = @id
	@info     = nil
	@flags    = nil
	logger.debug "Saved. new filename: #{@filename}" if !@@logger.nil?
end

#to_sObject


method: to_s {{{ ++ Returns the String representation of the Message object



382
383
384
# File 'lib/vpopmail/message.rb', line 382

def to_s
	return "Message #{@id}, info=#{@info}, flags=#{@flags}, path=\"#{@path}\""
end

#to_xmlObject


method: to_xml {{{ ++ Returns the REXML::Document that represents the Message object



370
371
372
373
374
375
376
# File 'lib/vpopmail/message.rb', line 370

def to_xml
	xml  = "<Message id=\"#{@id}\""
	xml += " info=\"#{@info}\""   if !@info.nil?
	xml += " flags=\"#{@flags}\"" if !@flags.nil?
	xml += " path=\"#{@path}\" />"
	return REXML::Document.new(xml)
end

#validAddress?(p_address, p_blacklist = [], p_whitelist = []) ⇒ Boolean


method: validAddress? {{{ ++ Tells if the RMail::Address p_address is valid or not. Extracts the origin email address when querying the mail server of p_address from the current Message

p_blackList contains the list of email address or domains that should always be rejected. p_whiteList contains the list of email address or domains that should always be accepted.

See Message.validAddress?

Returns:

  • (Boolean)


194
195
196
# File 'lib/vpopmail/message.rb', line 194

def validAddress?(p_address, p_blacklist = [], p_whitelist = [])
	return Message.validAddress?(p_address, @folder.domain.postmaster, p_blacklist, p_whitelist)
end