vpopmail 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +59 -0
- data/Rakefile +102 -0
- data/examples/emailcheck.rb +11 -0
- data/examples/vlearn.rb +113 -0
- data/install.rb +30 -0
- data/lib/vpopmail.rb +47 -0
- data/lib/vpopmail/config.rb +22 -0
- data/lib/vpopmail/domain.rb +105 -0
- data/lib/vpopmail/folder.rb +131 -0
- data/lib/vpopmail/imapdb.rb +154 -0
- data/lib/vpopmail/message.rb +311 -0
- data/lib/vpopmail/user.rb +89 -0
- metadata +63 -0
data/README
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= VPOPMail
|
2
|
+
|
3
|
+
VPOPMail is a library that helps to manipulate vpopmail (http://www.inter7.com/vpopmail), a software package to provide an easy way to manage virtual email domains.
|
4
|
+
|
5
|
+
This library can be used to write email filters, spam learners, etc.
|
6
|
+
|
7
|
+
= Requirements
|
8
|
+
|
9
|
+
* Ruby 1.8.* (or newer). Only tested under Linux, should be fine under any Unix.
|
10
|
+
|
11
|
+
* Installed {vpopmail}[http://www.inter7.com/vpopmail] software.
|
12
|
+
* Installed {spamassassin}[http://spamassassin.apache.org] for spam learning.
|
13
|
+
|
14
|
+
= Gem requirements
|
15
|
+
|
16
|
+
{RubyMail}[http://rubyforge.org/projects/rubymail] 0.17 or newer
|
17
|
+
|
18
|
+
= Documentation
|
19
|
+
|
20
|
+
The documentation is in RDoc format in the doc subdirectory.
|
21
|
+
|
22
|
+
= Install
|
23
|
+
|
24
|
+
Type the following while in the package directory:
|
25
|
+
|
26
|
+
ruby install.rb config
|
27
|
+
ruby install.rb setup
|
28
|
+
ruby install.rb install
|
29
|
+
|
30
|
+
You may need special permissions to execute the last line. If you
|
31
|
+
want to just install RubyMail to a custom location, just copy the
|
32
|
+
rmail subdirectory manually.
|
33
|
+
|
34
|
+
= Support
|
35
|
+
|
36
|
+
To reach the author of ruby-vpopmail, send mail to gildas@breizh.org.
|
37
|
+
|
38
|
+
= License
|
39
|
+
|
40
|
+
Copyright (c) 2006 Gildas Cherruel
|
41
|
+
|
42
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
43
|
+
a copy of this software and associated documentation files (the
|
44
|
+
"Software"), to deal in the Software without restriction, including
|
45
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
46
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
47
|
+
permit persons to whom the Software is furnished to do so, subject to
|
48
|
+
the following conditions:
|
49
|
+
|
50
|
+
The above copyright notice and this permission notice shall be
|
51
|
+
included in all copies or substantial portions of the Software.
|
52
|
+
|
53
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
54
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
55
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
56
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
57
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
58
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
59
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'rake/packagetask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require 'rake/contrib/rubyforgepublisher'
|
7
|
+
require File.join(File.dirname(__FILE__), 'lib', 'vpopmail')
|
8
|
+
|
9
|
+
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
10
|
+
PKG_NAME = 'vpopmail'
|
11
|
+
PKG_VERSION = VPOPMail::VERSION::STRING + PKG_BUILD
|
12
|
+
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
13
|
+
|
14
|
+
RELEASE_NAME = "REL #{PKG_VERSION}"
|
15
|
+
|
16
|
+
RUBY_FORGE_PROJECT = "ruby-vpopmail"
|
17
|
+
RUBY_FORGE_USER = "gildasc"
|
18
|
+
|
19
|
+
PKG_FILES = FileList[
|
20
|
+
"lib/**/*", "test/**/*", "examples/**/*", "doc/**/*", "[A-Z]*", "install.rb", "Rakefile"
|
21
|
+
].exclude(/\bCVS\b|~$/)
|
22
|
+
|
23
|
+
|
24
|
+
# Generate the RDoc documentation
|
25
|
+
|
26
|
+
Rake::RDocTask.new { |rdoc|
|
27
|
+
rdoc.rdoc_dir = 'doc'
|
28
|
+
rdoc.title = "Ruby VPOPMail -- VPOPMail interface for Ruby"
|
29
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
30
|
+
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
31
|
+
rdoc.rdoc_files.include('README')
|
32
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
33
|
+
}
|
34
|
+
|
35
|
+
# Create compressed packages
|
36
|
+
|
37
|
+
spec = Gem::Specification.new do |s|
|
38
|
+
s.name = PKG_NAME
|
39
|
+
s.version = PKG_VERSION
|
40
|
+
s.summary = "Ruby VPOPMail interface"
|
41
|
+
s.description = %q{VPOPMail interface for Ruby. Allows to write filters, spam learners, etc. using the vpopmail software package.}
|
42
|
+
|
43
|
+
s.files = FileList["{lib,examples}/**/*"].exclude("rdoc").to_a
|
44
|
+
s.files += [ "Rakefile", "install.rb", "README" ]
|
45
|
+
|
46
|
+
s.add_dependency('rmail', '>= 0.17' + PKG_BUILD)
|
47
|
+
|
48
|
+
s.require_path = 'lib'
|
49
|
+
s.autorequire = 'vpopmail'
|
50
|
+
|
51
|
+
s.has_rdoc = true
|
52
|
+
s.extra_rdoc_files = %w( README )
|
53
|
+
s.rdoc_options.concat ['--main', 'README']
|
54
|
+
|
55
|
+
s.author = "Gildas Cherruel"
|
56
|
+
s.email = "gildas@breizh.org"
|
57
|
+
s.homepage = "http://ruby-vpopmail.rubyforge.org"
|
58
|
+
s.rubyforge_project = "ruby-vpopmail"
|
59
|
+
end
|
60
|
+
|
61
|
+
Rake::GemPackageTask.new(spec) do |p|
|
62
|
+
p.gem_spec = spec
|
63
|
+
p.need_tar = true
|
64
|
+
p.need_zip = true
|
65
|
+
end
|
66
|
+
|
67
|
+
task :lines do
|
68
|
+
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
69
|
+
|
70
|
+
for file_name in FileList["lib/vpopmail/**/*.rb"]
|
71
|
+
f = File.open(file_name)
|
72
|
+
|
73
|
+
while line = f.gets
|
74
|
+
lines += 1
|
75
|
+
next if line =~ /^\s*$/
|
76
|
+
next if line =~ /^\s*#/
|
77
|
+
codelines += 1
|
78
|
+
end
|
79
|
+
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
80
|
+
|
81
|
+
total_lines += lines
|
82
|
+
total_codelines += codelines
|
83
|
+
|
84
|
+
lines, codelines = 0, 0
|
85
|
+
end
|
86
|
+
|
87
|
+
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Publishing ------------------------------------------------------
|
92
|
+
|
93
|
+
desc "Publish the release files to RubyForge."
|
94
|
+
task :release => [ :package ] do
|
95
|
+
`rubyforge login`
|
96
|
+
|
97
|
+
for ext in %w( gem tgz zip )
|
98
|
+
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
99
|
+
puts release_command
|
100
|
+
system(release_command)
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'vpopmail'
|
3
|
+
|
4
|
+
logname = File.basename(__FILE__, '.rb')
|
5
|
+
log = Log4r::Logger.new(logname)
|
6
|
+
log.add(Log4r::FileOutputter.new(logname, :filename => logname + '.log', :level => Log4r::DEBUG))
|
7
|
+
VPOPMail::logger = log
|
8
|
+
|
9
|
+
log.info "Starting emailcheck"
|
10
|
+
valid = VPOPMail::Message.validAddress?(ARGV[0], 'postmaster@breizh.org')
|
11
|
+
puts "Address #{ARGV[0]} is " + (valid ? "valid" : "invalid")
|
data/examples/vlearn.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'getoptlong'
|
3
|
+
require 'vpopmail'
|
4
|
+
|
5
|
+
$CFG = { "Junk Folder" => "Junk E-mails",
|
6
|
+
"Learn SPAM Folder" => "Learn Junk",
|
7
|
+
"Learn HAM Folder" => "Learn Good",
|
8
|
+
"archive user" => "postmaster",
|
9
|
+
"archive SPAM Folder" => "SPAM",
|
10
|
+
"archive HAM Folder" => "HAM",
|
11
|
+
}
|
12
|
+
|
13
|
+
$VERBOSE = true
|
14
|
+
$OPTS = GetoptLong.new(
|
15
|
+
["--domain", "-d", GetoptLong::REQUIRED_ARGUMENT],
|
16
|
+
["--folder", "-f", GetoptLong::REQUIRED_ARGUMENT],
|
17
|
+
["--ham", GetoptLong::NO_ARGUMENT],
|
18
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT],
|
19
|
+
["--silent", "-s", GetoptLong::NO_ARGUMENT],
|
20
|
+
["--spam", GetoptLong::NO_ARGUMENT],
|
21
|
+
["--user", "-u", GetoptLong::REQUIRED_ARGUMENT],
|
22
|
+
["--verbose", "-v", GetoptLong::NO_ARGUMENT])
|
23
|
+
|
24
|
+
#------------------------------------------------------------------------------------------
|
25
|
+
# function: Verbose {{{
|
26
|
+
def Verbose(p_string)
|
27
|
+
puts p_string if $VERBOSE
|
28
|
+
end # }}}
|
29
|
+
|
30
|
+
# function: usage {{{
|
31
|
+
def usage(p_error = nil, p_errno = 0, p_wantQuit = true)
|
32
|
+
puts "Usage: vlearn.rb [options] [filename]"
|
33
|
+
puts "Error: #{p_error}" if ! p_error.nil?
|
34
|
+
puts "Options:"
|
35
|
+
puts " -d domain, --domain=domain Will work only on this vpopmail domain. (Default: all)"
|
36
|
+
puts " -u user, --user=user Will work only on this vpopmail user. (Default: all)"
|
37
|
+
puts " --ham Will consider messages as good messages"
|
38
|
+
puts " --spam Will consider messages as spam/junk messages"
|
39
|
+
puts " -h, --help Prints this help."
|
40
|
+
puts " -s, --silent Proceed silently."
|
41
|
+
puts " -v, --verbose Proceed verbosely."
|
42
|
+
exit p_errno if p_wantQuit
|
43
|
+
end # }}}
|
44
|
+
|
45
|
+
# function: loadFolder {{{
|
46
|
+
def loadFolder(p_user, p_name = nil)
|
47
|
+
begin
|
48
|
+
return VPOPMail::Folder.new(p_user, p_name)
|
49
|
+
rescue Errno::ENOENT => p_e
|
50
|
+
Verbose "User postmaster: Folder #{p_name} does not exist"
|
51
|
+
return nil
|
52
|
+
end
|
53
|
+
end # }}}
|
54
|
+
|
55
|
+
# main {{{
|
56
|
+
domainname = nil
|
57
|
+
foldername = nil
|
58
|
+
username = nil
|
59
|
+
kind = nil
|
60
|
+
|
61
|
+
$OPTS.each {|opt, arg|
|
62
|
+
case opt
|
63
|
+
when "--domain" then domainname = arg
|
64
|
+
when "--folder" then foldername = arg
|
65
|
+
when "--user" then username = arg
|
66
|
+
when "--ham" then kind = "ham"
|
67
|
+
when "--spam" then kind = "spam"
|
68
|
+
when "--help" then usage()
|
69
|
+
when "--silent" then $VERBOSE = false
|
70
|
+
when "--verbose" then $VERBOSE = true
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
if !domainname.nil? and !username.nil? and !foldername.nil? then
|
75
|
+
domain = VPOPMail::Domain.FindDomains(domainname)[0]
|
76
|
+
Verbose "Analyzing domain #{domain.name}"
|
77
|
+
user = VPOPMail::User.FindUsers(domain, username)[0]
|
78
|
+
Verbose "Analyzing user #{user.name}"
|
79
|
+
folder = VPOPMail::Folder.new(user, foldername)
|
80
|
+
Verbose "Analyzing folder #{folder}, size = #{folder.size}"
|
81
|
+
messageid = ARGV[0]
|
82
|
+
|
83
|
+
if !messageid.nil? and !messageid.empty? then
|
84
|
+
message = VPOPMail::Message.new(folder, messageid)
|
85
|
+
Verbose "Processing: #{message.id}"
|
86
|
+
message.markAs(kind) if !kind.nil?
|
87
|
+
else
|
88
|
+
folder.each {|message|
|
89
|
+
Verbose "Processing: #{message.id}"
|
90
|
+
message.markAs(kind) if !kind.nil?
|
91
|
+
}
|
92
|
+
end
|
93
|
+
else
|
94
|
+
VPOPMail::Domain.FindDomains.each { |domain|
|
95
|
+
Verbose "Analyzing domain #{domain.name}"
|
96
|
+
archiveUser = VPOPMail::User.FindUsers(domain, $CFG["archive user"])[0]
|
97
|
+
archiveSPAM = loadFolder(archiveUser, $CFG["archive SPAM Folder"])
|
98
|
+
archiveHAM = loadFolder(archiveUser, $CFG["archive HAM Folder"])
|
99
|
+
|
100
|
+
VPOPMail::User.FindUsers(domain).each { |user|
|
101
|
+
Verbose "Analyzing user #{user.name}"
|
102
|
+
junkFolder = loadFolder(user, $CFG["Junk Folder"])
|
103
|
+
learnFolder = loadFolder(user, $CFG["Learn SPAM Folder"])
|
104
|
+
learnFolder.learnAs("spam", junkFolder, archiveSPAM) if !learnFolder.nil?
|
105
|
+
|
106
|
+
inboxFolder = loadFolder(user, nil)
|
107
|
+
learnFolder = loadFolder(user, $CFG["Learn HAM Folder"])
|
108
|
+
learnFolder.learnAs("ham", inboxFolder, archiveHAM) if !learnFolder.nil?
|
109
|
+
}
|
110
|
+
}
|
111
|
+
end
|
112
|
+
exit 0
|
113
|
+
# }}}
|
data/install.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'find'
|
3
|
+
require 'ftools'
|
4
|
+
|
5
|
+
include Config
|
6
|
+
|
7
|
+
# this was adapted from rdoc's install.rb by ways of Log4r
|
8
|
+
|
9
|
+
$sitedir = CONFIG["sitelibdir"]
|
10
|
+
unless $sitedir
|
11
|
+
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
|
12
|
+
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
13
|
+
$sitedir = $:.find {|x| x =~ /site_ruby/ }
|
14
|
+
if !$sitedir
|
15
|
+
$sitedir = File.join($libdir, "site_ruby")
|
16
|
+
elsif $sitedir !~ Regexp.quote(version)
|
17
|
+
$sitedir = File.join($sitedir, version)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# the acual gruntwork
|
22
|
+
Dir.chdir("lib")
|
23
|
+
|
24
|
+
Find.find("vpopmail", "vpopmail.rb") { |f|
|
25
|
+
if f[-3..-1] == ".rb"
|
26
|
+
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
27
|
+
else
|
28
|
+
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
29
|
+
end
|
30
|
+
}
|
data/lib/vpopmail.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: vpopmail.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
10
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
11
|
+
|
12
|
+
require 'vpopmail/domain'
|
13
|
+
require 'vpopmail/user'
|
14
|
+
require 'vpopmail/imapdb'
|
15
|
+
require 'vpopmail/folder'
|
16
|
+
require 'vpopmail/message'
|
17
|
+
|
18
|
+
#------------------------------------------------------------------------------------------
|
19
|
+
#------------------------------------------------------------------------------------------
|
20
|
+
# module: VPOPMail {{{
|
21
|
+
#++
|
22
|
+
# The VPOPMail module contains all classes used to manipulate {vpopmail}[http://www.inter7.com/vpopmail] objects
|
23
|
+
module VPOPMail
|
24
|
+
|
25
|
+
#--------------------------------------------------------------------------------------
|
26
|
+
# Versionning # {{{
|
27
|
+
module VERSION #:nodoc:
|
28
|
+
MAJOR = 1
|
29
|
+
MINOR = 0
|
30
|
+
TINY = 0
|
31
|
+
BUILD = %q$Id: vpopmail.rb 4 2006-10-07 23:18:13Z gildasc $
|
32
|
+
STRING = [MAJOR, MINOR, TINY].join('.')
|
33
|
+
end # }}}
|
34
|
+
|
35
|
+
#--------------------------------------------------------------------------------------
|
36
|
+
# module function: logger= {{{
|
37
|
+
#++
|
38
|
+
# Assigns the Logger object to all classes of the VPOPMail module.
|
39
|
+
# <tt>p_logger</tt> must comply to the Logger class.
|
40
|
+
def VPOPMail.logger=(p_logger)
|
41
|
+
Domain.logger = p_logger
|
42
|
+
User.logger = p_logger
|
43
|
+
Folder.logger = p_logger
|
44
|
+
Message.logger = p_logger
|
45
|
+
IMAPDB.logger = p_logger
|
46
|
+
end # }}}
|
47
|
+
end # }}}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: config.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
|
10
|
+
#------------------------------------------------------------------------------------------
|
11
|
+
# module: VPOPMail {{{
|
12
|
+
#++
|
13
|
+
module VPOPMail
|
14
|
+
|
15
|
+
# CFG contains default values for manipulating the vpopmail software package
|
16
|
+
CFG = { "VPOP Bin" => "/home/vpopmail/bin",
|
17
|
+
"SPAM Mark" => "***SPAM*** ",
|
18
|
+
"SPAM Subject Field" => "X-Spam-Prev-Subject",
|
19
|
+
"IMAPDB" => "courierimapuiddb",
|
20
|
+
}
|
21
|
+
|
22
|
+
end # }}}
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: domain.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'rmail'
|
10
|
+
require 'rexml/document'
|
11
|
+
require 'vpopmail/config'
|
12
|
+
|
13
|
+
#------------------------------------------------------------------------------------------
|
14
|
+
#------------------------------------------------------------------------------------------
|
15
|
+
# module: VPOPMail {{{
|
16
|
+
#++
|
17
|
+
module VPOPMail
|
18
|
+
|
19
|
+
#------------------------------------------------------------------------------------------
|
20
|
+
# class: Domain {{{
|
21
|
+
#++
|
22
|
+
# The Domain class represents a virtual domain in {vpopmail}[http://www.inter7.com/vpopmail]
|
23
|
+
class Domain
|
24
|
+
attr_reader :name, :path
|
25
|
+
attr_writer :name, :path
|
26
|
+
|
27
|
+
#--------------------------------------------------------------------------------------
|
28
|
+
# class attribute: logger {{{
|
29
|
+
#++
|
30
|
+
@@logger = nil
|
31
|
+
def self.logger ; @@logger ; end
|
32
|
+
def self.logger=(p_object) ; @@logger = p_object ; end
|
33
|
+
def logger(p_object) ; @@logger ; end
|
34
|
+
def logger=(p_object) ; @@logger = p_object ; end
|
35
|
+
# }}}
|
36
|
+
|
37
|
+
#----------------------------------------------------------------------------------
|
38
|
+
# method: initialize {{{
|
39
|
+
#++
|
40
|
+
# Creates a new Domain object
|
41
|
+
def initialize
|
42
|
+
@name = @path = ""
|
43
|
+
end # }}}
|
44
|
+
|
45
|
+
#----------------------------------------------------------------------------------
|
46
|
+
# class method: FindDomains {{{
|
47
|
+
#++
|
48
|
+
# Finds all virtual domains that match the pattern <tt>p_name</tt>. If the pattern
|
49
|
+
# is not present, finds all virtual domains.
|
50
|
+
# Returns an array of Domain objects.
|
51
|
+
def Domain.FindDomains(p_name = nil)
|
52
|
+
cmdString = "#{VPOPMail::CFG['VPOP Bin']}/vdominfo -n -d"
|
53
|
+
if !p_name.nil? and !p_name.empty? then
|
54
|
+
raise ArgumentError unless p_name.kind_of?(String)
|
55
|
+
cmdString = cmdString + " #{p_name}"
|
56
|
+
end
|
57
|
+
cmd = IO.popen(cmdString, "w+")
|
58
|
+
cmd.close_write
|
59
|
+
|
60
|
+
domains = Array.new
|
61
|
+
domain = Domain.new
|
62
|
+
getName = true
|
63
|
+
while line = cmd.gets do
|
64
|
+
line = line.gsub(/[\r\n]*$/, "")
|
65
|
+
next if line.empty?
|
66
|
+
if getName then
|
67
|
+
domain.name = line
|
68
|
+
else
|
69
|
+
domain.path = line
|
70
|
+
domains << domain
|
71
|
+
domain = Domain.new
|
72
|
+
end
|
73
|
+
getName = !getName
|
74
|
+
end
|
75
|
+
return domains
|
76
|
+
end # }}}
|
77
|
+
|
78
|
+
#----------------------------------------------------------------------------------
|
79
|
+
# method: postmaster {{{
|
80
|
+
#++
|
81
|
+
# Calculates the default postmaster email address if the Domain.
|
82
|
+
# If <tt>p_name</tt> is present, it will be used instead of 'postmaster'
|
83
|
+
# Returns a RMail::Address object
|
84
|
+
def postmaster(p_name = 'postmaster')
|
85
|
+
return RMail::Address.parse("#{name}@#{@name}")[0]
|
86
|
+
end # }}}
|
87
|
+
|
88
|
+
#----------------------------------------------------------------------------------
|
89
|
+
# method: to_xml {{{
|
90
|
+
#++
|
91
|
+
# Returns the REXML::Document that represents the Domain object
|
92
|
+
def to_xml
|
93
|
+
return REXML::Document.new("<Domain name=\"#{@name}\" path=\"#{@path}\" />")
|
94
|
+
end # }}}
|
95
|
+
|
96
|
+
#----------------------------------------------------------------------------------
|
97
|
+
# method: to_s {{{
|
98
|
+
#++
|
99
|
+
# Returns the String representation of the Domain object
|
100
|
+
def to_s
|
101
|
+
return "Domain #{@name} stored in #{@path}"
|
102
|
+
end # }}}
|
103
|
+
end # }}}
|
104
|
+
|
105
|
+
end # }}}
|
@@ -0,0 +1,131 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: folder.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'vpopmail/config'
|
11
|
+
require 'vpopmail/domain'
|
12
|
+
require 'vpopmail/user'
|
13
|
+
require 'vpopmail/imapdb'
|
14
|
+
require 'vpopmail/message'
|
15
|
+
|
16
|
+
#------------------------------------------------------------------------------------------
|
17
|
+
#------------------------------------------------------------------------------------------
|
18
|
+
# module: VPOPMail {{{
|
19
|
+
#++
|
20
|
+
module VPOPMail
|
21
|
+
|
22
|
+
#------------------------------------------------------------------------------------------
|
23
|
+
# class: Folder {{{
|
24
|
+
#++
|
25
|
+
# The Folder class represents a Maildir folder
|
26
|
+
class Folder
|
27
|
+
attr_reader :name, :path, :curpath, :newpath, :tmppath, :imapdb, :user
|
28
|
+
|
29
|
+
#--------------------------------------------------------------------------------------
|
30
|
+
# class attribute: logger {{{
|
31
|
+
@@logger = nil
|
32
|
+
def self.logger ; @@logger ; end
|
33
|
+
def self.logger=(p_object) ; @@logger = p_object ; end
|
34
|
+
def logger(p_object) ; @@logger ; end
|
35
|
+
def logger=(p_object) ; @@logger = p_object ; end
|
36
|
+
# }}}
|
37
|
+
|
38
|
+
#----------------------------------------------------------------------------------
|
39
|
+
# method: initialize {{{
|
40
|
+
#++
|
41
|
+
# Creates a new Folder object after <tt>p_name</tt> and owned by the User <tt>p_user</tt>
|
42
|
+
# If <tt>p_name</tt> is not given opens the Inbox.
|
43
|
+
#
|
44
|
+
# Initializes the IMAPDB Database for the given Folder and set the working directory
|
45
|
+
# to the <i>cur</i> Maildir folder.
|
46
|
+
#
|
47
|
+
# Raises a SystemCallError if the Maildir folder does not exist
|
48
|
+
def initialize(p_user, p_name = nil)
|
49
|
+
@user = p_user
|
50
|
+
@name = p_name
|
51
|
+
@name = "INBOX" if @name.nil? or @name.empty?
|
52
|
+
@path = p_user.path + File::SEPARATOR + "Maildir"
|
53
|
+
@path = @path + File::SEPARATOR + '.' + @name if @name !~ /inbox/i
|
54
|
+
@path = @path
|
55
|
+
@curpath = @path + File::SEPARATOR + "cur"
|
56
|
+
@newpath = @path + File::SEPARATOR + "new"
|
57
|
+
@tmppath = @path + File::SEPARATOR + "tmp"
|
58
|
+
@dir = Dir.new(@curpath) # This will raise a if the path does not exist
|
59
|
+
@imapdb = IMAPDB.new(self)
|
60
|
+
end # }}}
|
61
|
+
|
62
|
+
#----------------------------------------------------------------------------------
|
63
|
+
# method: domain {{{
|
64
|
+
#++
|
65
|
+
# Gives the Domain of the User that owns the Folder
|
66
|
+
def domain
|
67
|
+
return @user.domain
|
68
|
+
end # }}}
|
69
|
+
|
70
|
+
#----------------------------------------------------------------------------------
|
71
|
+
# method: size {{{
|
72
|
+
#++
|
73
|
+
# Gives the number of Message objects stored in the Folder
|
74
|
+
def size
|
75
|
+
return @dir.entries.size - 2 # . and .. should not be counted
|
76
|
+
end # }}}
|
77
|
+
|
78
|
+
#----------------------------------------------------------------------------------
|
79
|
+
# method: each {{{
|
80
|
+
#++
|
81
|
+
# Apply the <i>block</i> to all Message objects stored in the Folder
|
82
|
+
# Returns <b>self</b>
|
83
|
+
def each #p_block
|
84
|
+
@dir.each { |entry|
|
85
|
+
next if entry == "."
|
86
|
+
next if entry == ".."
|
87
|
+
message = Message.new(self, entry)
|
88
|
+
yield(message)
|
89
|
+
}
|
90
|
+
self
|
91
|
+
end # }}}
|
92
|
+
|
93
|
+
#----------------------------------------------------------------------------------
|
94
|
+
# method: learnAs {{{
|
95
|
+
#++
|
96
|
+
# Learns all Message objects as <tt>p_kind</tt>.
|
97
|
+
# Moves them to the <tt>p_properFolder</tt> Folder if present.
|
98
|
+
# And makes a copy of each Message to the <tt>p_archiveFolder</tt> if present.
|
99
|
+
# This allows to re-train the spam analyzer with a bunch of ham and spam messages later on.
|
100
|
+
#
|
101
|
+
# Returns <b>self</b>
|
102
|
+
def learnAs(p_kind = "spam", p_properFolder = nil, p_archiveFolder = nil)
|
103
|
+
raise ArgumentError unless p_kind == "spam" or p_kind == "ham"
|
104
|
+
self.each {|message|
|
105
|
+
logger.info "Processing message #{message.id}" if !@@logger.nil?
|
106
|
+
message.learnAs(p_kind)
|
107
|
+
message.copyTo(p_archiveFolder) if !p_archiveFolder.nil?
|
108
|
+
message.markAs(p_kind)
|
109
|
+
message.moveTo(p_properFolder) if !p_properFolder.nil?
|
110
|
+
}
|
111
|
+
self
|
112
|
+
end # }}}
|
113
|
+
|
114
|
+
#----------------------------------------------------------------------------------
|
115
|
+
# method: to_xml {{{
|
116
|
+
#++
|
117
|
+
# Returns the REXML::Document that represents the Folder object
|
118
|
+
def to_xml
|
119
|
+
return REXML::Document.new("<Folder name=\"#{@name}\" path=\"#{@path}\" />")
|
120
|
+
end # }}}
|
121
|
+
|
122
|
+
#----------------------------------------------------------------------------------
|
123
|
+
# method: to_s {{{
|
124
|
+
#++
|
125
|
+
# Returns the String representation of the Domain object
|
126
|
+
def to_s
|
127
|
+
return "Folder #{@name}, path=\"#{@path}\""
|
128
|
+
end # }}}
|
129
|
+
end # }}}
|
130
|
+
|
131
|
+
end # }}}
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: imapdb.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'vpopmail/config'
|
11
|
+
require 'vpopmail/domain'
|
12
|
+
require 'vpopmail/user'
|
13
|
+
require 'vpopmail/message'
|
14
|
+
|
15
|
+
#------------------------------------------------------------------------------------------
|
16
|
+
#------------------------------------------------------------------------------------------
|
17
|
+
# module: VPOPMail {{{
|
18
|
+
#++
|
19
|
+
module VPOPMail
|
20
|
+
|
21
|
+
#------------------------------------------------------------------------------------------
|
22
|
+
# class: IMAPDB {{{
|
23
|
+
#++
|
24
|
+
# The IMAPDB class is used to manipulate the status of Message object in a Folder
|
25
|
+
class IMAPDB
|
26
|
+
attr_reader :name, :path
|
27
|
+
|
28
|
+
#--------------------------------------------------------------------------------------
|
29
|
+
# class attribute: logger {{{
|
30
|
+
@@logger = nil
|
31
|
+
def self.logger ; @@logger ; end
|
32
|
+
def self.logger=(p_object) ; @@logger = p_object ; end
|
33
|
+
def logger(p_object) ; @@logger ; end
|
34
|
+
def logger=(p_object) ; @@logger = p_object ; end
|
35
|
+
# }}}
|
36
|
+
|
37
|
+
#----------------------------------------------------------------------------------
|
38
|
+
# method: initialize {{{
|
39
|
+
#++
|
40
|
+
# Creates a new IMAPDB object attached to the Folder <tt>p_folder</tt>
|
41
|
+
#
|
42
|
+
# Uses <b>VPOPMail::CFG["IMAPDB"]</b> as the name of the file that contains the IMAP database.
|
43
|
+
def initialize(p_folder)
|
44
|
+
raise ArgumentError unless p_folder.kind_of?(Folder)
|
45
|
+
@name = VPOPMail::CFG["IMAPDB"]
|
46
|
+
@folder = p_folder
|
47
|
+
@path = @folder.path
|
48
|
+
@filename = @folder.path + File::SEPARATOR + @name
|
49
|
+
end # }}}
|
50
|
+
|
51
|
+
#----------------------------------------------------------------------------------
|
52
|
+
# method: update {{{
|
53
|
+
#++
|
54
|
+
# Marks the Message <tt>p_message</tt> as updated in the IMAP Database.
|
55
|
+
def update(p_message)
|
56
|
+
raise ArgumentError unless p_message.kind_of?(Message)
|
57
|
+
lines = self.load
|
58
|
+
return if lines.empty?
|
59
|
+
|
60
|
+
# Search and destroy the message in the IMAPDB
|
61
|
+
lines.delete_if {|line| line =~ /^\d+ #{p_message.id}/}
|
62
|
+
|
63
|
+
# Add the message at the end with the highest uid
|
64
|
+
lines << "#{@newuid} #{p_message.id}\n"
|
65
|
+
@newuid = @newuid.to_i + 1
|
66
|
+
lines[0] = "1 #{@uidv} #{@newuid}\n"
|
67
|
+
|
68
|
+
self.save(lines)
|
69
|
+
end # }}}
|
70
|
+
|
71
|
+
#----------------------------------------------------------------------------------
|
72
|
+
# method: add {{{
|
73
|
+
#++
|
74
|
+
# Adds the Message <tt>p_message</tt> to the IMAP Database.
|
75
|
+
def add(p_message)
|
76
|
+
raise ArgumentError unless p_message.kind_of?(Message)
|
77
|
+
lines = self.load
|
78
|
+
return if lines.empty?
|
79
|
+
|
80
|
+
# Search the message in the IMAPDB
|
81
|
+
lines.each {|line| return if line =~ /^\d+ #{p_message.id}/}
|
82
|
+
|
83
|
+
# Add the message at the end with the highest uid
|
84
|
+
lines << "#{@newuid} #{p_message.id}\n"
|
85
|
+
@newuid = @newuid.to_i + 1
|
86
|
+
lines[0] = "1 #{@uidv} #{@newuid}\n"
|
87
|
+
|
88
|
+
self.save(lines)
|
89
|
+
end # }}}
|
90
|
+
|
91
|
+
#----------------------------------------------------------------------------------
|
92
|
+
# method: delete {{{
|
93
|
+
#++
|
94
|
+
# Deletes the Message <tt>p_message</tt> from the IMAP Database.
|
95
|
+
def delete(p_message)
|
96
|
+
raise ArgumentError unless p_message.kind_of?(Message)
|
97
|
+
lines = self.load
|
98
|
+
return if lines.empty?
|
99
|
+
|
100
|
+
# Search and destroy the message in the IMAPDB
|
101
|
+
deleted = lines.delete_if {|line| line =~ /^\d+ #{p_message.id}/}
|
102
|
+
return if deleted.empty?
|
103
|
+
|
104
|
+
self.save(lines)
|
105
|
+
end # }}}
|
106
|
+
|
107
|
+
#----------------------------------------------------------------------------------
|
108
|
+
# method: to_xml {{{
|
109
|
+
#++
|
110
|
+
# Returns the REXML::Document that represents the IMAPDB object
|
111
|
+
def to_xml
|
112
|
+
return REXML::Document.new("<IMAPDB name=\"#{@name}\" path=\"#{@path}\" />")
|
113
|
+
end # }}}
|
114
|
+
|
115
|
+
#----------------------------------------------------------------------------------
|
116
|
+
# method: to_s {{{
|
117
|
+
#++
|
118
|
+
# Returns the String representation of the Domain object
|
119
|
+
def to_s
|
120
|
+
return "IMAP Database @name, path=\"#{@path}\""
|
121
|
+
end # }}}
|
122
|
+
|
123
|
+
#----------------------------------------------------------------------------------
|
124
|
+
# method: load {{{
|
125
|
+
#++
|
126
|
+
# Loads the IMAPDB in memory from its file.
|
127
|
+
def load
|
128
|
+
begin
|
129
|
+
lines = IO.readlines(@filename)
|
130
|
+
rescue Errno::ENOENT
|
131
|
+
logger.warning "Folder #{@folder.name}: IMAPDB does not exist, nothing to do" if !@@logger.nil?
|
132
|
+
return Array.new
|
133
|
+
end
|
134
|
+
if lines[0] !~ /^1 (\d+) (\d+)/ then
|
135
|
+
logger.error "Folder #{@folder.name}: Wrong IMAPDB version, aborting" if !@@logger.nil?
|
136
|
+
return Array.new
|
137
|
+
end
|
138
|
+
@uidv = $1
|
139
|
+
@newuid = $2
|
140
|
+
logger.info "Folder #{@folder.name}: uidv=#{@uidv}, new uid=#{@newuid}" if !@@logger.nil?
|
141
|
+
return lines
|
142
|
+
end # }}}
|
143
|
+
|
144
|
+
#----------------------------------------------------------------------------------
|
145
|
+
# method: save {{{
|
146
|
+
#++
|
147
|
+
# Saves the IMAPDB to its file.
|
148
|
+
def save(p_lines)
|
149
|
+
File.open(@filename, 'w') { |file| file.puts p_lines }
|
150
|
+
logger.info "Folder #{@folder.name}: IMAPDB saved" if !@@logger.nil?
|
151
|
+
end # }}}
|
152
|
+
end # }}}
|
153
|
+
|
154
|
+
end # }}}
|
@@ -0,0 +1,311 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: message.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'rexml/document'
|
10
|
+
require 'rmail'
|
11
|
+
require 'net/smtp'
|
12
|
+
require 'net/dns'
|
13
|
+
require 'vpopmail/config'
|
14
|
+
require 'vpopmail/domain'
|
15
|
+
require 'vpopmail/user'
|
16
|
+
require 'vpopmail/imapdb'
|
17
|
+
|
18
|
+
#------------------------------------------------------------------------------------------
|
19
|
+
#------------------------------------------------------------------------------------------
|
20
|
+
# module: Net {{{
|
21
|
+
#++
|
22
|
+
module Net # :nodoc: all
|
23
|
+
#------------------------------------------------------------------------------------------
|
24
|
+
# class: SMTP {{{
|
25
|
+
#++
|
26
|
+
class SMTP
|
27
|
+
#--------------------------------------------------------------------------------------
|
28
|
+
# method: validAddress? {{{
|
29
|
+
#++
|
30
|
+
def validAddress?(p_address, p_local)
|
31
|
+
begin
|
32
|
+
mailfrom p_local
|
33
|
+
rcptto p_address
|
34
|
+
getok('RSET')
|
35
|
+
rescue SMTPFatalError
|
36
|
+
begin
|
37
|
+
getok('RSET')
|
38
|
+
rescue
|
39
|
+
end
|
40
|
+
return false
|
41
|
+
rescue Exception => p_e
|
42
|
+
STDERR.puts "Unknown Error! #{p_e}"
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
return true
|
46
|
+
end # }}}
|
47
|
+
|
48
|
+
end # }}}
|
49
|
+
end # }}}
|
50
|
+
|
51
|
+
#------------------------------------------------------------------------------------------
|
52
|
+
#------------------------------------------------------------------------------------------
|
53
|
+
# module: VPOPMail {{{
|
54
|
+
#++
|
55
|
+
module VPOPMail
|
56
|
+
|
57
|
+
#------------------------------------------------------------------------------------------
|
58
|
+
# class: Message {{{
|
59
|
+
#++
|
60
|
+
# The Message class represents an email message stored in a Folder
|
61
|
+
class Message
|
62
|
+
attr_reader :filename, :id, :path, :info, :flags
|
63
|
+
|
64
|
+
#--------------------------------------------------------------------------------------
|
65
|
+
# class attribute: logger {{{
|
66
|
+
@@logger = nil
|
67
|
+
def self.logger ; @@logger ; end
|
68
|
+
def self.logger=(p_object) ; @@logger = p_object ; end
|
69
|
+
def logger(p_object) ; @@logger ; end
|
70
|
+
def logger=(p_object) ; @@logger = p_object ; end
|
71
|
+
# }}}
|
72
|
+
|
73
|
+
#--------------------------------------------------------------------------------------
|
74
|
+
# method: initialize {{{
|
75
|
+
#++
|
76
|
+
# Creates a new Message object after stored in the Folder <tt>p_folder</tt>.
|
77
|
+
#
|
78
|
+
# <tt>p_name</tt> contains the name of the file that contains the email.
|
79
|
+
# <tt>p_name</tt> follows the Maildir filename formatt as described in
|
80
|
+
# http://cr.yp.to/proto/maildir.html
|
81
|
+
def initialize(p_folder, p_name)
|
82
|
+
# The name analysis comes from http://cr.yp.to/proto/maildir.html
|
83
|
+
raise ArgumentError unless p_folder.kind_of?(Folder)
|
84
|
+
@folder = p_folder
|
85
|
+
@filename = p_name
|
86
|
+
@id = p_name
|
87
|
+
@info = nil
|
88
|
+
@flags = nil
|
89
|
+
if p_name =~ /.*:(\d),([A-Z]*)$/ then
|
90
|
+
@info = $1
|
91
|
+
@flags = $2
|
92
|
+
@id = p_name.gsub(/:\d,[A-Z]*$/, '')
|
93
|
+
end
|
94
|
+
self.load
|
95
|
+
end # }}}
|
96
|
+
|
97
|
+
#--------------------------------------------------------------------------------------
|
98
|
+
# class method: validAddress? {{{
|
99
|
+
#++
|
100
|
+
# Tells if the RMail::Address <tt>p_address</tt> is valid or not. Uses <tt>p_local</tt>
|
101
|
+
# as the origin email address when querying the mail server of <tt>p_address</tt>
|
102
|
+
#
|
103
|
+
# This methods queries the DNS of the domain contains in <tt>p_address</tt> for its
|
104
|
+
# MX Servers. Then it will use the SMTP protocol to query the MX servers for the validity
|
105
|
+
# of <tt>p_address</tt>
|
106
|
+
def self.validAddress?(p_address, p_local)
|
107
|
+
p_address = RMail::Address.parse(p_address)[0] if !p_address.kind_of? RMail::Address
|
108
|
+
p_local = RMail::Address.parse(p_local)[0] if !p_local.kind_of? RMail::Address
|
109
|
+
|
110
|
+
logger.info "Checking Domain: #{p_address.domain}" if !@@logger.nil?
|
111
|
+
resolver = Net::DNS::Resolver.new
|
112
|
+
logger.debug "#{resolver.inspect}"
|
113
|
+
answer = resolver.query(p_address.domain, Net::DNS::MX)
|
114
|
+
logger.debug "#{answer.inspect}"
|
115
|
+
if answer.nil? or answer.header.anCount == 0
|
116
|
+
logger.warn "No nameserver found for domain: #{resolver.errorstring}" if !@@logger.nil?
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
hosts = answer.answer.sort {|x, y| x.preference <=> y.preference}
|
120
|
+
|
121
|
+
hosts.each { |host|
|
122
|
+
logger.info "Connecting to MX #{host.exchange}" if !@@logger.nil?
|
123
|
+
begin
|
124
|
+
Net::SMTP.start(host.exchange, 25, p_local.domain) { |smtp|
|
125
|
+
logger.debug "Validating #{p_address.address}" if !@@logger.nil?
|
126
|
+
return smtp.validAddress?(p_address.address, p_local.address)
|
127
|
+
}
|
128
|
+
rescue Errno::ETIMEDOUT => p_e
|
129
|
+
# That means the MX Host does not answer
|
130
|
+
logger.warn "timeout in querying #{host.exchange}" if !@@logger.nil?
|
131
|
+
rescue Exception => p_e
|
132
|
+
logger.error "Unknown error: #{p_e}" if !@@logger.nil?
|
133
|
+
end
|
134
|
+
}
|
135
|
+
return false
|
136
|
+
end # }}}
|
137
|
+
|
138
|
+
#--------------------------------------------------------------------------------------
|
139
|
+
# method: validAddress? {{{
|
140
|
+
#++
|
141
|
+
# Tells if the RMail::Address <tt>p_address</tt> is valid or not. Extracts
|
142
|
+
# the origin email address when querying the mail server of <tt>p_address</tt>
|
143
|
+
# from the current Message
|
144
|
+
#
|
145
|
+
# See Message.validAddress?
|
146
|
+
def validAddress?(p_address)
|
147
|
+
return Message.validAddress?(p_address, @folder.domain.postmaster)
|
148
|
+
end # }}}
|
149
|
+
|
150
|
+
#--------------------------------------------------------------------------------------
|
151
|
+
# method: markAs {{{
|
152
|
+
#++
|
153
|
+
# Marks the Message as <i>ham</i> or <i>spam</i>
|
154
|
+
#
|
155
|
+
# Raises an ArgumentError if <tt>p_kind</tt> contains another value.
|
156
|
+
def markAs(p_kind)
|
157
|
+
logger.info "Marking message as #{p_kind}" if !@@logger.nil?
|
158
|
+
field = VPOPMail::CFG["SPAM Subject Field"]
|
159
|
+
header = @rmail.header
|
160
|
+
case p_kind
|
161
|
+
when /spam/i
|
162
|
+
if !header.field?(field) then
|
163
|
+
header[field] = header.subject || ''
|
164
|
+
header.subject = VPOPMail::CFG["SPAM Mark"] + (header.subject || '')
|
165
|
+
self.save
|
166
|
+
else
|
167
|
+
logger.info "Message #{@id} is already marked as SPAM" if !@@logger.nil?
|
168
|
+
end
|
169
|
+
when /ham/i
|
170
|
+
if header.field?(field) then
|
171
|
+
logger.debug "There is a #{field} field,\nSubject: #{header.subject}\nSPAM: #{header[field]}" if !@@logger.nil?
|
172
|
+
header.subject = header[field]
|
173
|
+
header.delete(field)
|
174
|
+
self.save
|
175
|
+
else
|
176
|
+
logger.info "Message #{@id} is already marked as HAM" if !@@logger.nil?
|
177
|
+
end
|
178
|
+
else
|
179
|
+
raise ArgumentError, "Bad kind: #{p_kind}", caller
|
180
|
+
end
|
181
|
+
end # }}}
|
182
|
+
|
183
|
+
#--------------------------------------------------------------------------------------
|
184
|
+
# method: learnAs {{{
|
185
|
+
#++
|
186
|
+
# Learns the Message as <i>ham</i> or <i>spam</i> using
|
187
|
+
# {spamassassin}[http://spamassassin.apache.org]
|
188
|
+
def learnAs(p_kind)
|
189
|
+
raise ArgumentError unless p_kind == "spam" or p_kind == "ham"
|
190
|
+
logger.info "Learning message as #{p_kind}" if !@@logger.nil?
|
191
|
+
debug = "-D bayes,learn,dns"
|
192
|
+
debug = ""
|
193
|
+
cmdString = "/usr/bin/sa-learn --single #{debug} --username=vpopmail --#{p_kind}"
|
194
|
+
cmd = IO.popen(cmdString, "w+")
|
195
|
+
RMail::Serialize.write(cmd, @rmail)
|
196
|
+
cmd.close_write
|
197
|
+
|
198
|
+
while line = cmd.gets do
|
199
|
+
line = line.gsub(/[\r\n]*$/, "")
|
200
|
+
next if line.empty?
|
201
|
+
logger.debug "SA-Learn: #{line}" if !@@logger.nil?
|
202
|
+
end
|
203
|
+
end # }}}
|
204
|
+
|
205
|
+
#--------------------------------------------------------------------------------------
|
206
|
+
# method: load {{{
|
207
|
+
#++
|
208
|
+
# Loads the Message from its file in its <tt>@rmail</tt> attribute (RMail::Message)
|
209
|
+
def load
|
210
|
+
if @filename == @id then
|
211
|
+
path = @folder.newpath + File::SEPARATOR + @filename
|
212
|
+
else
|
213
|
+
path = @folder.curpath + File::SEPARATOR + @filename
|
214
|
+
end
|
215
|
+
logger.info "Loading #{path}" if !@@logger.nil?
|
216
|
+
begin
|
217
|
+
@rmail = File.open(path) { |file| RMail::Parser.read(file) }
|
218
|
+
stat = File.stat(path)
|
219
|
+
@uid = stat.uid
|
220
|
+
@gid = stat.gid
|
221
|
+
logger.debug "Loaded. uid=#{@uid}, gid=#{@gid}" if !@@logger.nil?
|
222
|
+
rescue Errno::ENOENT
|
223
|
+
logger.error "Cannot load #{path}" if !@@logger.nil?
|
224
|
+
exit -1
|
225
|
+
end
|
226
|
+
end # }}}
|
227
|
+
|
228
|
+
#--------------------------------------------------------------------------------------
|
229
|
+
# method: save {{{
|
230
|
+
#++
|
231
|
+
# Saves the Message to its file, by serializing its <tt>@rmail</tt> attribute.
|
232
|
+
def save
|
233
|
+
@folder.imapdb.update(self)
|
234
|
+
path = @folder.newpath + File::SEPARATOR + @id
|
235
|
+
logger.info "Saving #{path}" if !@@logger.nil?
|
236
|
+
File.open(path, "w") { |file| RMail::Serialize.write(file, @rmail) }
|
237
|
+
File.chmod(0644, path)
|
238
|
+
File.chown(@uid, @gid, path)
|
239
|
+
File.delete(@folder.curpath + File::SEPARATOR + @filename)
|
240
|
+
@filename = @id
|
241
|
+
@info = nil
|
242
|
+
@flags = nil
|
243
|
+
logger.debug "Saved. new filename: #{@filename}" if !@@logger.nil?
|
244
|
+
end # }}}
|
245
|
+
|
246
|
+
#--------------------------------------------------------------------------------------
|
247
|
+
# method: copyTo {{{
|
248
|
+
#++
|
249
|
+
# Copies the Message to the Folder <tt>p_folder</tt>
|
250
|
+
def copyTo(p_folder)
|
251
|
+
logger.info "Copying to folder #{p_folder.name}" if !@@logger.nil?
|
252
|
+
p_folder.imapdb.add(self)
|
253
|
+
path = p_folder.newpath + File::SEPARATOR + @id
|
254
|
+
File.open(path, "w") { |file| RMail::Serialize.write(file, @rmail) }
|
255
|
+
File.chmod(0644, path)
|
256
|
+
File.chown(@uid, @gid, path)
|
257
|
+
end # }}}
|
258
|
+
|
259
|
+
#--------------------------------------------------------------------------------------
|
260
|
+
# method: deleteFrom {{{
|
261
|
+
#++
|
262
|
+
# Deletes the Message from the Folder <tt>p_folder</tt>
|
263
|
+
def deleteFrom(p_folder)
|
264
|
+
logger.info "Deleting from folder #{p_folder.name}" if !@@logger.nil?
|
265
|
+
p_folder.imapdb.delete(self)
|
266
|
+
begin
|
267
|
+
File.delete(p_folder.curpath + File::SEPARATOR + @filename)
|
268
|
+
rescue => p_e
|
269
|
+
begin
|
270
|
+
File.delete(p_folder.newpath + File::SEPARATOR + @filename)
|
271
|
+
rescue => p_e
|
272
|
+
logger.error "Problem while deleting: #{p_e}" if !@@logger.nil?
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end # }}}
|
276
|
+
|
277
|
+
#--------------------------------------------------------------------------------------
|
278
|
+
# method: moveTo {{{
|
279
|
+
#++
|
280
|
+
# Moves the Message to the Folder <tt>p_folder</tt>
|
281
|
+
def moveTo(p_folder)
|
282
|
+
logger.info "Moving to folder #{p_folder.name}" if !@@logger.nil?
|
283
|
+
self.copyTo(p_folder)
|
284
|
+
self.deleteFrom(@folder)
|
285
|
+
logger.info "Reloading from #{p_folder.name}" if !@@logger.nil?
|
286
|
+
@folder = p_folder
|
287
|
+
self.load
|
288
|
+
end # }}}
|
289
|
+
|
290
|
+
#--------------------------------------------------------------------------------------
|
291
|
+
# method: to_xml {{{
|
292
|
+
#++
|
293
|
+
# Returns the REXML::Document that represents the Message object
|
294
|
+
def to_xml
|
295
|
+
xml = "<Message id=\"#{@id}\""
|
296
|
+
xml += " info=\"#{@info}\"" if !@info.nil?
|
297
|
+
xml += " flags=\"#{@flags}\"" if !@flags.nil?
|
298
|
+
xml += " path=\"#{@path}\" />"
|
299
|
+
return REXML::Document.new(xml)
|
300
|
+
end # }}}
|
301
|
+
|
302
|
+
#--------------------------------------------------------------------------------------
|
303
|
+
# method: to_s {{{
|
304
|
+
#++
|
305
|
+
# Returns the String representation of the Message object
|
306
|
+
def to_s
|
307
|
+
return "Message #{@id}, info=#{@info}, flags=#{@flags}, path=\"#{@path}\""
|
308
|
+
end # }}}
|
309
|
+
end # }}}
|
310
|
+
|
311
|
+
end # }}}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006 Gildas Cherruel
|
3
|
+
# Read COPYRIGHT in the root of VPOPMail library for copyright info
|
4
|
+
# Creator: gildasc@rubyforg.org
|
5
|
+
# Author: $Author: gildasc $
|
6
|
+
# Version: $Id: user.rb 4 2006-10-07 23:18:13Z gildasc $
|
7
|
+
#++
|
8
|
+
|
9
|
+
require 'vpopmail/config'
|
10
|
+
require 'vpopmail/domain'
|
11
|
+
|
12
|
+
#------------------------------------------------------------------------------------------
|
13
|
+
#------------------------------------------------------------------------------------------
|
14
|
+
# module: VPOPMail {{{
|
15
|
+
module VPOPMail
|
16
|
+
|
17
|
+
#------------------------------------------------------------------------------------------
|
18
|
+
# class: User {{{
|
19
|
+
class User
|
20
|
+
attr_reader :name, :path, :domain
|
21
|
+
attr_writer :name, :path
|
22
|
+
|
23
|
+
#--------------------------------------------------------------------------------------
|
24
|
+
# class attribute: logger {{{
|
25
|
+
@@logger = nil
|
26
|
+
def self.logger ; @@logger ; end
|
27
|
+
def self.logger=(p_object) ; @@logger = p_object ; end
|
28
|
+
def logger(p_object) ; @@logger ; end
|
29
|
+
def logger=(p_object) ; @@logger = p_object ; end
|
30
|
+
# }}}
|
31
|
+
|
32
|
+
#----------------------------------------------------------------------------------
|
33
|
+
# method: initialize {{{
|
34
|
+
def initialize(p_domain)
|
35
|
+
@name = @path = ""
|
36
|
+
@domain = p_domain
|
37
|
+
end # }}}
|
38
|
+
|
39
|
+
#----------------------------------------------------------------------------------
|
40
|
+
# class method: FindUsers {{{
|
41
|
+
def self.FindUsers(p_domain, p_name = nil)
|
42
|
+
raise ArgumentError unless p_domain.kind_of?(Domain)
|
43
|
+
cmdString = "#{VPOPMail::CFG['VPOP Bin']}/vuserinfo -n -d"
|
44
|
+
if p_name.nil? or p_name.empty? then
|
45
|
+
cmdString = cmdString + " -D #{p_domain.name}"
|
46
|
+
else
|
47
|
+
raise ArgumentError unless p_name.kind_of?(String)
|
48
|
+
cmdString = cmdString + " #{p_name}@#{p_domain.name}"
|
49
|
+
end
|
50
|
+
cmd = IO.popen(cmdString, "w+")
|
51
|
+
cmd.close_write
|
52
|
+
|
53
|
+
users = Array.new
|
54
|
+
user = User.new(p_domain)
|
55
|
+
getName = true
|
56
|
+
while line = cmd.gets do
|
57
|
+
line = line.gsub(/[\r\n]*$/, "")
|
58
|
+
next if line.empty?
|
59
|
+
if getName then
|
60
|
+
user.name = line
|
61
|
+
else
|
62
|
+
user.path = line
|
63
|
+
users << user
|
64
|
+
user = User.new(p_domain)
|
65
|
+
end
|
66
|
+
getName = !getName
|
67
|
+
end
|
68
|
+
return users
|
69
|
+
end # }}}
|
70
|
+
|
71
|
+
#----------------------------------------------------------------------------------
|
72
|
+
# method: to_xml {{{
|
73
|
+
def to_xml
|
74
|
+
return "<User name=\"#{@name}\" path=\"#{@path}\" />"
|
75
|
+
end # }}}
|
76
|
+
|
77
|
+
#----------------------------------------------------------------------------------
|
78
|
+
# method: to_s {{{
|
79
|
+
def to_s
|
80
|
+
return to_xml
|
81
|
+
end # }}}
|
82
|
+
|
83
|
+
#----------------------------------------------------------------------------------
|
84
|
+
# Access Control {{{
|
85
|
+
public :name, :path, :name=, :path=
|
86
|
+
# }}}
|
87
|
+
end # }}}
|
88
|
+
|
89
|
+
end # }}}
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.10
|
3
|
+
specification_version: 1
|
4
|
+
name: vpopmail
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.0.0
|
7
|
+
date: 2006-10-08
|
8
|
+
summary: Ruby VPOPMail interface
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: gildas@breizh.org
|
12
|
+
homepage: http://ruby-vpopmail.rubyforge.org
|
13
|
+
rubyforge_project: ruby-vpopmail
|
14
|
+
description: "VPOPMail interface for Ruby. Allows to write filters, spam learners, etc. using
|
15
|
+
the vpopmail software package."
|
16
|
+
autorequire: vpopmail
|
17
|
+
default_executable:
|
18
|
+
bindir: bin
|
19
|
+
has_rdoc: true
|
20
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
21
|
+
requirements:
|
22
|
+
-
|
23
|
+
- ">"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.0.0
|
26
|
+
version:
|
27
|
+
platform: ruby
|
28
|
+
authors:
|
29
|
+
- Gildas Cherruel
|
30
|
+
files:
|
31
|
+
- lib/vpopmail
|
32
|
+
- lib/vpopmail.rb
|
33
|
+
- lib/vpopmail/config.rb
|
34
|
+
- lib/vpopmail/domain.rb
|
35
|
+
- lib/vpopmail/folder.rb
|
36
|
+
- lib/vpopmail/imapdb.rb
|
37
|
+
- lib/vpopmail/message.rb
|
38
|
+
- lib/vpopmail/user.rb
|
39
|
+
- examples/emailcheck.rb
|
40
|
+
- examples/vlearn.rb
|
41
|
+
- Rakefile
|
42
|
+
- install.rb
|
43
|
+
- README
|
44
|
+
test_files: []
|
45
|
+
rdoc_options:
|
46
|
+
- "--main"
|
47
|
+
- README
|
48
|
+
extra_rdoc_files:
|
49
|
+
- README
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
requirements: []
|
53
|
+
dependencies:
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rmail
|
56
|
+
version_requirement:
|
57
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
58
|
+
requirements:
|
59
|
+
-
|
60
|
+
- ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0.17"
|
63
|
+
version:
|