catflap 0.0.2 → 1.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.
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