ruby-managesieve 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/bin/sievectl +137 -0
  2. data/lib/managesieve.rb +301 -0
  3. 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
@@ -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: []