ruby-managesieve 0.1.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/bin/sievectl +137 -0
- data/lib/managesieve.rb +301 -0
- metadata +38 -0
data/bin/sievectl
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Copyright (c) 2004 Andre Nathan <andre@digirati.com.br>
|
4
|
+
#
|
5
|
+
# Permission to use, copy, modify, and distribute this software for any
|
6
|
+
# purpose with or without fee is hereby granted, provided that the above
|
7
|
+
# copyright notice and this permission notice appear in all copies.
|
8
|
+
#
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
10
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
11
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
12
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
13
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
14
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
15
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
16
|
+
#
|
17
|
+
# $Id: sievectl,v 1.1.1.1 2004/12/20 17:49:51 andre Exp $
|
18
|
+
#
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'rubygems'
|
22
|
+
rescue LoadError
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'managesieve'
|
27
|
+
require 'yaml'
|
28
|
+
|
29
|
+
class ManageSieve
|
30
|
+
def print_capabilities
|
31
|
+
puts 'Capabilities:'
|
32
|
+
@capabilities.each { |cap| puts " - #{cap}" }
|
33
|
+
|
34
|
+
puts 'Login Mechanisms:'
|
35
|
+
@login_mechs.each { |mech| puts " - #{mech}" }
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_scripts
|
39
|
+
puts 'Available scripts:'
|
40
|
+
each_script do |name, active|
|
41
|
+
print " - #{name}"
|
42
|
+
print active ? " (active)\n" : "\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def upload_script(name, script, active=false)
|
47
|
+
put_script(name, script)
|
48
|
+
set_active(name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ConfigFile < File
|
53
|
+
def ConfigFile.open(*args)
|
54
|
+
file = super(*args)
|
55
|
+
conf = YAML::load(file)
|
56
|
+
file.close
|
57
|
+
return conf
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def usage
|
62
|
+
STDERR.puts <<__EOF__
|
63
|
+
Usage: #{File::basename($0)} <caps|list|show|activate|add|addactive|del> [script name]
|
64
|
+
__EOF__
|
65
|
+
end
|
66
|
+
|
67
|
+
def help
|
68
|
+
prog = File::basename $0
|
69
|
+
puts <<__EOF__
|
70
|
+
Usage: #{prog} <caps|list|show|activate|add|addactive|del> [script name]
|
71
|
+
|
72
|
+
Examples:
|
73
|
+
List server capabilities:
|
74
|
+
#{prog} caps
|
75
|
+
|
76
|
+
List available scripts:
|
77
|
+
#{prog} list
|
78
|
+
|
79
|
+
Show contents of a script:
|
80
|
+
#{prog} show scriptname
|
81
|
+
|
82
|
+
Add a script:
|
83
|
+
#{prog} add scriptname script.txt
|
84
|
+
or
|
85
|
+
cat script.txt | #{prog} add scriptname
|
86
|
+
|
87
|
+
Delete a script:
|
88
|
+
#{prog} del scriptname
|
89
|
+
__EOF__
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#
|
94
|
+
# Main
|
95
|
+
#
|
96
|
+
|
97
|
+
conf = ConfigFile::open(ENV['HOME'] + '/.sievectlrc')
|
98
|
+
|
99
|
+
m = ManageSieve.new(
|
100
|
+
:host => conf['host'],
|
101
|
+
:port => conf['port'],
|
102
|
+
:user => conf['user'],
|
103
|
+
:euser => conf['euser'],
|
104
|
+
:password => conf['password'],
|
105
|
+
:auth => conf['auth']
|
106
|
+
)
|
107
|
+
|
108
|
+
action, name, file = ARGV
|
109
|
+
|
110
|
+
case action
|
111
|
+
when /^cap(abilitie)?s$/
|
112
|
+
m.print_capabilities
|
113
|
+
when /^list$/
|
114
|
+
m.print_scripts
|
115
|
+
when /^show$/
|
116
|
+
raise ArgumentError, "`show' requires a script name" unless name
|
117
|
+
puts m.get_script(name)
|
118
|
+
when /^act(ivate)?$/
|
119
|
+
raise ArgumentError, "`activate' requires a script name" unless name
|
120
|
+
m.set_active(name)
|
121
|
+
when /^add$/
|
122
|
+
raise ArgumentError, "`add' requires a script name" unless name
|
123
|
+
script = file ? File.open(file).readlines : STDIN.readlines
|
124
|
+
m.put_script(name, script.to_s)
|
125
|
+
when /^addact(ive)?/
|
126
|
+
raise ArgumentError, "`add' requires a script name" unless name
|
127
|
+
script = file ? File.open(file).readlines : STDIN.readlines
|
128
|
+
m.put_script(name, script.to_s)
|
129
|
+
m.set_active(name)
|
130
|
+
when /^del(ete)?$/
|
131
|
+
raise ArgumentError, "`activate' requires a script name" unless name
|
132
|
+
m.delete_script(name)
|
133
|
+
when /^h(elp)?$/
|
134
|
+
help
|
135
|
+
else
|
136
|
+
usage
|
137
|
+
end
|
data/lib/managesieve.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
#--
|
4
|
+
# Copyright (c) 2004 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.3 2004/12/20 18:34:32 andre Exp $
|
29
|
+
#++
|
30
|
+
#
|
31
|
+
|
32
|
+
require 'base64'
|
33
|
+
require 'socket'
|
34
|
+
|
35
|
+
class SieveAuthError < Exception; end
|
36
|
+
class SieveCommandError < Exception; end
|
37
|
+
class SieveResponseError < Exception; end
|
38
|
+
|
39
|
+
#
|
40
|
+
# ManageSieve implements MANAGESIEVE, a protocol for remote management of
|
41
|
+
# Sieve[http://www.cyrusoft.com/sieve/] scripts.
|
42
|
+
#
|
43
|
+
# The following MANAGESIEVE commands are implemented:
|
44
|
+
# * CAPABILITY
|
45
|
+
# * DELETESCRIPT
|
46
|
+
# * GETSCRIPT
|
47
|
+
# * HAVESPACE
|
48
|
+
# * LISTSCRIPTS
|
49
|
+
# * LOGOUT
|
50
|
+
# * PUTSCRIPT
|
51
|
+
# * SETACTIVE
|
52
|
+
#
|
53
|
+
# The AUTHENTICATE command is partially implemented. Currently the +LOGIN+
|
54
|
+
# and +PLAIN+ authentication mechanisms are implemented.
|
55
|
+
#
|
56
|
+
# = Example
|
57
|
+
#
|
58
|
+
# # Create a new ManageSieve instance
|
59
|
+
# m = ManageSieve.new(
|
60
|
+
# :host => 'sievehost.mydomain.com',
|
61
|
+
# :port => 2000,
|
62
|
+
# :user => 'johndoe',
|
63
|
+
# :password => 'secret',
|
64
|
+
# :auth => 'PLAIN'
|
65
|
+
# )
|
66
|
+
#
|
67
|
+
# # List installed scripts
|
68
|
+
# m.each_script do |name, active|
|
69
|
+
# print name
|
70
|
+
# print active ? " (active)\n" : "\n"
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# script = <<__EOF__
|
74
|
+
# require "fileinto";
|
75
|
+
# if header :contains ["to", "cc"] "ruby-talk@ruby-lang.org" {
|
76
|
+
# fileinto "Ruby-talk";
|
77
|
+
# }
|
78
|
+
# __EOF__
|
79
|
+
#
|
80
|
+
# # Test if there's enough space for script 'foobar'
|
81
|
+
# puts m.have_space?('foobar', script.length)
|
82
|
+
#
|
83
|
+
# # Upload it
|
84
|
+
# m.put_script('foobar', script)
|
85
|
+
#
|
86
|
+
# # Show its contents
|
87
|
+
# puts m.get_script('foobar')
|
88
|
+
#
|
89
|
+
# # Close the connection
|
90
|
+
# m.logout
|
91
|
+
#
|
92
|
+
class ManageSieve
|
93
|
+
SIEVE_PORT = 2000
|
94
|
+
|
95
|
+
attr_reader :host, :port, :user, :euser, :capabilities, :login_mechs
|
96
|
+
|
97
|
+
# Create a new ManageSieve instance. The +info+ parameter is a hash with the
|
98
|
+
# following keys:
|
99
|
+
#
|
100
|
+
# [<i>:host</i>] the sieve server
|
101
|
+
# [<i>:port</i>] the sieve port (defaults to 2000)
|
102
|
+
# [<i>:user</i>] the name of the user
|
103
|
+
# [<i>:euser</i>] the name of the effective user (defaults to +:user+)
|
104
|
+
# [<i>:password</i>] the password of the user
|
105
|
+
# [<i>:auth_mech</i>] the authentication mechanism (defaults to +"ANONYMOUS"+)
|
106
|
+
#
|
107
|
+
def initialize(info)
|
108
|
+
@host = info[:host]
|
109
|
+
@port = info[:port] || 2000
|
110
|
+
@user = info[:user]
|
111
|
+
@euser = info[:euser] || @user
|
112
|
+
@password = info[:password]
|
113
|
+
@auth_mech = info[:auth] || 'ANONYMOUS'
|
114
|
+
|
115
|
+
@capabilities = []
|
116
|
+
@login_mechs = []
|
117
|
+
@implementation = ''
|
118
|
+
@supports_tls = false
|
119
|
+
@socket = TCPSocket.new(@host, @port)
|
120
|
+
|
121
|
+
data = get_response
|
122
|
+
server_features(data)
|
123
|
+
authenticate
|
124
|
+
@password = nil
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
# Calls the given block for each script stored on the server, passing
|
129
|
+
# its name and status as parameters. The status is either 'ACTIVE' or
|
130
|
+
# nil.
|
131
|
+
def each_script
|
132
|
+
begin
|
133
|
+
scripts = send_command('LISTSCRIPTS')
|
134
|
+
rescue SieveCommandError => e
|
135
|
+
raise e, "Cannot list scripts"
|
136
|
+
end
|
137
|
+
scripts.each { |name, status| yield(name, status) }
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns the contents of +script+ as a string.
|
141
|
+
def get_script(script)
|
142
|
+
begin
|
143
|
+
data = send_command('GETSCRIPT', sieve_name(script))
|
144
|
+
rescue SieveCommandError => e
|
145
|
+
raise e, "Cannot get script: #{e}"
|
146
|
+
end
|
147
|
+
return data.to_s
|
148
|
+
end
|
149
|
+
|
150
|
+
# Uploads +script+ to the server, using +data+ as its contents.
|
151
|
+
def put_script(script, data)
|
152
|
+
args = sieve_name(script)
|
153
|
+
args += ' ' + sieve_string(data) if data
|
154
|
+
send_command('PUTSCRIPT', args)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Deletes +script+ from the server.
|
158
|
+
def delete_script(script)
|
159
|
+
send_command('DELETESCRIPT', sieve_name(script))
|
160
|
+
end
|
161
|
+
|
162
|
+
# Sets +script+ as active.
|
163
|
+
def set_active(script)
|
164
|
+
send_command('SETACTIVE', sieve_name(script))
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns true if there is space on the server to store +script+ with
|
168
|
+
# size +size+ and false otherwise.
|
169
|
+
def have_space?(script, size)
|
170
|
+
begin
|
171
|
+
args = sieve_name(script) + ' ' + size.to_s
|
172
|
+
send_command('HAVESPACE', args)
|
173
|
+
return true
|
174
|
+
rescue SieveCommandError
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Returns true if the server supports TLS and false otherwise.
|
180
|
+
def supports_tls?
|
181
|
+
@supports_tls
|
182
|
+
end
|
183
|
+
|
184
|
+
# Disconnect from the server.
|
185
|
+
def logout
|
186
|
+
send_command('LOGOUT')
|
187
|
+
@socket.close
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def authenticate # :nodoc:
|
192
|
+
unless @login_mechs.include? @auth_mech
|
193
|
+
raise SieveAuthError, "Server doesn't allow #{@auth_mech} authentication"
|
194
|
+
end
|
195
|
+
case @auth_mech
|
196
|
+
when /PLAIN/i
|
197
|
+
auth_plain(@euser, @user, @password)
|
198
|
+
when /LOGIN/i
|
199
|
+
auth_login(@user, @password)
|
200
|
+
else
|
201
|
+
raise SieveAuthError, "#{@auth_mech} authentication is not implemented"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
def auth_plain(euser, user, pass) # :nodoc:
|
207
|
+
args = [ euser, user, pass ]
|
208
|
+
params = sieve_name('PLAIN') + ' '
|
209
|
+
params += sieve_name(encode64(args.join(0.chr)).gsub(/\n/, ''))
|
210
|
+
send_command('AUTHENTICATE', params)
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
def auth_login(user, pass) # :nodoc:
|
215
|
+
send_command('AUTHENTICATE', sieve_name('LOGIN'), false)
|
216
|
+
send_command(sieve_name(encode64(user)).gsub(/\n/, ''), nil, false)
|
217
|
+
send_command(sieve_name(encode64(pass)).gsub(/\n/, ''))
|
218
|
+
end
|
219
|
+
|
220
|
+
private
|
221
|
+
def server_features(lines) # :nodoc:
|
222
|
+
lines.each do |type, data|
|
223
|
+
case type
|
224
|
+
when 'IMPLEMENTATION'
|
225
|
+
@implementation = data
|
226
|
+
when 'SASL'
|
227
|
+
@login_mechs = data.split
|
228
|
+
when 'SIEVE'
|
229
|
+
@capabilities = data.split
|
230
|
+
when 'STARTTLS'
|
231
|
+
@supports_tls = true
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
private
|
237
|
+
def get_line # :nodoc:
|
238
|
+
return @socket.readline.chomp
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
def send_command(cmd, args=nil, wait_response=true) # :nodoc:
|
243
|
+
cmd += ' ' + args if args
|
244
|
+
begin
|
245
|
+
@socket.send(cmd + "\r\n", 0)
|
246
|
+
resp = get_response if wait_response
|
247
|
+
rescue SieveResponseError => e
|
248
|
+
raise SieveCommandError, "Command error: #{e}"
|
249
|
+
end
|
250
|
+
return resp
|
251
|
+
end
|
252
|
+
|
253
|
+
private
|
254
|
+
def parse_each_line # :nodoc:
|
255
|
+
loop do
|
256
|
+
data = get_line
|
257
|
+
|
258
|
+
# server response
|
259
|
+
m = /(OK|NO|BYE)( \((.*)\))?( (.*))?/.match(data)
|
260
|
+
yield :response, m.captures.values_at(0, 3) and next if m
|
261
|
+
|
262
|
+
# quoted text
|
263
|
+
m = /"([^"]*)"(\s"?([^"]*)"?)?$/.match(data)
|
264
|
+
yield :quoted, m.captures.values_at(0,2) and next if m
|
265
|
+
|
266
|
+
# literal
|
267
|
+
m = /\{(\d+)\+?\}/.match(data)
|
268
|
+
size = m.captures.first.to_i
|
269
|
+
yield :literal, @socket.read(size + 2) and next if m # + 2 for \r\n
|
270
|
+
|
271
|
+
# other
|
272
|
+
yield :other, data
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
def get_response # :nodoc:
|
278
|
+
response = []
|
279
|
+
parse_each_line do |flag, data|
|
280
|
+
case flag
|
281
|
+
when :response
|
282
|
+
type, error = data
|
283
|
+
raise SieveResponseError, error unless type == 'OK'
|
284
|
+
return response
|
285
|
+
else
|
286
|
+
response << data
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
private
|
292
|
+
def sieve_name(name) # :nodoc:
|
293
|
+
return "\"#{name}\""
|
294
|
+
end
|
295
|
+
|
296
|
+
private
|
297
|
+
def sieve_string(string) # :nodoc:
|
298
|
+
return "{#{string.length}+}\r\n#{string}"
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
metadata
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.8.1
|
3
|
+
specification_version: 1
|
4
|
+
name: ruby-managesieve
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.1.0
|
7
|
+
date: 2004-12-20
|
8
|
+
summary: A Ruby library for the MANAGESIEVE protocol
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
author: Andre Nathan
|
12
|
+
email: andre@digirati.com.br
|
13
|
+
homepage: http://managesieve.rubyforge.org
|
14
|
+
rubyforge_project: ruby-managesieve
|
15
|
+
description: "ruby-managesieve is a pure-ruby implementation of the MANAGESIEVE protocol, allowing remote management of Sieve scripts from ruby."
|
16
|
+
autorequire: managesieve
|
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
|
+
files:
|
29
|
+
- lib/managesieve.rb
|
30
|
+
test_files: []
|
31
|
+
rdoc_options: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
executables:
|
34
|
+
- sievectl
|
35
|
+
extensions: []
|
36
|
+
requirements:
|
37
|
+
- A network connection and a MANAGESIEVE server.
|
38
|
+
dependencies: []
|