albanpeignier-ruby-managesieve 0.3.1
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/bin/sievectl +358 -0
- data/lib/managesieve.rb +351 -0
- metadata +53 -0
data/bin/sievectl
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# Copyright (c) 2004-2006 Andre Nathan <andre@digirati.com.br>
|
|
5
|
+
#
|
|
6
|
+
# Permission to use, copy, modify, and distribute this software for any
|
|
7
|
+
# purpose with or without fee is hereby granted, provided that the above
|
|
8
|
+
# copyright notice and this permission notice appear in all copies.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
11
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
12
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
13
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
14
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
15
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
16
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
+
#++
|
|
18
|
+
#
|
|
19
|
+
# = Overview
|
|
20
|
+
#
|
|
21
|
+
# Sievectl is a utility that allows for management of
|
|
22
|
+
# Sieve[http://www.cyrusoft.com/sieve/] scripts from the command line. It
|
|
23
|
+
# supports multiple accounts, configured in the .sievectlrc file located in
|
|
24
|
+
# the user's home directory.
|
|
25
|
+
#
|
|
26
|
+
# == Configuration file example
|
|
27
|
+
#
|
|
28
|
+
# accountA:
|
|
29
|
+
# host: sieve.accounta.tld
|
|
30
|
+
# port: 2000
|
|
31
|
+
# user: johndoe
|
|
32
|
+
# euser: johndoe
|
|
33
|
+
# password: secret
|
|
34
|
+
# auth: PLAIN
|
|
35
|
+
#
|
|
36
|
+
# accountB:
|
|
37
|
+
# host: mail.accountb.tld
|
|
38
|
+
# user: john
|
|
39
|
+
# password: secret
|
|
40
|
+
# auth: PLAIN
|
|
41
|
+
#
|
|
42
|
+
# The +port+ and +euser+ parameters can be ommited, and will respectively
|
|
43
|
+
# default to 2000 and the value of the +user+ parameter. If the +auth+
|
|
44
|
+
# parameter is ommited, it will default to +ANONYMOUS+.
|
|
45
|
+
#
|
|
46
|
+
# == Usage and examples
|
|
47
|
+
#
|
|
48
|
+
# $ sievectl help
|
|
49
|
+
# Usage: sievectl <account> <action> [script name]
|
|
50
|
+
# Action is one of:
|
|
51
|
+
# capabilities, list, show, activate, deactivate, add, addactive, delete
|
|
52
|
+
#
|
|
53
|
+
# Short forms for some actions are also accepted:
|
|
54
|
+
# caps (capabilities), act (activate), deact (deactivate),
|
|
55
|
+
# addact (addactive), del (delete)
|
|
56
|
+
#
|
|
57
|
+
# Examples:
|
|
58
|
+
# List server capabilities:
|
|
59
|
+
# sievectl myaccount caps
|
|
60
|
+
#
|
|
61
|
+
# List available scripts:
|
|
62
|
+
# sievectl myaccount list
|
|
63
|
+
#
|
|
64
|
+
# Show contents of a script:
|
|
65
|
+
# sievectl myaccount show scriptname
|
|
66
|
+
#
|
|
67
|
+
# Add a script:
|
|
68
|
+
# sievectl myaccount add scriptname script.txt
|
|
69
|
+
# or
|
|
70
|
+
# sievectl myaccount add scriptname < script.txt
|
|
71
|
+
# or
|
|
72
|
+
# cat script.txt | sievectl myaccount add scriptname
|
|
73
|
+
#
|
|
74
|
+
# Delete a script:
|
|
75
|
+
# sievectl myaccount del scriptname
|
|
76
|
+
#
|
|
77
|
+
#--
|
|
78
|
+
# $Id: sievectl,v 1.27 2006/08/30 18:06:21 andre Exp $
|
|
79
|
+
#++
|
|
80
|
+
#
|
|
81
|
+
|
|
82
|
+
begin
|
|
83
|
+
require 'rubygems'
|
|
84
|
+
rescue LoadError
|
|
85
|
+
end
|
|
86
|
+
require 'managesieve'
|
|
87
|
+
|
|
88
|
+
$has_termios = true
|
|
89
|
+
begin
|
|
90
|
+
require_gem 'termios'
|
|
91
|
+
rescue LoadError
|
|
92
|
+
begin
|
|
93
|
+
require 'termios'
|
|
94
|
+
rescue LoadError
|
|
95
|
+
$has_termios = false
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
require 'yaml'
|
|
100
|
+
|
|
101
|
+
class ManageSieve # :nodoc:
|
|
102
|
+
def print_capabilities
|
|
103
|
+
puts 'Capabilities:'
|
|
104
|
+
@capabilities.sort.each { |cap| puts " - #{cap}" }
|
|
105
|
+
|
|
106
|
+
puts 'Login Mechanisms:'
|
|
107
|
+
@login_mechs.sort.each { |mech| puts " - #{mech}" }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def print_scripts
|
|
111
|
+
puts 'Available scripts:'
|
|
112
|
+
scripts.sort.each do |name, active|
|
|
113
|
+
print " - #{name}"
|
|
114
|
+
print active ? " (active)\n" : "\n"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def upload_script(name, script, active=false)
|
|
119
|
+
put_script(name, script)
|
|
120
|
+
set_active(name)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class TemplateError < Exception # :nodoc:
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class ConfigFile < File # :nodoc:
|
|
128
|
+
def ConfigFile.open(name)
|
|
129
|
+
begin
|
|
130
|
+
conf = nil
|
|
131
|
+
super(name) do |file|
|
|
132
|
+
conf = YAML::load(file)
|
|
133
|
+
end
|
|
134
|
+
conf.each_key do |acct|
|
|
135
|
+
conf[acct].each_key { |k| conf[acct][k] = conf[acct][k].to_s }
|
|
136
|
+
end
|
|
137
|
+
conf
|
|
138
|
+
rescue Errno::ENOENT
|
|
139
|
+
ConfigFile::create_template(name)
|
|
140
|
+
exit 0
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
private
|
|
145
|
+
def ConfigFile.create_template(name)
|
|
146
|
+
STDERR.puts <<-__EOF__
|
|
147
|
+
* Could not find configuration file #{name}.
|
|
148
|
+
* A template file will be created. Please edit the values to fit your
|
|
149
|
+
* local configuration and run `#{File::basename $0}' again.
|
|
150
|
+
__EOF__
|
|
151
|
+
|
|
152
|
+
begin
|
|
153
|
+
file = File.new(name, 'w', 0600)
|
|
154
|
+
file.puts <<-__EOF__
|
|
155
|
+
accountname:
|
|
156
|
+
host: servername
|
|
157
|
+
port: port
|
|
158
|
+
user: username
|
|
159
|
+
euser: effectiveusername
|
|
160
|
+
password: password
|
|
161
|
+
auth: authmethod
|
|
162
|
+
__EOF__
|
|
163
|
+
rescue => e
|
|
164
|
+
raise TemplateError, e
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# The SieveCtl class is a simple set of wrapper methods around the ones
|
|
171
|
+
# available on the #ManageSieve class.
|
|
172
|
+
#
|
|
173
|
+
class SieveCtl
|
|
174
|
+
def initialize(conf)
|
|
175
|
+
@manage_sieve = ManageSieve.new(conf)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Prints the server capabilities.
|
|
179
|
+
def capabilities
|
|
180
|
+
@manage_sieve.print_capabilities
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Lists the available scripts, specifying which one is active.
|
|
184
|
+
def list
|
|
185
|
+
@manage_sieve.print_scripts
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Shows the contents of +script+.
|
|
189
|
+
def show(script)
|
|
190
|
+
raise ArgumentError, "`show' requires a script name" unless script
|
|
191
|
+
puts @manage_sieve.get_script(script)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Activates +script+.
|
|
195
|
+
def activate(script)
|
|
196
|
+
raise ArgumentError, "`activate' requires a script name" unless script
|
|
197
|
+
@manage_sieve.set_active(script)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Deactivates +script+. There is no +DEACTIVATE+ command in the MANAGESIEVE
|
|
201
|
+
# draft, so we fetch the script, remove it and upload it again.
|
|
202
|
+
def deactivate(script)
|
|
203
|
+
raise ArgumentError, "`deactivate' requires a script name" unless script
|
|
204
|
+
data = @manage_sieve.get_script(script)
|
|
205
|
+
@manage_sieve.delete_script(script)
|
|
206
|
+
@manage_sieve.put_script(script, data)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Adds a script named +script+, from file +file+. If +file+ is +nil+, read
|
|
210
|
+
# the script from +STDIN+. Activates the script is +active+ is true.
|
|
211
|
+
def add(script, file=nil, active=false)
|
|
212
|
+
action = "add#{active ? 'active' : ''}"
|
|
213
|
+
raise ArgumentError, "`#{action}' requires a script name" unless script
|
|
214
|
+
data = file ? File.open(file).readlines.to_s : STDIN.readlines.to_s
|
|
215
|
+
unless @manage_sieve.have_space?(script, data.length)
|
|
216
|
+
raise SieveCommandError, "not enough space for script `#{script}' " +
|
|
217
|
+
"(#{data.length} bytes)"
|
|
218
|
+
end
|
|
219
|
+
@manage_sieve.put_script(script, data)
|
|
220
|
+
activate script if active
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Deletes +script+
|
|
224
|
+
def delete(script)
|
|
225
|
+
raise ArgumentError, "`activate' requires a script name" unless script
|
|
226
|
+
@manage_sieve.delete_script(script)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def usage(quit=true, out=STDERR) # :nodoc: #
|
|
231
|
+
prog = File.basename $0
|
|
232
|
+
out.puts <<-__EOF__
|
|
233
|
+
Usage: #{prog} <account> <action> [script name]
|
|
234
|
+
Action is one of:
|
|
235
|
+
capabilities, list, show, activate, deactivate, add, addactive, delete
|
|
236
|
+
|
|
237
|
+
You can also try `#{prog} help' for usage examples.
|
|
238
|
+
__EOF__
|
|
239
|
+
exit 1 if quit
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def help # :nodoc:
|
|
243
|
+
prog = File::basename $0
|
|
244
|
+
usage(false, STDOUT)
|
|
245
|
+
puts <<-__EOF__
|
|
246
|
+
|
|
247
|
+
Short forms for some actions are also accepted:
|
|
248
|
+
caps (capabilities), act (activate), deact (deactivate), addact (addactive),
|
|
249
|
+
del (delete)
|
|
250
|
+
|
|
251
|
+
Examples:
|
|
252
|
+
List server capabilities:
|
|
253
|
+
#{prog} myaccount caps
|
|
254
|
+
|
|
255
|
+
List available scripts:
|
|
256
|
+
#{prog} myaccount list
|
|
257
|
+
|
|
258
|
+
Show contents of a script:
|
|
259
|
+
#{prog} myaccount show scriptname
|
|
260
|
+
|
|
261
|
+
Add a script:
|
|
262
|
+
#{prog} myaccount add scriptname script.txt
|
|
263
|
+
or
|
|
264
|
+
#{prog} myaccount add scriptname < script.txt
|
|
265
|
+
or
|
|
266
|
+
cat script.txt | #{prog} myaccount add scriptname
|
|
267
|
+
|
|
268
|
+
Delete a script:
|
|
269
|
+
#{prog} myaccount del scriptname
|
|
270
|
+
__EOF__
|
|
271
|
+
exit 0
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
#
|
|
276
|
+
# Main
|
|
277
|
+
#
|
|
278
|
+
|
|
279
|
+
help if ARGV[0] =~ /^h(elp)?$/i
|
|
280
|
+
|
|
281
|
+
account, action, name, file = ARGV
|
|
282
|
+
usage if action.nil?
|
|
283
|
+
|
|
284
|
+
begin
|
|
285
|
+
conf = ConfigFile::open(ENV['HOME'] + '/.sievectlrc')
|
|
286
|
+
rescue TemplateError => e
|
|
287
|
+
STDERR.puts "Cannot create template configuration file: #{e}"
|
|
288
|
+
exit 1
|
|
289
|
+
rescue => e
|
|
290
|
+
STDERR.puts "Cannot load configuration file: `#{e}'"
|
|
291
|
+
exit 1
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
unless conf.has_key? account
|
|
295
|
+
STDERR.puts <<-__EOF__
|
|
296
|
+
* Configuration for account `#{account}' not found.
|
|
297
|
+
* Maybe your configuration file is in the old format?
|
|
298
|
+
__EOF__
|
|
299
|
+
exit 1
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
info = conf[account]
|
|
303
|
+
|
|
304
|
+
if $has_termios and info['password'].nil?
|
|
305
|
+
oldt = Termios.tcgetattr(STDIN)
|
|
306
|
+
newt = oldt.dup
|
|
307
|
+
newt.lflag &= ~Termios::ECHO
|
|
308
|
+
Termios.tcsetattr(STDIN, Termios::TCSANOW, newt)
|
|
309
|
+
print 'Password: '
|
|
310
|
+
info['password'] = STDIN.gets
|
|
311
|
+
Termios.tcsetattr(STDIN, Termios::TCSANOW, oldt)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if info['password'].nil?
|
|
315
|
+
STDERR.puts "* Password not given."
|
|
316
|
+
exit 1
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
info['password'].chomp!
|
|
320
|
+
|
|
321
|
+
begin
|
|
322
|
+
sievectl = SieveCtl.new(
|
|
323
|
+
:host => info['host'],
|
|
324
|
+
:port => info['port'] || 2000,
|
|
325
|
+
:user => info['user'],
|
|
326
|
+
:euser => info['euser'] || info['user'],
|
|
327
|
+
:password => info['password'],
|
|
328
|
+
:auth => info['auth']
|
|
329
|
+
)
|
|
330
|
+
rescue SieveNetworkError => e
|
|
331
|
+
STDERR.puts "* #{e}"
|
|
332
|
+
exit 1
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
begin
|
|
336
|
+
case action
|
|
337
|
+
when /^act(ivate)?$/
|
|
338
|
+
sievectl.activate(name)
|
|
339
|
+
when /^add$/
|
|
340
|
+
sievectl.add(name, file)
|
|
341
|
+
when /^addact(ive)?/
|
|
342
|
+
sievectl.add(name, file, true)
|
|
343
|
+
when /^cap(abilitie)?s$/
|
|
344
|
+
sievectl.capabilities
|
|
345
|
+
when /^deact(ivate)?$/
|
|
346
|
+
sievectl.deactivate(name)
|
|
347
|
+
when /^del(ete)?$/
|
|
348
|
+
sievectl.delete(name)
|
|
349
|
+
when /^list$/
|
|
350
|
+
sievectl.list
|
|
351
|
+
when /^show$/
|
|
352
|
+
sievectl.show(name)
|
|
353
|
+
else
|
|
354
|
+
usage
|
|
355
|
+
end
|
|
356
|
+
rescue ArgumentError, SieveCommandError => e
|
|
357
|
+
STDERR.puts "* sievectl: #{e}"
|
|
358
|
+
end
|
data/lib/managesieve.rb
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
#
|
|
3
|
+
#--
|
|
4
|
+
# Copyright (c) 2004-2006 Andre Nathan <andre@digirati.com.br>
|
|
5
|
+
#
|
|
6
|
+
# Permission to use, copy, modify, and distribute this software for any
|
|
7
|
+
# purpose with or without fee is hereby granted, provided that the above
|
|
8
|
+
# copyright notice and this permission notice appear in all copies.
|
|
9
|
+
#
|
|
10
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
11
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
12
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
13
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
14
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
15
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
16
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
17
|
+
#++
|
|
18
|
+
#
|
|
19
|
+
# == Overview
|
|
20
|
+
#
|
|
21
|
+
# This library is a pure-ruby implementation of the MANAGESIEVE protocol, as
|
|
22
|
+
# specified in its
|
|
23
|
+
# Draft[http://managesieve.rubyforge.org/draft-martin-managesieve-04.txt].
|
|
24
|
+
#
|
|
25
|
+
# See the ManageSieve class for documentation and examples.
|
|
26
|
+
#
|
|
27
|
+
#--
|
|
28
|
+
# $Id: managesieve.rb,v 1.13 2006/08/30 14:23:58 andre Exp $
|
|
29
|
+
#++
|
|
30
|
+
#
|
|
31
|
+
|
|
32
|
+
require 'base64'
|
|
33
|
+
require 'socket'
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
require 'openssl'
|
|
37
|
+
rescue LoadError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# Define our own Base64.encode64 for compatibility with ruby <= 1.8.1, which
|
|
42
|
+
# defines encode64() at the top level.
|
|
43
|
+
#
|
|
44
|
+
module Base64 # :nodoc:
|
|
45
|
+
def encode64(s)
|
|
46
|
+
[s].pack('m')
|
|
47
|
+
end
|
|
48
|
+
module_function :encode64
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class SieveAuthError < Exception; end
|
|
52
|
+
class SieveCommandError < Exception; end
|
|
53
|
+
class SieveNetworkError < Exception; end
|
|
54
|
+
class SieveResponseError < Exception; end
|
|
55
|
+
|
|
56
|
+
#
|
|
57
|
+
# ManageSieve implements MANAGESIEVE, a protocol for remote management of
|
|
58
|
+
# Sieve[http://www.cyrusoft.com/sieve/] scripts.
|
|
59
|
+
#
|
|
60
|
+
# The following MANAGESIEVE commands are implemented:
|
|
61
|
+
# * CAPABILITY
|
|
62
|
+
# * DELETESCRIPT
|
|
63
|
+
# * GETSCRIPT
|
|
64
|
+
# * HAVESPACE
|
|
65
|
+
# * LISTSCRIPTS
|
|
66
|
+
# * LOGOUT
|
|
67
|
+
# * PUTSCRIPT
|
|
68
|
+
# * SETACTIVE
|
|
69
|
+
#
|
|
70
|
+
# The AUTHENTICATE command is partially implemented. Currently the +LOGIN+
|
|
71
|
+
# and +PLAIN+ authentication mechanisms are implemented.
|
|
72
|
+
#
|
|
73
|
+
# = Example
|
|
74
|
+
#
|
|
75
|
+
# # Create a new ManageSieve instance
|
|
76
|
+
# m = ManageSieve.new(
|
|
77
|
+
# :host => 'sievehost.mydomain.com',
|
|
78
|
+
# :port => 2000,
|
|
79
|
+
# :user => 'johndoe',
|
|
80
|
+
# :password => 'secret',
|
|
81
|
+
# :auth => 'PLAIN'
|
|
82
|
+
# )
|
|
83
|
+
#
|
|
84
|
+
# # List installed scripts
|
|
85
|
+
# m.scripts.sort do |name, active|
|
|
86
|
+
# print name
|
|
87
|
+
# print active ? " (active)\n" : "\n"
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
# script = <<__EOF__
|
|
91
|
+
# require "fileinto";
|
|
92
|
+
# if header :contains ["to", "cc"] "ruby-talk@ruby-lang.org" {
|
|
93
|
+
# fileinto "Ruby-talk";
|
|
94
|
+
# }
|
|
95
|
+
# __EOF__
|
|
96
|
+
#
|
|
97
|
+
# # Test if there's enough space for script 'foobar'
|
|
98
|
+
# puts m.have_space?('foobar', script.length)
|
|
99
|
+
#
|
|
100
|
+
# # Upload it
|
|
101
|
+
# m.put_script('foobar', script)
|
|
102
|
+
#
|
|
103
|
+
# # Show its contents
|
|
104
|
+
# puts m.get_script('foobar')
|
|
105
|
+
#
|
|
106
|
+
# # Close the connection
|
|
107
|
+
# m.logout
|
|
108
|
+
#
|
|
109
|
+
class ManageSieve
|
|
110
|
+
SIEVE_PORT = 2000
|
|
111
|
+
|
|
112
|
+
attr_reader :host, :port, :user, :euser, :capabilities, :login_mechs, :use_tls
|
|
113
|
+
|
|
114
|
+
# Create a new ManageSieve instance. The +info+ parameter is a hash with the
|
|
115
|
+
# following keys:
|
|
116
|
+
#
|
|
117
|
+
# [<i>:host</i>] the sieve server
|
|
118
|
+
# [<i>:port</i>] the sieve port (defaults to 2000)
|
|
119
|
+
# [<i>:user</i>] the name of the user
|
|
120
|
+
# [<i>:euser</i>] the name of the effective user (defaults to +:user+)
|
|
121
|
+
# [<i>:password</i>] the password of the user
|
|
122
|
+
# [<i>:auth_mech</i>] the authentication mechanism (defaults to +"ANONYMOUS"+)
|
|
123
|
+
# [<i>:use_tls</i>] if true, try to use tls when server supports it
|
|
124
|
+
#
|
|
125
|
+
def initialize(info)
|
|
126
|
+
@host = info[:host]
|
|
127
|
+
@port = info[:port] || 2000
|
|
128
|
+
@user = info[:user]
|
|
129
|
+
@euser = info[:euser] || @user
|
|
130
|
+
@password = info[:password]
|
|
131
|
+
@auth_mech = info[:auth] || 'ANONYMOUS'
|
|
132
|
+
@use_tls = info[:use_tls] || true
|
|
133
|
+
|
|
134
|
+
@capabilities = []
|
|
135
|
+
@login_mechs = []
|
|
136
|
+
@implementation = ''
|
|
137
|
+
@supports_tls = false
|
|
138
|
+
@socket = TCPSocket.new(@host, @port)
|
|
139
|
+
|
|
140
|
+
data = get_response
|
|
141
|
+
server_features(data)
|
|
142
|
+
|
|
143
|
+
starttls if use_tls and supports_tls?
|
|
144
|
+
|
|
145
|
+
authenticate
|
|
146
|
+
@password = nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# If a block is given, calls it for each script stored on the server,
|
|
151
|
+
# passing its name and status as parameters. Else, and array
|
|
152
|
+
# of [ +name+, +status+ ] arrays is returned. The status is either
|
|
153
|
+
# 'ACTIVE' or nil.
|
|
154
|
+
def scripts
|
|
155
|
+
begin
|
|
156
|
+
scripts = send_command('LISTSCRIPTS')
|
|
157
|
+
rescue SieveCommandError => e
|
|
158
|
+
raise e, "Cannot list scripts: #{e}"
|
|
159
|
+
end
|
|
160
|
+
return scripts unless block_given?
|
|
161
|
+
scripts.each { |name, status| yield(name, status) }
|
|
162
|
+
end
|
|
163
|
+
alias :each_script :scripts
|
|
164
|
+
|
|
165
|
+
# Returns the contents of +script+ as a string.
|
|
166
|
+
def get_script(script)
|
|
167
|
+
begin
|
|
168
|
+
data = send_command('GETSCRIPT', sieve_name(script))
|
|
169
|
+
rescue SieveCommandError => e
|
|
170
|
+
raise e, "Cannot get script: #{e}"
|
|
171
|
+
end
|
|
172
|
+
return data.to_s.chomp
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Uploads +script+ to the server, using +data+ as its contents.
|
|
176
|
+
def put_script(script, data)
|
|
177
|
+
args = sieve_name(script)
|
|
178
|
+
args += ' ' + sieve_string(data) if data
|
|
179
|
+
send_command('PUTSCRIPT', args)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Deletes +script+ from the server.
|
|
183
|
+
def delete_script(script)
|
|
184
|
+
send_command('DELETESCRIPT', sieve_name(script))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Sets +script+ as active.
|
|
188
|
+
def set_active(script)
|
|
189
|
+
send_command('SETACTIVE', sieve_name(script))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Returns true if there is space on the server to store +script+ with
|
|
193
|
+
# size +size+ and false otherwise.
|
|
194
|
+
def have_space?(script, size)
|
|
195
|
+
begin
|
|
196
|
+
args = sieve_name(script) + ' ' + size.to_s
|
|
197
|
+
send_command('HAVESPACE', args)
|
|
198
|
+
return true
|
|
199
|
+
rescue SieveCommandError
|
|
200
|
+
return false
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Returns true if the server supports TLS and false otherwise.
|
|
205
|
+
def supports_tls?
|
|
206
|
+
@supports_tls
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Disconnect from the server.
|
|
210
|
+
def logout
|
|
211
|
+
send_command('LOGOUT')
|
|
212
|
+
@socket.close
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private
|
|
216
|
+
def authenticate # :nodoc:
|
|
217
|
+
unless @login_mechs.include? @auth_mech
|
|
218
|
+
raise SieveAuthError, "Server doesn't allow #{@auth_mech} authentication"
|
|
219
|
+
end
|
|
220
|
+
case @auth_mech
|
|
221
|
+
when /PLAIN/i
|
|
222
|
+
auth_plain(@euser, @user, @password)
|
|
223
|
+
when /LOGIN/i
|
|
224
|
+
auth_login(@user, @password)
|
|
225
|
+
else
|
|
226
|
+
raise SieveAuthError, "#{@auth_mech} authentication is not implemented"
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
private
|
|
231
|
+
def auth_plain(euser, user, pass) # :nodoc:
|
|
232
|
+
args = [ euser, user, pass ]
|
|
233
|
+
params = sieve_name('PLAIN') + ' '
|
|
234
|
+
params += sieve_name(Base64.encode64(args.join(0.chr)).gsub(/\n/, ''))
|
|
235
|
+
send_command('AUTHENTICATE', params)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
def auth_login(user, pass) # :nodoc:
|
|
240
|
+
send_command('AUTHENTICATE', sieve_name('LOGIN'), false)
|
|
241
|
+
send_command(sieve_name(Base64.encode64(user)).gsub(/\n/, ''), nil, false)
|
|
242
|
+
send_command(sieve_name(Base64.encode64(pass)).gsub(/\n/, ''))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
private
|
|
246
|
+
def server_features(lines) # :nodoc:
|
|
247
|
+
lines.each do |type, data|
|
|
248
|
+
case type
|
|
249
|
+
when 'IMPLEMENTATION'
|
|
250
|
+
@implementation = data
|
|
251
|
+
when 'SASL'
|
|
252
|
+
@login_mechs = data.split
|
|
253
|
+
when 'SIEVE'
|
|
254
|
+
@capabilities = data.split
|
|
255
|
+
when 'STARTTLS'
|
|
256
|
+
@supports_tls = true
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
private
|
|
262
|
+
def get_line # :nodoc:
|
|
263
|
+
begin
|
|
264
|
+
return @socket.readline.chomp
|
|
265
|
+
rescue EOFError => e
|
|
266
|
+
raise SieveNetworkError, "Network error: #{e}"
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
private
|
|
271
|
+
def send_command(cmd, args=nil, wait_response=true) # :nodoc:
|
|
272
|
+
cmd += ' ' + args if args
|
|
273
|
+
begin
|
|
274
|
+
@socket.write(cmd + "\r\n")
|
|
275
|
+
resp = get_response if wait_response
|
|
276
|
+
rescue SieveResponseError => e
|
|
277
|
+
raise SieveCommandError, "Command error: #{e}"
|
|
278
|
+
end
|
|
279
|
+
return resp
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
private
|
|
283
|
+
def parse_each_line # :nodoc:
|
|
284
|
+
loop do
|
|
285
|
+
data = get_line
|
|
286
|
+
|
|
287
|
+
# server ok
|
|
288
|
+
m = /^OK(.*)?$/.match(data)
|
|
289
|
+
yield :ok, m.captures.values_at(0, 3) and next if m
|
|
290
|
+
|
|
291
|
+
# server error
|
|
292
|
+
m = /^(NO|BYE)(.*)?$/.match(data)
|
|
293
|
+
if m
|
|
294
|
+
err, msg = m.captures
|
|
295
|
+
size = msg.scan(/\{(\d+)\+?\}/).to_s.to_i
|
|
296
|
+
yield :error, @socket.read(size.to_i + 2) and next if size > 0
|
|
297
|
+
yield :error, msg and next
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# quoted text
|
|
301
|
+
m = /"([^"]*)"(\s"?([^"]*)"?)?$/.match(data)
|
|
302
|
+
yield :quoted, m.captures.values_at(0,2) and next if m
|
|
303
|
+
|
|
304
|
+
# literal
|
|
305
|
+
m = /\{(\d+)\+?\}/.match(data)
|
|
306
|
+
size = m.captures.first.to_i
|
|
307
|
+
yield :literal, @socket.read(size + 2) and next if m # + 2 for \r\n
|
|
308
|
+
|
|
309
|
+
# other
|
|
310
|
+
yield :other, data
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
private
|
|
315
|
+
def get_response # :nodoc:
|
|
316
|
+
response = []
|
|
317
|
+
parse_each_line do |flag, data|
|
|
318
|
+
case flag
|
|
319
|
+
when :ok
|
|
320
|
+
return response
|
|
321
|
+
when :error
|
|
322
|
+
raise SieveResponseError, data.strip.gsub(/\r\n/, ' ')
|
|
323
|
+
else
|
|
324
|
+
response << data
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
private
|
|
330
|
+
def sieve_name(name) # :nodoc:
|
|
331
|
+
return "\"#{name}\""
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
private
|
|
335
|
+
def sieve_string(string) # :nodoc:
|
|
336
|
+
return "{#{string.length}+}\r\n#{string}"
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
private
|
|
340
|
+
def starttls
|
|
341
|
+
send_command 'STARTTLS'
|
|
342
|
+
|
|
343
|
+
@socket = OpenSSL::SSL::SSLSocket.new @socket
|
|
344
|
+
@socket.sync_close = true
|
|
345
|
+
@socket.connect
|
|
346
|
+
|
|
347
|
+
data = get_response
|
|
348
|
+
server_features(data)
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: albanpeignier-ruby-managesieve
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Andre Nathan
|
|
8
|
+
autorequire: managesieve
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-01-11 00:00:00 -08:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies: []
|
|
15
|
+
|
|
16
|
+
description: ruby-managesieve is a pure-ruby implementation of the MANAGESIEVE protocol, allowing remote management of Sieve scripts from ruby.
|
|
17
|
+
email: andre@digirati.com.br
|
|
18
|
+
executables:
|
|
19
|
+
- sievectl
|
|
20
|
+
extensions: []
|
|
21
|
+
|
|
22
|
+
extra_rdoc_files: []
|
|
23
|
+
|
|
24
|
+
files:
|
|
25
|
+
- lib/managesieve.rb
|
|
26
|
+
has_rdoc: true
|
|
27
|
+
homepage: http://managesieve.rubyforge.org
|
|
28
|
+
post_install_message:
|
|
29
|
+
rdoc_options: []
|
|
30
|
+
|
|
31
|
+
require_paths:
|
|
32
|
+
- lib
|
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: "0"
|
|
38
|
+
version:
|
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: "0"
|
|
44
|
+
version:
|
|
45
|
+
requirements:
|
|
46
|
+
- A network connection and a MANAGESIEVE server.
|
|
47
|
+
rubyforge_project: ruby-managesieve
|
|
48
|
+
rubygems_version: 1.2.0
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 2
|
|
51
|
+
summary: A Ruby library for the MANAGESIEVE protocol
|
|
52
|
+
test_files: []
|
|
53
|
+
|