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.
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: []