catflap 0.0.2 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>
6
+ <script src="/js/sha256.js"></script>
7
+ <script src="/js/catflap.js"></script>
8
+ <link rel="stylesheet" href="/css/catflap.css" />
9
+ <title>Catflap | No dogs allowed!</title>
10
+ </head>
11
+ <body id="home">
12
+ <div id="logo-wrapper"><img id="logo" class="centered" src="/images/catflap.png" /></div>
13
+ <div id="passphrase-form-wrapper" class="centered">
14
+ <input id="passphrase" name="passphrase" type="text" class="default-value" value="Enter your pass phrase here." />
15
+ <div id="message-wrapper" class="centered">
16
+ <div id="locked-message" class="message centered">The site you are trying to access is locked. You must enter a valid pass phrase
17
+ to enter the site.</div>
18
+ <div id="failed-message" class="message centered hidden">Sorry, but we don't recognize that pass phrase. Please try again and if you
19
+ are still having trouble email your contact for further instructions.</div>
20
+ </div>
21
+ </div>
22
+ </body>
23
+ </html>
@@ -0,0 +1,85 @@
1
+ (function($) { $(document).ready(function() {
2
+
3
+ // Hide default text
4
+ $('.default-value').each(function() {
5
+ var default_value = this.value;
6
+ $(this).focus(function() {
7
+ if(this.value === default_value) {
8
+ this.value = '';
9
+ }
10
+ });
11
+ $(this).blur(function() {
12
+ if(this.value === '') {
13
+ this.value = default_value;
14
+ }
15
+ });
16
+ });
17
+
18
+ // Bind token input field with 'enter' keypress.
19
+ $('input#passphrase').keypress(function(e) {
20
+ if(e.which == 13) {
21
+ var pass = $('input#passphrase').val();
22
+
23
+ // Get the first word to send as the key for the pass phrase.
24
+ // The first word is any part that comes before a special
25
+ // character (including spaces) other than the underscore.
26
+ var matches = pass.match(/^(\w+)\W+/);
27
+ // If there is nothing that looks like a key then don't make
28
+ // an authentication request.
29
+ if (matches === null) {
30
+ return;
31
+ }
32
+
33
+ // Handshake with the Catflap server by requesting a timestamp.
34
+ ts = 0;
35
+ $.ajax({
36
+ url: '/catflap/sync',
37
+ method: 'POST',
38
+ success: function(jsonData){
39
+ data = JSON.parse(jsonData);
40
+ ts = data.Timestamp;
41
+
42
+ // Construct our data packet to send to the server.
43
+ var data = {
44
+ "_key" : matches[1],
45
+ "ts" : ts
46
+ };
47
+ data.token = Sha256.hash(pass + ts);
48
+
49
+ $.ajax({
50
+ url: '/catflap/knock',
51
+ method: 'POST',
52
+ data: data,
53
+ success: function(jsonData){
54
+ data = JSON.parse(jsonData);
55
+
56
+ switch (data.StatusCode) {
57
+ case 200:
58
+ if (data.RedirectUrl == "reload") {
59
+ location.reload(true);
60
+ } else {
61
+ $(location).attr('href', data.RedirectUrl);
62
+ }
63
+ break;
64
+ default:
65
+ $('#passphrase').addClass('failed');
66
+ $('#locked-message').hide();
67
+ $('#failed-message').show();
68
+ break;
69
+ }
70
+ }
71
+ })
72
+ .fail(function(jsonData){
73
+ console.log('Unable to authenticate with the server: ' + jsonData);
74
+ });
75
+ }
76
+ })
77
+ .fail(function(jsonData){
78
+ console.log('Sync handshake failed: ' + jsonData);
79
+ return;
80
+ });
81
+
82
+ }
83
+ });
84
+ });
85
+ })(jQuery);
@@ -0,0 +1,166 @@
1
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2
+ /* SHA-256 implementation in JavaScript (c) Chris Veness 2002-2014 / MIT Licence */
3
+ /* */
4
+ /* - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html */
5
+ /* http://csrc.nist.gov/groups/ST/toolkit/examples.html */
6
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
7
+
8
+ /* jshint node:true *//* global define, escape, unescape */
9
+ 'use strict';
10
+
11
+
12
+ /**
13
+ * SHA-256 hash function reference implementation.
14
+ *
15
+ * @namespace
16
+ */
17
+ var Sha256 = {};
18
+
19
+
20
+ /**
21
+ * Generates SHA-256 hash of string.
22
+ *
23
+ * @param {string} msg - String to be hashed
24
+ * @returns {string} Hash of msg as hex character string
25
+ */
26
+ Sha256.hash = function(msg) {
27
+ // convert string to UTF-8, as SHA only deals with byte-streams
28
+ msg = msg.utf8Encode();
29
+
30
+ // constants [§4.2.2]
31
+ var K = [
32
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
33
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
34
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
35
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
36
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
37
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
38
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
39
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ];
40
+ // initial hash value [§5.3.1]
41
+ var H = [
42
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ];
43
+
44
+ // PREPROCESSING
45
+
46
+ msg += String.fromCharCode(0x80); // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
47
+
48
+ // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
49
+ var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + ‘1’ + appended length
50
+ var N = Math.ceil(l/16); // number of 16-integer-blocks required to hold 'l' ints
51
+ var M = new Array(N);
52
+
53
+ for (var i=0; i<N; i++) {
54
+ M[i] = new Array(16);
55
+ for (var j=0; j<16; j++) { // encode 4 chars per integer, big-endian encoding
56
+ M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
57
+ (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
58
+ } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
59
+ }
60
+ // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
61
+ // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
62
+ // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
63
+ M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
64
+ M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
65
+
66
+
67
+ // HASH COMPUTATION [§6.1.2]
68
+
69
+ var W = new Array(64); var a, b, c, d, e, f, g, h;
70
+ for (var i=0; i<N; i++) {
71
+
72
+ // 1 - prepare message schedule 'W'
73
+ for (var t=0; t<16; t++) W[t] = M[i][t];
74
+ for (var t=16; t<64; t++) W[t] = (Sha256.σ1(W[t-2]) + W[t-7] + Sha256.σ0(W[t-15]) + W[t-16]) & 0xffffffff;
75
+
76
+ // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
77
+ a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];
78
+
79
+ // 3 - main loop (note 'addition modulo 2^32')
80
+ for (var t=0; t<64; t++) {
81
+ var T1 = h + Sha256.Σ1(e) + Sha256.Ch(e, f, g) + K[t] + W[t];
82
+ var T2 = Sha256.Σ0(a) + Sha256.Maj(a, b, c);
83
+ h = g;
84
+ g = f;
85
+ f = e;
86
+ e = (d + T1) & 0xffffffff;
87
+ d = c;
88
+ c = b;
89
+ b = a;
90
+ a = (T1 + T2) & 0xffffffff;
91
+ }
92
+ // 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
93
+ H[0] = (H[0]+a) & 0xffffffff;
94
+ H[1] = (H[1]+b) & 0xffffffff;
95
+ H[2] = (H[2]+c) & 0xffffffff;
96
+ H[3] = (H[3]+d) & 0xffffffff;
97
+ H[4] = (H[4]+e) & 0xffffffff;
98
+ H[5] = (H[5]+f) & 0xffffffff;
99
+ H[6] = (H[6]+g) & 0xffffffff;
100
+ H[7] = (H[7]+h) & 0xffffffff;
101
+ }
102
+
103
+ return Sha256.toHexStr(H[0]) + Sha256.toHexStr(H[1]) + Sha256.toHexStr(H[2]) + Sha256.toHexStr(H[3]) +
104
+ Sha256.toHexStr(H[4]) + Sha256.toHexStr(H[5]) + Sha256.toHexStr(H[6]) + Sha256.toHexStr(H[7]);
105
+ };
106
+
107
+
108
+ /**
109
+ * Rotates right (circular right shift) value x by n positions [§3.2.4].
110
+ * @private
111
+ */
112
+ Sha256.ROTR = function(n, x) {
113
+ return (x >>> n) | (x << (32-n));
114
+ };
115
+
116
+ /**
117
+ * Logical functions [§4.1.2].
118
+ * @private
119
+ */
120
+ Sha256.Σ0 = function(x) { return Sha256.ROTR(2, x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); };
121
+ Sha256.Σ1 = function(x) { return Sha256.ROTR(6, x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); };
122
+ Sha256.σ0 = function(x) { return Sha256.ROTR(7, x) ^ Sha256.ROTR(18, x) ^ (x>>>3); };
123
+ Sha256.σ1 = function(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); };
124
+ Sha256.Ch = function(x, y, z) { return (x & y) ^ (~x & z); };
125
+ Sha256.Maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); };
126
+
127
+
128
+ /**
129
+ * Hexadecimal representation of a number.
130
+ * @private
131
+ */
132
+ Sha256.toHexStr = function(n) {
133
+ // note can't use toString(16) as it is implementation-dependant,
134
+ // and in IE returns signed numbers when used on full words
135
+ var s="", v;
136
+ for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
137
+ return s;
138
+ };
139
+
140
+
141
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
142
+
143
+
144
+ /** Extend String object with method to encode multi-byte string to utf8
145
+ * - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
146
+ if (typeof String.prototype.utf8Encode == 'undefined') {
147
+ String.prototype.utf8Encode = function() {
148
+ return unescape( encodeURIComponent( this ) );
149
+ };
150
+ }
151
+
152
+ /** Extend String object with method to decode utf8 string to multi-byte */
153
+ if (typeof String.prototype.utf8Decode == 'undefined') {
154
+ String.prototype.utf8Decode = function() {
155
+ try {
156
+ return decodeURIComponent( escape( this ) );
157
+ } catch (e) {
158
+ return this; // invalid UTF-8? return as-is
159
+ }
160
+ };
161
+ }
162
+
163
+
164
+ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
165
+ if (typeof module != 'undefined' && module.exports) module.exports = Sha256; // CommonJs export
166
+ if (typeof define == 'function' && define.amd) define([], function() { return Sha256; }); // AMD
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: catflap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 1.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,19 +9,116 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-08 00:00:00.000000000 Z
13
- dependencies: []
14
- description: A simple solution to provide on-demand service access (e.g. port 80 on
15
- webserver), where a more robust and secure VPN solution is not available.
16
- email: nyk@demotix.com
12
+ date: 2016-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.8.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.8.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: bundler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.11'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.11'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '10.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '3.0'
78
+ description: ! "A simple solution to provide on-demand service access (e.g.\n port
79
+ 80 on webserver), where a more robust and secure VPN solution is not\n available.
80
+ Essentially, it is a more user-friendly form of \"port knocking\".\n The original
81
+ proof-of-concept implementation was run for almost three years\n by Demotix, to
82
+ protect development and staging servers from search engine\n crawlers and other
83
+ unwanted traffic."
84
+ email: nykcowham@gmail.com
17
85
  executables:
18
86
  - catflap
19
87
  extensions: []
20
88
  extra_rdoc_files: []
21
89
  files:
22
- - lib/catflap.rb
23
- - lib/catflap-http.rb
90
+ - .gitignore
91
+ - .rspec
92
+ - .rubocop.yml
93
+ - .rubocop_todo.yml
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - Gemfile.lock
97
+ - LICENSE
98
+ - README.md
99
+ - Rakefile
24
100
  - bin/catflap
101
+ - bin/console
102
+ - bin/setup
103
+ - catflap.gemspec
104
+ - etc/config.yaml
105
+ - etc/init.d/catflap
106
+ - etc/passfile.yaml
107
+ - lib/catflap.rb
108
+ - lib/catflap/command.rb
109
+ - lib/catflap/firewall.rb
110
+ - lib/catflap/http.rb
111
+ - lib/catflap/netfilter/writer.rb
112
+ - lib/catflap/plugins/firewall/iptables.rb
113
+ - lib/catflap/plugins/firewall/netfilter.rb
114
+ - lib/catflap/plugins/firewall/plugin.rb
115
+ - lib/catflap/version.rb
116
+ - lib/netfilter/writer.rb
117
+ - ui/css/catflap.css
118
+ - ui/images/catflap.png
119
+ - ui/index.rhtml
120
+ - ui/js/catflap.js
121
+ - ui/js/sha256.js
25
122
  homepage: https://github.com/nyk/catflap
26
123
  licenses:
27
124
  - MIT
@@ -44,9 +141,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
44
141
  requirements:
45
142
  - NetFilters (iptables) installed and working.
46
143
  rubyforge_project:
47
- rubygems_version: 1.8.11
144
+ rubygems_version: 1.8.23
48
145
  signing_key:
49
146
  specification_version: 3
50
- summary: Manage NetFilter-based rules to grant port access on-demand via commandline
51
- or REST API requests.
147
+ summary: Manage NetFilter-based rules to grant port access on-demand commandline or
148
+ REST API requests.
52
149
  test_files: []
150
+ has_rdoc:
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'catflap'
4
- require 'webrick'
5
- include WEBrick
6
-
7
- module CatflapWebserver
8
-
9
- def self.generate_server port
10
- config = {:Port => port}
11
- server = HTTPServer.new(config)
12
- yield server if block_given?
13
- ['INT', 'TERM'].each {|signal|
14
- trap(signal) {server.shutdown}
15
- }
16
- server.start
17
- end
18
-
19
- def self.start_server cf
20
- generate_server cf.port do |server|
21
- server.mount '/', Servlet, cf
22
- end
23
- end
24
-
25
- class Servlet < HTTPServlet::AbstractServlet
26
-
27
- def initialize server, cf
28
- super server
29
- @cf = cf
30
- end
31
-
32
- def do_GET req, resp
33
- # Split the path into piece
34
- path = req.path[1..-1].split('/')
35
- raise HTTPStatus::OK if path[0] == 'favicon.ico'
36
- response_class = CatflapRestService.const_get 'Service'
37
-
38
- if response_class and response_class.is_a? Class
39
- # There was a method given
40
- if path[0]
41
- response_method = path[0].to_sym
42
- # Make sure the method exists in the class
43
- raise HTTPStatus::NotFound if !response_class.respond_to? response_method
44
-
45
- if path[0] == "enter"
46
- url = response_class.send response_method, req, resp, @cf
47
- resp.set_redirect HTTPStatus::Redirect, url
48
- end
49
-
50
- # Remaining path segments get passed in as arguments to the method
51
- if path.length > 1
52
- resp.body = response_class.send response_method, req, resp, @cf, path[1..-1]
53
- else
54
- resp.body = response_class.send response_method, req, resp, @cf
55
- end
56
- raise HTTPStatus::OK
57
-
58
- # No method was given, so check for an "index" method instead
59
- else
60
- raise HTTPStatus::NotFound if !response_class.respond_to? :index
61
- resp.body = response_class.send :index
62
- raise HTTPStatus::OK
63
- end
64
- else
65
- raise HTTPStatus::NotFound
66
- end
67
- end
68
- end
69
- end
70
-
71
- module CatflapRestService
72
- class Service
73
-
74
- def self.index
75
- return "hello world"
76
- end
77
-
78
- def self.enter req, resp, cf
79
- ip = req.peeraddr.pop
80
- host = req.addr[2]
81
- cf.add_address! ip unless cf.check_address ip
82
- return "http://" << host << ":80"
83
- end
84
-
85
- def self.add req, resp, cf, args
86
- ip = args[0]
87
- unless cf.check_address ip
88
- cf.add_address! ip
89
- return "#{ip} has been granted access"
90
- else
91
- return "#{ip} already has access"
92
- end
93
- end
94
-
95
- def self.remove req, resp, cf, args
96
- ip = args[0]
97
- cf.delete_address! ip
98
- return "Access granted to #{ip} has been removed"
99
- end
100
-
101
- def self.check req, resp, cf, args
102
- ip = args[0]
103
-
104
- if cf.check_address ip
105
- return "#{ip} has access to ports: #{cf.dports}"
106
- else
107
- return "#{ip} does not have access to ports: #{cf.dports}"
108
- end
109
- end
110
- end
111
- end