zimbra_wall 0.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5ff4ea8a8bac60edb0c0844ea3f9b193b9fc788f
4
+ data.tar.gz: 348cf17d109ecb33bfb164cf0be7bb74822ff48d
5
+ SHA512:
6
+ metadata.gz: 92421e69edb13bb1542f16486f6b2d45e83475fab0299f33bcdc0b98b70b5bae7297e27b6d3143c48275123fc85bc60048cd2ccfcddc4c74f5559bd60ca8902b
7
+ data.tar.gz: ce7808dcb42bcce1c7bb0c82b8ba07506fa45dd3ee0a89e9f1f17daa48ae405c340a608ca74e4c945ec7d071ccbd207c193dae2c2e2469bf206d1c7b1e1a2bf1
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/fixtures/*.org
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zimbra_intercepting_proxy.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Patricio Bruna
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,147 @@
1
+ # Zimbra Wall
2
+ A server for blocking users to access Zimbra CS.
3
+
4
+ This software is used to intercept and apply modifications to the traffic between a Zimbra Proxy and Zimbra Mailboxes.
5
+ If you don't know what a Zimbra Proxy is, You can read about it here: [https://wiki.zimbra.com/wiki/Zimbra_Proxy_Guide](https://wiki.zimbra.com/wiki/Zimbra_Proxy_Guide)
6
+
7
+ This work for all kind of client access:
8
+
9
+ * POP3
10
+ * IMAP
11
+ * Webmail
12
+ * ActiveSync
13
+ * Zimbra Outlook Connector
14
+
15
+ ## Use cases
16
+ You need to deny access to some users from Internet.
17
+
18
+
19
+ ### How it works
20
+ Zimbra Wall reads a map file, a `YAML` file, in which you indicate the pair `email:zimbraID` of the users you want to block
21
+
22
+ Based on this information, `Zimbra Wall` tell the Zimbra Proxy to deny access to the user.
23
+
24
+ ## Instalation and configuration
25
+
26
+ ### Requirements
27
+ This has been tested with:
28
+
29
+ * Zimbra >= 8.5
30
+ * Ruby >= 1.93
31
+ * Bundler >= 1.3
32
+ * Zimbra Proxy
33
+
34
+ **You need to have direct access to the `7072` port of both Mailboxes**.
35
+
36
+ ### Installation
37
+ It's recommended to install it on the same Zimbra Proxy server. All you need to do is run:
38
+
39
+ ```bash
40
+ $ gem install bundler
41
+ $ gem install zimbra_wall
42
+ ```
43
+
44
+ ### Zimbra Proxy Modification
45
+
46
+ **Important Note**
47
+ You are going to modify Zimbra template files, used to build the configuration files of Nginx. **Take some backups!!**
48
+
49
+ * All the files are located in `/opt/zimbra/conf/nginx/templates`.
50
+ * `<`, config being replaced
51
+ * `>`, new config
52
+
53
+ You have to make this modifications
54
+
55
+ ```diff
56
+ # nginx.conf.web.template
57
+ < ${web.login.upstream.disable} ${web.upstream.loginserver.:servers}
58
+ < ${web.login.upstream.disable} ${web.ssl.upstream.loginserver.:servers}
59
+ ---
60
+ >
61
+ > ${web.login.upstream.disable} server localhost:9292 fail_timeout=60s version=8.6.0_GA_1153;
62
+ > ${web.login.upstream.disable} server localhost:9292 fail_timeout=60s version=8.6.0_GA_1153;
63
+ ```
64
+
65
+ ```diff
66
+ # nginx.conf.zmlookup.template
67
+ < zm_lookup_handlers ${zmlookup.:handlers};
68
+ ---
69
+ > zm_lookup_handlers localhost:9292/service/extension/nginx-lookup;
70
+ ```
71
+
72
+ Next restart. You should restart memcached and nginx, but just to be sure:
73
+
74
+ ```bash
75
+ $ zmcontrol restart
76
+ ```
77
+
78
+ ### Starting Zimbra Wall
79
+
80
+ ```bash
81
+ $ bundle exec bin/zimbra_wall -d zboxapp.dev -f /tmp/users.yml -m 192.168.50.10 -a 0.0.0.0 -p 9292 --mailbox-port 8080
82
+ ```
83
+
84
+ #### Options
85
+
86
+ * `-d`, the domain, in case the user only enters the username,
87
+ * `-m`, One of the mailboxes,
88
+ * `-f`, the `YAML` map file, with the list of users on the `--newmailbox`,
89
+ * `-p`, the bind port
90
+ * `-a`, the bind address
91
+ * `--mailbox-port`, the mailbox port
92
+
93
+ #### The Map File
94
+
95
+ It's a simple YAML file with a `email:zimbraId` pair, like
96
+
97
+ ```yaml
98
+ max@example.com: "7b562c60-be97-0132-9a66-482a1423458f"
99
+ moliery@example.com: "7b562ce0-be97-0132-9a66-482a1423458f"
100
+ watson@example.com: "251b1902-2250-4477-bdd1-8a101f7e7e4e"
101
+ sherlock@example.com: "7b562dd0-be97-0132-9a66-482a1423458f"
102
+ ```
103
+
104
+ Updating the file does **not require** a restart.
105
+
106
+ You can get the `zimbraId` with:
107
+
108
+ ```
109
+ $ zmprov ga watson@example.com zimbraId
110
+ ```
111
+
112
+ ##### Error in Map File
113
+ If you have an error in your file, `Zimbra Wall` will return the on memory Map, this way we can keep the service up. In this event you should see this on `STDOUT`:
114
+
115
+ ```shel
116
+ ERROR Yaml File: (./test/fixtures/users.yml): could not find expected ':' while scanning a simple key at line 7
117
+ ```
118
+
119
+ ## Init scripts
120
+
121
+ In the `examples` directory you have the following files:
122
+
123
+ * `zimbra_wall`, to start the server on port 9292
124
+
125
+ Copy the file to the `/etc/init.d/` directory and then enable the services like this:
126
+
127
+ ```bash
128
+ $ chkconfig --add zimbra_wall
129
+ ```
130
+
131
+ ### Monit
132
+ It may be posible that this crash for some reason, it's a new software after all. To reduce the down time we recomend to use [Monit](http://mmonit.com/monit/) to monitor and restart `Zimbra Wall` in case of trouble.
133
+
134
+ Check the examples directory for config files.
135
+
136
+ ## Thanks
137
+
138
+ * To the Zimbra folks for a great product, and
139
+ * [@igrigorik](http://twitter.com/igrigorik) for [em-proxy](https://github.com/igrigorik/em-proxy)
140
+
141
+ ## Contributing
142
+
143
+ 1. Fork it ( https://github.com/pbruna/zimbra_intercepting_proxy/fork )
144
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
145
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
146
+ 4. Push to the branch (`git push origin my-new-feature`)
147
+ 5. Create a new Pull Request
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |task|
5
+ task.libs << %w(test lib)
6
+ task.pattern = 'test/test_*.rb'
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'zimbra_wall'
5
+ require 'optparse'
6
+
7
+ # host, port
8
+ ARGV << '-h' if ARGV.empty?
9
+
10
+ options = {}
11
+
12
+ optparse = OptionParser.new do |opts|
13
+
14
+ opts.banner = "Usage: zimbra_wall [options]"
15
+
16
+ opts.on("-dDOMAIN", "--domain=DOMAIN", "Email domain of the mailboxes") do |o|
17
+ options[:domain] = o
18
+ end
19
+
20
+ opts.on("-fFILE", "--usersfile=FILE", "YAML file with the list of blocked users") do |o|
21
+ options[:blocked_users_file] = o
22
+ end
23
+
24
+ opts.on("-mSERVER", "--mailbox=SERVER", "Hostname or IP of the mailbox to where we are migrating") do |o|
25
+ options[:backend] = o
26
+ end
27
+
28
+ opts.on("-zSERVERPORT", "--mailbox-port=SERVERPORT", "Hostname or IP of the mailbox to where we are migrating") do |o|
29
+ options[:backend_port] = o
30
+ end
31
+
32
+ opts.on("-aADDRESS", "--bindaddress=ADDRESS", "The IP Address to bind to. Default 0.0.0.0") do |o|
33
+ options[:bind_address] = o
34
+ end
35
+
36
+ opts.on("-pPORT", "--bindport=PORT", "The Port to bind to. Default 0.0.0.0") do |o|
37
+ options[:bind_port] = o
38
+ end
39
+
40
+ opts.on("-v", "--verbose", "Save logs information to /var/log/zimbra_wall.log") do |o|
41
+ options[:debug] = true
42
+ end
43
+
44
+ opts.on("-h", "--help", "Prints this help") do
45
+ puts opts
46
+ exit
47
+ end
48
+
49
+ end
50
+
51
+ optparse.parse!
52
+ ZimbraWall.start options
@@ -0,0 +1,5 @@
1
+ check process zimbra_wall with pidfile /var/run/zimbra_wall.pid
2
+ start program = "/etc/init.d/zimbra_wall start"
3
+ stop program = "/etc/init.d/zimbra_wall stop"
4
+ if failed host 127.0.0.1 port 9292 then restart
5
+ alert email@example.com
@@ -0,0 +1,91 @@
1
+ #!/bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides:
4
+ # Default-Start: 2 3 4 5
5
+ # Default-Stop: 0 1 6
6
+ # Short-Description: Start daemon at boot time
7
+ # Description: Enable service provided by daemon.
8
+ ### END INIT INFO
9
+
10
+ dir=""
11
+ user=""
12
+ cmd="zimbra_wall -d zboxapp.dev -f /tmp/users.yml -m 192.168.50.10 -a 0.0.0.0 -p 9292 --mailbox-port 8080"
13
+
14
+ name=`basename $0`
15
+ pid_file="/var/run/$name.pid"
16
+ stdout_log="/var/log/$name.log"
17
+ stderr_log="/var/log/$name.err"
18
+
19
+ get_pid() {
20
+ cat "$pid_file"
21
+ }
22
+
23
+ is_running() {
24
+ [ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
25
+ }
26
+
27
+ case "$1" in
28
+ start)
29
+ if is_running; then
30
+ echo "Already started"
31
+ else
32
+ echo "Starting $name"
33
+ $cmd >> "$stdout_log" 2>> "$stderr_log" &
34
+ echo $! > "$pid_file"
35
+ if ! is_running; then
36
+ echo "Unable to start, see $stdout_log and $stderr_log"
37
+ exit 1
38
+ fi
39
+ fi
40
+ ;;
41
+ stop)
42
+ if is_running; then
43
+ echo -n "Stopping $name.."
44
+ kill `get_pid`
45
+ for i in {1..10}
46
+ do
47
+ if ! is_running; then
48
+ break
49
+ fi
50
+
51
+ echo -n "."
52
+ sleep 1
53
+ done
54
+ echo
55
+
56
+ if is_running; then
57
+ echo "Not stopped; may still be shutting down or shutdown may have failed"
58
+ exit 1
59
+ else
60
+ echo "Stopped"
61
+ if [ -f "$pid_file" ]; then
62
+ rm "$pid_file"
63
+ fi
64
+ fi
65
+ else
66
+ echo "Not running"
67
+ fi
68
+ ;;
69
+ restart)
70
+ $0 stop
71
+ if is_running; then
72
+ echo "Unable to stop, will not attempt to start"
73
+ exit 1
74
+ fi
75
+ $0 start
76
+ ;;
77
+ status)
78
+ if is_running; then
79
+ echo "Running"
80
+ else
81
+ echo "Stopped"
82
+ exit 1
83
+ fi
84
+ ;;
85
+ *)
86
+ echo "Usage: $0 {start|stop|restart|status}"
87
+ exit 1
88
+ ;;
89
+ esac
90
+
91
+ exit 0
@@ -0,0 +1,36 @@
1
+ require "yaml"
2
+ require "uuid"
3
+ require 'em-proxy'
4
+ require 'http/parser'
5
+ require "addressable/uri"
6
+ require 'logger'
7
+ require 'cgi'
8
+ require 'xmlsimple'
9
+ require "zimbra_wall/version"
10
+ require "zimbra_wall/user"
11
+ require "zimbra_wall/config"
12
+ require "zimbra_wall/backend"
13
+ require "zimbra_wall/request"
14
+ require "zimbra_wall/server"
15
+ require "zimbra_wall/debug"
16
+ require "zimbra_wall/connection"
17
+ require "zimbra_wall/yamler"
18
+
19
+ module ZimbraWall
20
+
21
+ def self.start(options)
22
+ config!(options)
23
+ ZimbraWall::Server.run
24
+ end
25
+
26
+ def self.config!(options)
27
+ ZimbraWall::Config.domain = options[:domain]
28
+ ZimbraWall::Config.blocked_users_file = options[:blocked_users_file]
29
+ ZimbraWall::Config.backend = options[:backend]
30
+ ZimbraWall::Config.backend_port = options[:backend_port]
31
+ ZimbraWall::Config.bind_address = options[:bind_address] || "0.0.0.0"
32
+ ZimbraWall::Config.bind_port = options[:bind_port]
33
+ ZimbraWall::Config.debug = options[:debug]
34
+ end
35
+
36
+ end
@@ -0,0 +1,10 @@
1
+ module ZimbraWall
2
+ module Backend
3
+
4
+ def self.for_user(user)
5
+ return ZimbraWall::Config.new_backend if user.migrated?
6
+ ZimbraWall::Config.old_backend
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module ZimbraWall
2
+ module Config
3
+
4
+ ROUTE_URL = "/service/extension/nginx-lookup"
5
+ ROUTE_REQUEST_PORT = 7072
6
+ AUTH_REQUEST_PORT = 80
7
+
8
+ def self.domain=(domain)
9
+ @domain = domain
10
+ end
11
+
12
+ def self.domain
13
+ @domain
14
+ end
15
+
16
+ def self.blocked_users_file=(file)
17
+ @blocked_users_file = file
18
+ end
19
+
20
+ def self.blocked_users_file
21
+ @blocked_users_file
22
+ end
23
+
24
+ def self.backend=(backend)
25
+ @backend = backend
26
+ end
27
+
28
+ def self.backend
29
+ @backend
30
+ end
31
+
32
+ def self.backend_port=(port)
33
+ @backend_port = port
34
+ end
35
+
36
+ def self.backend_port
37
+ @backend_port
38
+ end
39
+
40
+ def self.bind_port=(bind_port)
41
+ @bind_port = bind_port
42
+ end
43
+
44
+ def self.bind_port
45
+ @bind_port
46
+ end
47
+
48
+ def self.bind_address=(bind_address)
49
+ @bind_address = bind_address
50
+ end
51
+
52
+ def self.bind_address
53
+ @bind_address
54
+ end
55
+
56
+ def self.debug=(debug)
57
+ @debug = debug
58
+ end
59
+
60
+ def self.debug
61
+ @debug
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,26 @@
1
+ module ZimbraWall
2
+ class Connection
3
+
4
+ attr_accessor :headers, :body, :started, :done, :buffer
5
+
6
+ def initialize
7
+ @headers = nil
8
+ @body = ""
9
+ @started = false
10
+ @done = false
11
+ @buffer = ''
12
+ end
13
+
14
+ def self.parse_lookup_response(resp)
15
+ array = resp.split("\r\n")
16
+ array.push("")
17
+ hash = {}
18
+ array[1..-1].each do |h|
19
+ tmp = h.split(/: /)
20
+ hash[tmp[0]] = tmp[1]
21
+ end
22
+ hash
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ module ZimbraWall
2
+ module Debug
3
+
4
+ require 'logger'
5
+
6
+ def self.logger(data)
7
+ return unless ZimbraWall::Config.debug
8
+ return if data.nil?
9
+ logger = Logger.new(STDOUT)
10
+ logger.info { data }
11
+ end
12
+
13
+ end
14
+ end
15
+
@@ -0,0 +1,75 @@
1
+ module ZimbraWall
2
+ class Request
3
+ require 'pp'
4
+
5
+ attr_accessor :body, :headers, :parser
6
+
7
+ def initialize(connection, parser = nil)
8
+ @body = connection.body
9
+ @headers = connection.headers
10
+ @parser = parser
11
+ end
12
+
13
+ def auth_request?
14
+ default_auth_request? || zco_auth_request?
15
+ end
16
+
17
+ # This is the post Auth request sent by Webmail, ActiveSync and POP and IMAP
18
+ def default_auth_request?
19
+ @parser.http_method == 'POST' && @parser.request_url == '/' && @headers["Cookie"] = "ZM_TEST=true" && auth_request_params?
20
+ end
21
+
22
+ def zco_auth_request?
23
+ @parser.http_method == 'POST' && @parser.request_url == "/service/soap/AuthRequest" && @headers["User-Agent"].match(/Zimbra-ZCO/)
24
+ end
25
+
26
+
27
+ def blank_password_from_body
28
+ params = CGI::parse(@body)
29
+ params['password'] = ['']
30
+ URI.encode_www_form params
31
+ end
32
+
33
+ def auth_request_params?
34
+ uri = Addressable::URI.parse("#{@headers['Origin']}/?#{@body}")
35
+ uri.query_values["loginOp"] == "login"
36
+ end
37
+
38
+ def modify_query_field(field, value)
39
+ uri = Addressable::URI.parse("#{@headers['Referer']}?#{@body}")
40
+ uri.query_values[field] = value
41
+ uri.query_values
42
+ end
43
+
44
+ def route_request?
45
+ @parser.request_url == ZimbraWall::Config::ROUTE_URL
46
+ end
47
+
48
+ def port
49
+ return ZimbraWall::Config::ROUTE_REQUEST_PORT if route_request?
50
+ return ZimbraWall::Config::AUTH_REQUEST_PORT if auth_request?
51
+ end
52
+
53
+ def user_token
54
+ return auth_username if default_auth_request?
55
+ return auth_zimbraId if route_request?
56
+ return auth_zco_username if zco_auth_request?
57
+ end
58
+
59
+ def auth_zimbraId
60
+ headers["Auth-User"]
61
+ end
62
+
63
+ def auth_zco_username
64
+ # Hold on, we have to dig deeper
65
+ xml = XmlSimple.xml_in @body
66
+ xml["Body"].first["AuthRequest"].first["account"].first["content"]
67
+ end
68
+
69
+ def auth_username
70
+ uri = Addressable::URI.parse("http://localhost/?#{@body}")
71
+ uri.query_values["username"]
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,72 @@
1
+ module ZimbraWall
2
+
3
+ module Server
4
+ require 'pp'
5
+
6
+
7
+ def run
8
+ debug = ZimbraWall::Debug
9
+ host = ZimbraWall::Config.bind_address
10
+ port = ZimbraWall::Config.bind_port
11
+
12
+ Proxy.start(host: host, port: port) do |conn|
13
+ debug.logger "Running on #{host}:#{port} - V. #{ZimbraWall::VERSION}"
14
+
15
+ @backend = { host: ZimbraWall::Config.backend, port: '7072' }
16
+ connection = ZimbraWall::Connection.new
17
+
18
+ @parser = Http::Parser.new
19
+ @parser.on_message_begin = proc { connection.started = true }
20
+ @parser.on_headers_complete = proc { |e| connection.headers = e }
21
+ @parser.on_body = proc { |chunk| connection.body << chunk }
22
+ @parser.on_message_complete = proc do
23
+ request = ZimbraWall::Request.new(connection, @parser)
24
+ if request.auth_request?
25
+ @backend[:port] = ZimbraWall::Config.backend_port
26
+ user = User.new(request.user_token)
27
+ if user.blocked?
28
+ connection.buffer.gsub!(/(?<=&password=).*(?=(&|$))/, '')
29
+ puts "User blocked: #{user.email} - #{user.zimbraId}"
30
+ end
31
+ conn.server @backend[:host], host: @backend[:host], port: @backend[:port]
32
+ conn.relay_to_servers connection.buffer
33
+ end
34
+ end
35
+
36
+ conn.server @backend[:host], host: @backend[:host], port: @backend[:port]
37
+ conn.relay_to_servers connection.buffer
38
+ connection.buffer.clear
39
+
40
+ conn.on_connect do |data, b|
41
+ debug.logger [:on_connect, data, b]
42
+ end
43
+
44
+ conn.on_data do |data|
45
+ debug.logger [:on_data, data]
46
+ connection.buffer << data
47
+ @parser << data if data !~ /\/service\/extension\/nginx-lookup/
48
+ data
49
+ end
50
+
51
+ conn.on_response do |backend, resp|
52
+ debug.logger [:on_response, backend, resp]
53
+ if resp =~ /Auth-Status/i
54
+ lookup_resp = ZimbraWall::Connection.parse_lookup_response(resp)
55
+ user = User.new(lookup_resp['Auth-User'])
56
+ if user.blocked?
57
+ puts "User blocked: #{user.email} - #{user.zimbraId}"
58
+ resp.gsub!(/Auth-Status: OK/, 'Auth-Status: ERR')
59
+ end
60
+ end
61
+ resp
62
+ end
63
+
64
+ conn.on_finish do |_, name|
65
+ debug.logger [:on_finish, name].inspect
66
+ end
67
+ end
68
+ end
69
+
70
+ module_function :run
71
+ end
72
+ end
@@ -0,0 +1,67 @@
1
+ module ZimbraWall
2
+
3
+ class User
4
+ attr_accessor :email, :zimbraId
5
+
6
+ @@db = {}
7
+
8
+ # user_identifier can be an email address, zimbraId UUID or just the
9
+ # local part of an email address, like user in user@example.com
10
+ def initialize(user_identifier)
11
+ @zimbraId = set_zimbraId user_identifier
12
+ @email = set_email user_identifier
13
+ User.load_migrated_users
14
+ end
15
+
16
+ # If user has email (unless email.nil?)
17
+ def blocked?
18
+ !find_in_db.nil?
19
+ end
20
+
21
+ def backend
22
+ ZimbraWall::Config.backend
23
+ end
24
+
25
+ def find_in_db
26
+ self.zimbraId = User.DB[email] if has_email?
27
+ self.email = User.DB.invert[zimbraId] if has_zimbraId?
28
+ User.DB[email] || User.DB.invert[zimbraId]
29
+ end
30
+
31
+ def has_email?
32
+ !email.nil?
33
+ end
34
+
35
+ def has_zimbraId?
36
+ !zimbraId.nil?
37
+ end
38
+
39
+ # Return the old DB if the YAML file has error
40
+ def self.load_migrated_users
41
+ data = ZimbraWall::Yamler.db
42
+ return @@db unless data
43
+ @@db = data
44
+ end
45
+
46
+ def self.DB
47
+ load_migrated_users
48
+ @@db
49
+ end
50
+
51
+ private
52
+ def set_zimbraId user_identifier
53
+ return user_identifier if UUID.validate user_identifier
54
+ nil
55
+ end
56
+
57
+ def set_email user_identifier
58
+ return nil if user_identifier.nil?
59
+ return user_identifier if user_identifier.match(/@/)
60
+ return "#{user_identifier}@#{ZimbraWall::Config.domain}" unless UUID.validate user_identifier
61
+ nil
62
+ end
63
+
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,3 @@
1
+ module ZimbraWall
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,18 @@
1
+ module ZimbraWall
2
+
3
+ module Yamler
4
+ require 'pp'
5
+
6
+ def self.db
7
+ begin
8
+ YAML.load_file ZimbraWall::Config.blocked_users_file
9
+ rescue Psych::SyntaxError => e
10
+ puts "ERROR Yaml File: #{e}"
11
+ return false
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,14 @@
1
+ POST /zimbra/ HTTP/1.0
2
+ X-Forwarded-For: 186.67.32.114
3
+ Host: 200.91.20.185
4
+ Connection: close
5
+ Content-Type: application/x-www-form-urlencoded
6
+ Origin: http://190.196.208.85
7
+ Cookie: ZM_TEST=true; ZM_TEST=true
8
+ Content-Length: 66
9
+ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
10
+ User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/600.5.17 (KHTML, like Gecko) Version/8.0.5 Safari/600.5.17
11
+ Accept-Language: es-es
12
+ Accept-Encoding: gzip, deflate
13
+
14
+ loginOp=login&username=pbruna&password=5693537374&client=preferred
@@ -0,0 +1,9 @@
1
+ GET /service/extension/nginx-lookup HTTP/1.0
2
+ Host: 127.0.0.1
3
+ Auth-Method: zimbraId
4
+ Auth-User: 251b1902-2250-4477-bdd1-8a101f7e7e4e
5
+ Auth-Pass: XXX
6
+ Auth-Salt: SSS
7
+ Auth-Protocol: http
8
+ Auth-Login-Attempt: 0
9
+ X-Proxy-Host: 200.91.20.186
@@ -0,0 +1,6 @@
1
+ pato@example.com: "7b562ae0-be97-0132-9a66-482a1423458f"
2
+ max@example.com: "7b562c60-be97-0132-9a66-482a1423458f"
3
+ moliery@example.com: "7b562ce0-be97-0132-9a66-482a1423458f"
4
+ watson@example.com: "251b1902-2250-4477-bdd1-8a101f7e7e4e"
5
+ sherlock@example.com: "7b562dd0-be97-0132-9a66-482a1423458f"
6
+ pbruna@itlinux.cl: "cfd6e914-4f00-440c-9a57-e1a9327128b9"
@@ -0,0 +1 @@
1
+ <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><soap:Header><context xmlns="urn:zimbra"><userAgent name="ZimbraConnectorForOutlook" version="8.5.1.1259"/><nonotify/><noqualify/></context></soap:Header><soap:Body><AuthRequest xmlns="urn:zimbraAccount"><account by="name">watson@example.com</account><password>watson</password><virtualHost>200.91.20.186</virtualHost></AuthRequest></soap:Body></soap:Envelope>
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ class Backend < Minitest::Test
4
+
5
+ def setup
6
+ @user_migrated_email = ZimbraWall::User.new("watson@example.com")
7
+ @user_migrated_zimbraId = ZimbraWall::User.new("251b1902-2250-4477-bdd1-8a101f7e7e4e")
8
+ @user_not_migrated_email = ZimbraWall::User.new("jackbower@example.com")
9
+ @user_not_migrated_zimbraId = ZimbraWall::User.new("251b1902-2250-4477-bdd1-8a101f7e7333")
10
+ end
11
+
12
+ def test_should_return_old_backend_if_user_email_is_not_migrated
13
+ backend = ZimbraWall::Backend.for_user @user_not_migrated_email
14
+ assert_equal(ZimbraWall::Config.old_backend, backend)
15
+ end
16
+
17
+ def test_should_return_old_backend_if_user_zimbraID_is_not_migrated
18
+ backend = ZimbraWall::Backend.for_user @user_not_migrated_zimbraId
19
+ assert_equal(ZimbraWall::Config.old_backend, backend)
20
+ end
21
+
22
+ def test_should_return_new_backend_if_user_email_was_migrated
23
+ backend = ZimbraWall::Backend.for_user @user_migrated_email
24
+ assert_equal(ZimbraWall::Config.new_backend, backend)
25
+ end
26
+
27
+ def test_should_return_new_backend_if_user_zimbraID_was_migrated
28
+ backend = ZimbraWall::Backend.for_user @user_migrated_zimbraId
29
+ assert_equal(ZimbraWall::Config.new_backend, backend)
30
+ end
31
+
32
+ end
@@ -0,0 +1,29 @@
1
+ require "zimbra_intercepting_proxy"
2
+ require 'ostruct'
3
+
4
+ require 'minitest/autorun'
5
+ require 'minitest/reporters' # requires the gem
6
+
7
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # spec-like progress
8
+
9
+ ZimbraWall::Config.domain="example.com"
10
+ ZimbraWall::Config.blocked_users_file="./test/fixtures/users.yml"
11
+ ZimbraWall::Config.old_backend = "old-mailbox.example.com"
12
+ ZimbraWall::Config.new_backend = "new-mailbox.zboxapp.com"
13
+
14
+ def add_error_line
15
+ f = File.open ZimbraWall::Config.blocked_users_file, "a"
16
+ f.puts "juan :94949494-9494949-040404"
17
+ f.close
18
+ end
19
+
20
+ def restore_map_file
21
+ backup_file = "./test/fixtures/users.yml.org"
22
+ FileUtils.cp(backup_file, ZimbraWall::Config.blocked_users_file)
23
+ FileUtils.rm "./test/fixtures/users.yml.org"
24
+ end
25
+
26
+ def backup_map_file
27
+ backup_file = "./test/fixtures/users.yml.org"
28
+ FileUtils.cp(ZimbraWall::Config.blocked_users_file, backup_file)
29
+ end
@@ -0,0 +1,39 @@
1
+ require 'test_helper'
2
+
3
+ class Request < Minitest::Test
4
+
5
+ def setup
6
+ @done = false
7
+ @auth_request = IO.read("./test/fixtures/auth_80.txt")
8
+ @route_request = IO.read("./test/fixtures/route_7072.txt")
9
+ @soap_auth_request = IO.read("./test/fixtures/zco_soap.xml")
10
+ @connection = OpenStruct.new
11
+ @parser = OpenStruct.new
12
+ end
13
+
14
+ def test_should_return_user_token_from_auth_request
15
+ @parser.http_method = "POST"
16
+ @parser.request_url = "/zimbra/"
17
+ @connection.headers = {"Cookie" => "ZM_TEST=true"}
18
+ @connection.body = "loginOp=login&username=pbruna&password=5693537374&client=preferred"
19
+ request = ZimbraWall::Request.new @connection, @parser
20
+ assert_equal("pbruna", request.user_token)
21
+ end
22
+
23
+ def test_should_return_user_token_from_route_request
24
+ @parser.request_url = ZimbraWall::Config::ROUTE_URL
25
+ @connection.headers = {"Auth-User" => "251b1902-2250-4477-bdd1-8a101f7e7e4e"}
26
+ request = ZimbraWall::Request.new @connection, @parser
27
+ assert_equal("251b1902-2250-4477-bdd1-8a101f7e7e4e", request.user_token)
28
+ end
29
+
30
+ def test_should_get_username_from_zco_auth_request
31
+ @parser.http_method = "POST"
32
+ @parser.request_url = "/service/soap/AuthRequest"
33
+ @connection.headers = {"User-Agent" => "Zimbra-ZCO"}
34
+ @connection.body = @soap_auth_request
35
+ request = ZimbraWall::Request.new @connection, @parser
36
+ assert_equal("watson@example.com", request.user_token)
37
+ end
38
+
39
+ end
@@ -0,0 +1,71 @@
1
+ require 'test_helper'
2
+
3
+ class User < Minitest::Test
4
+
5
+ def setup
6
+ @user = {email: "watson@example.com", zimbraId: "251b1902-2250-4477-bdd1-8a101f7e7e4e"}
7
+ end
8
+
9
+ def test_only_set_zimbraId_when_passed_a_zimbraId
10
+ u = ZimbraWall::User.new(@user[:zimbraId])
11
+ assert_equal(u.zimbraId, @user[:zimbraId])
12
+ assert_nil(u.email)
13
+ end
14
+
15
+ def test_only_set_email_when_passed_an_email
16
+ u = ZimbraWall::User.new(@user[:email])
17
+ assert_equal(u.email, @user[:email])
18
+ assert_nil(u.zimbraId)
19
+ end
20
+
21
+ def test_return_email_when_passed_just_an_username
22
+ u = ZimbraWall::User.new("watson")
23
+ assert_equal(u.email, @user[:email])
24
+ assert_nil(u.zimbraId)
25
+ end
26
+
27
+ def test_should_load_db_user_file_to_global_hash
28
+ assert_equal(ZimbraWall::User.DB.class, Hash)
29
+ end
30
+
31
+ def test_db_should_have_emails_when_email_is_passed
32
+ assert(ZimbraWall::User.DB["watson@example.com"])
33
+ end
34
+
35
+ def test_return_true_if_migrated_user_with_zimbraId
36
+ u = ZimbraWall::User.new("7b562ce0-be97-0132-9a66-482a1423458f")
37
+ assert(u.migrated?, "Failure message.")
38
+ end
39
+
40
+ def test_return_false_if_not_migrated_user_with_zimbraId
41
+ u = ZimbraWall::User.new("7b562ce0-be97-0132-9a66-482a14234333")
42
+ assert(!u.migrated?, "Failure message.")
43
+ end
44
+
45
+ def test_return_true_if_migrated_user_with_email
46
+ u = ZimbraWall::User.new(@user[:email])
47
+ assert(u.migrated?, "Failure message.")
48
+ end
49
+
50
+ def test_return_false_if_no_migrated_user_with_email
51
+ u = ZimbraWall::User.new("pbruna")
52
+ assert(!u.migrated?, "Failure message.")
53
+ end
54
+
55
+ def test_migrated_false_if_email_nil
56
+ u = ZimbraWall::User.new(nil)
57
+ assert(!u.migrated?, "Failure message.")
58
+ end
59
+
60
+
61
+ def test_return_old_db_if_yamler_db_is_false
62
+ backup_map_file # backup users.yml
63
+ yaml_1 = ZimbraWall::User.DB.inspect
64
+ add_error_line
65
+ yaml_2 = ZimbraWall::User.DB.inspect
66
+ restore_map_file
67
+ assert_equal(Digest::MD5.digest(yaml_1), Digest::MD5.digest(yaml_2))
68
+ end
69
+
70
+
71
+ end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+ require 'digest'
3
+
4
+ class Yamler < Minitest::Test
5
+
6
+ def setup
7
+ backup_map_file
8
+ end
9
+
10
+ def teardown
11
+ restore_map_file
12
+ end
13
+
14
+ def test_yamler_db_must_return_the_yaml_file_hash
15
+ db = ZimbraWall::Yamler.db
16
+ assert_equal("251b1902-2250-4477-bdd1-8a101f7e7e4e", db["watson@example.com"])
17
+ end
18
+
19
+ def test_return_false_if_updated_files_has_error
20
+ add_error_line
21
+ assert(!ZimbraWall::Yamler.db, "Failure message.")
22
+ end
23
+
24
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zimbra_wall/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zimbra_wall"
8
+ spec.version = ZimbraWall::VERSION
9
+ spec.authors = ["Patricio Bruna"]
10
+ spec.email = ["pbruna@itlinux.cl"]
11
+ spec.summary = "An authorization wall for Zimbra"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/pbruna/zimbra_wall"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'em-proxy', "~> 0.1.8"
22
+ spec.add_dependency 'thor', "~> 0.19"
23
+ spec.add_dependency 'uuid', "~> 2.3"
24
+ spec.add_dependency 'http_parser.rb', "~> 0.6"
25
+ spec.add_dependency 'addressable', "~> 2.3"
26
+ spec.add_dependency 'xml-simple', "~> 1.1"
27
+ spec.add_development_dependency "bundler"
28
+
29
+
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "minitest-reporters"
32
+ end
metadata ADDED
@@ -0,0 +1,208 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zimbra_wall
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Patricio Bruna
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: em-proxy
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.8
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.8
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.19'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.19'
41
+ - !ruby/object:Gem::Dependency
42
+ name: uuid
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: http_parser.rb
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: addressable
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.3'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.3'
83
+ - !ruby/object:Gem::Dependency
84
+ name: xml-simple
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.1'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: bundler
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest-reporters
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description: An authorization wall for Zimbra
140
+ email:
141
+ - pbruna@itlinux.cl
142
+ executables:
143
+ - zimbra_wall
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - ".gitignore"
148
+ - Gemfile
149
+ - LICENSE.txt
150
+ - README.md
151
+ - Rakefile
152
+ - bin/zimbra_wall
153
+ - examples/monit_zimbra_wall
154
+ - examples/zimbra_wall
155
+ - lib/zimbra_wall.rb
156
+ - lib/zimbra_wall/backend.rb
157
+ - lib/zimbra_wall/config.rb
158
+ - lib/zimbra_wall/connection.rb
159
+ - lib/zimbra_wall/debug.rb
160
+ - lib/zimbra_wall/request.rb
161
+ - lib/zimbra_wall/server.rb
162
+ - lib/zimbra_wall/user.rb
163
+ - lib/zimbra_wall/version.rb
164
+ - lib/zimbra_wall/yamler.rb
165
+ - test/fixtures/auth_80.txt
166
+ - test/fixtures/route_7072.txt
167
+ - test/fixtures/users.yml
168
+ - test/fixtures/zco_soap.xml
169
+ - test/test_backend.rb
170
+ - test/test_helper.rb
171
+ - test/test_request.rb
172
+ - test/test_user.rb
173
+ - test/test_yamler.rb
174
+ - zimbra_wall.gemspec
175
+ homepage: https://github.com/pbruna/zimbra_wall
176
+ licenses:
177
+ - MIT
178
+ metadata: {}
179
+ post_install_message:
180
+ rdoc_options: []
181
+ require_paths:
182
+ - lib
183
+ required_ruby_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ requirements: []
194
+ rubyforge_project:
195
+ rubygems_version: 2.5.1
196
+ signing_key:
197
+ specification_version: 4
198
+ summary: An authorization wall for Zimbra
199
+ test_files:
200
+ - test/fixtures/auth_80.txt
201
+ - test/fixtures/route_7072.txt
202
+ - test/fixtures/users.yml
203
+ - test/fixtures/zco_soap.xml
204
+ - test/test_backend.rb
205
+ - test/test_helper.rb
206
+ - test/test_request.rb
207
+ - test/test_user.rb
208
+ - test/test_yamler.rb