vpopmail 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|