ipcam 0.3.5 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 364d7aea8083301b5c3bfd0ec5c26d8afa68ac6496433495da0e56812f4dafe0
4
- data.tar.gz: '09776c05b93354b60adaf89314d3c4f0ba1878ef83bd4cef1410cc72ba0639bb'
3
+ metadata.gz: a3646608decd74c82db0631040a7ddd1b4f94ae1feef0ae39272505a4b27e45f
4
+ data.tar.gz: bf43c0a17f8e99a083776aa4e983ab86525dc1cdf0f3949588549f54dd2662af
5
5
  SHA512:
6
- metadata.gz: 983cdf9103d28102f42f76f8b230b84c5ece86c41f94d01c78cce4a1d97461e7ff25c60eb344f508e107a338a3fedcd9eb880b11fe9712a121692580b33cb549
7
- data.tar.gz: eb89a8b0d1ad4e512f764e292c9fb61b237571bfc4bf4d785db335020704d58fe025ea0257f090911cd21fde6007c26423d74a9fe77f787bb2032ab326df33c0
6
+ metadata.gz: 56806547cb2de27720261e2c026337709e505f05ac8c73a9581f2d692a81af66c1358b4b99cc174e9035243e60aaaeecff87fb4b2ab080f5c6d75f8a1995536b
7
+ data.tar.gz: 15787dd7f41e5a406e50d8b9ffc4c6fc3802e677b1680a3aa5b9e4d29bb711c4af91495a28e2c6465aca9cc65f03fc4f6afc7ac90e69b7bf8a4d4642c1d1efb6
data/README.md CHANGED
@@ -22,6 +22,11 @@ Connect a camera device compatible with V4L2 and start ipcam as follows.
22
22
  ```
23
23
  ipcam [options] [device-file]
24
24
  options:
25
+ --use-ssl
26
+ --ssl-cert=CRT-FILE
27
+ --ssl-key=KEY-FILE
28
+ -D, --digest-auth=FILE
29
+ -A, --add-user=USER,PASSWD
25
30
  --bind=ADDR
26
31
  --port=PORT
27
32
  -d, --database-file=FILE
@@ -40,6 +45,21 @@ Then connect to port 4567 by http browser and operate. The accessible URLs are a
40
45
 
41
46
  ### options
42
47
  <dl>
48
+ <dt>--use-ssl</dt>
49
+ <dd>Specify use SSL/TLS. If you use this option, shall specify a certificate file and a key file (Use the --ssl-cert and --ssl-key options).</dd>
50
+
51
+ <dt>--ssl-cert=CRT-FILE</dt>
52
+ <dd>Specifies the file that contains the X 509 server certificate.</dd>
53
+
54
+ <dt>--ssl-key=KEY-FILE</dt>
55
+ <dd>Specifies the key file that was used when the certificate was created.</dd>
56
+
57
+ <dt>-D, --digest-auth=YAML-FILE</dt>
58
+ <dd>Specifies to use restrict access by Digest Authentication. This argument is followed by a password file written in YAML.</dd>
59
+
60
+ <dt>-A, --add-user=USER-NAME,PASSWORD</dt>
61
+ <dd>Add entry to the password file. If you specify this option, only to add an entry to the password file to exit this application.</dd>
62
+
43
63
  <dt>--bind=ADDR</dt>
44
64
  <dd>Specify the address to which the HTTP server binds. by default, IPv6 any address("::") is used.</dd>
45
65
 
@@ -62,6 +82,33 @@ Then connect to port 4567 by http browser and operate. The accessible URLs are a
62
82
  <dd></dd>
63
83
  </dl>
64
84
 
85
+ ### Use digest authentication
86
+ To restrict access by digest authentication, the password file written in YAML must be specified in the "--digest-auth" option. This file must be YAML-encoded map data of A1 strings (ref. RFC 7616) keyed by the user name.
87
+
88
+ This file can be created using the "--add-user" option. The actual procedure is as follows.
89
+
90
+ #### Create password file
91
+ ##### create password file, and add user "foo"
92
+ If specified password file does not exist and the "--digest-auth" and "--add-user" options are specified together, new password file containing user entry will be created.
93
+ ```
94
+ ipcam --digest-auth passwd.yml --add-user foo,XXXXXXX
95
+ ```
96
+
97
+ ##### and add user "bar"
98
+ If specified password file exists and the "--digest-auth" option and "--add-user" option are specified together, a user entry is added to the password file.
99
+ ```
100
+ ipcam --digest-auth passwd.yml --add-user bar,YYYYYY
101
+ ```
102
+
103
+ #### Run the server
104
+ If only the "--digest-auth" option is specified, the server is started and performs digest authentication with the specified password file.
105
+ ```
106
+ ipcam --digest-auth passwd.yml --use-ssl --ssl-cert cert/server.crt --ssl-key cert/server.key /dev/video0
107
+ ```
108
+
109
+ #### Delete user from password file
110
+ To delete a user, edit the YAML file directly.
111
+
65
112
  ### device-file
66
113
  specify target device file (ex: /dev/video1). if omittedm, it will use "/dev/video0".
67
114
 
data/bin/ipcam CHANGED
@@ -10,6 +10,8 @@
10
10
  require 'pathname'
11
11
  require 'optparse'
12
12
  require 'logger'
13
+ require 'yaml'
14
+ require 'digest/md5'
13
15
 
14
16
  Thread.abort_on_exception = true
15
17
 
@@ -47,6 +49,49 @@ OptionParser.new { |opt|
47
49
  opt.version = IPCam::VERSION
48
50
  opt.banner += " [DEVICE-FILE]"
49
51
 
52
+ opt.on('--use-ssl') {
53
+ $use_ssl = true
54
+ }
55
+
56
+ opt.on('--ssl-cert=CRT-FILE', String) { |val|
57
+ $ssl_cert = val
58
+ }
59
+
60
+ opt.on('--ssl-key=KEY-FILE', String) { |val|
61
+ $ssl_key = val
62
+ }
63
+
64
+ opt.on('-D', '--digest-auth=FILE', String) { |val|
65
+ $use_dauth = true
66
+ $pwd_file = Pathname.new(val)
67
+
68
+ if $pwd_file.exist?
69
+ if $pwd_file.world_readable? || $pwd_file.world_writable?
70
+ raise("password file shall be not world readble/writable")
71
+ end
72
+
73
+ $pwd_db = YAML.load_file(val)
74
+ else
75
+
76
+ $pwd_db = {}
77
+ end
78
+ }
79
+
80
+ opt.on('-A', '--add-user=USER,PASSWD', Array) { |val|
81
+ raise("passwd file is not specified") if not $pwd_db
82
+ raise("user \"#{val[0]}\" is already exist") if $pwd_db.include?(val[0])
83
+
84
+ $pwd_db[val[0]] = \
85
+ Digest::MD5.hexdigest("#{val[0]}:#{TRADITIONAL_NAME}:#{val[1]}")
86
+
87
+ $pwd_file.open("w") { |f|
88
+ f.chmod(0o600)
89
+ f.write($pwd_db.to_yaml)
90
+ }
91
+
92
+ exit
93
+ }
94
+
50
95
  opt.on('--bind=ADDR') { |val|
51
96
  $bind_addr = val
52
97
  }
@@ -117,6 +162,11 @@ OptionParser.new { |opt|
117
162
  if $db_file.exist? and (not $db_file.writable?)
118
163
  raise("#{$db_file.to_s} is not writable")
119
164
  end
165
+
166
+ if $use_ssl
167
+ raise("SSL cert file not specified") if not $ssl_cert
168
+ raise("SSL key file not specified") if not $ssl_key
169
+ end
120
170
  }
121
171
 
122
172
  #
@@ -1,3 +1,3 @@
1
1
  module IPCam
2
- VERSION = "0.3.5"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -50,12 +50,24 @@ module IPCam
50
50
 
51
51
  return nil
52
52
  end
53
+
54
+ def websock_url
55
+ return "#{($use_ssl)? "wss":"ws"}://${location.hostname}:#{$ws_port}"
56
+ end
53
57
  end
54
58
 
55
59
  get "/" do
56
60
  redirect "/main"
57
61
  end
58
62
 
63
+ get "/js/const.js" do
64
+ content_type("text/javascript")
65
+
66
+ <<~EOS
67
+ const WEBSOCK_URL = `#{websock_url}`;
68
+ EOS
69
+ end
70
+
59
71
  get "/main" do
60
72
  erb :main
61
73
  end
@@ -93,8 +105,10 @@ module IPCam
93
105
  begin
94
106
  app.add_client(queue)
95
107
 
96
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_QUICKACK, 1)
97
- sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
108
+ if sock.kind_of?(TCPSocket)
109
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_QUICKACK, 1)
110
+ sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
111
+ end
98
112
 
99
113
  sock.write("\r\n".b)
100
114
  sock.flush
@@ -107,23 +121,24 @@ module IPCam
107
121
 
108
122
  if $extend_header
109
123
  header = <<~EOT.b
110
- --#{boundary}
124
+ --#{boundary}\r
111
125
  Content-Type: image/jpeg\r
112
126
  Content-Length: #{body.bytesize}\r
113
- X-Frame-Number: #{fc}
114
- X-Timestamp: #{(Time.now.to_f * 1000).round}
127
+ X-Frame-Number: #{fc}\r
128
+ X-Timestamp: #{(Time.now.to_f * 1000).round}\r
115
129
  \r
116
130
  EOT
117
131
  else
118
132
  header = <<~EOT.b
119
- --#{boundary}
133
+ --#{boundary}\r
120
134
  Content-Type: image/jpeg\r
121
135
  Content-Length: #{body.bytesize}\r
122
136
  \r
123
137
  EOT
124
138
  end
125
139
 
126
- sock.write(header, body)
140
+ sock.write(header)
141
+ sock.write(body)
127
142
  sock.flush
128
143
  fc += 1
129
144
 
@@ -143,7 +158,7 @@ module IPCam
143
158
  }
144
159
  }
145
160
 
146
- # ↓力業でsinatraがContent-Lengthを付与仕様とするのを抑止している
161
+ # ↓力業でsinatraがContent-Lengthを付与するのを抑止している
147
162
  def response.calculate_content_length?
148
163
  return false
149
164
  end
@@ -167,6 +182,18 @@ module IPCam
167
182
  end
168
183
 
169
184
  class << self
185
+ if $use_dauth
186
+ def new(*)
187
+ ret = Rack::Auth::Digest::MD5.new(super) {|user| $pwd_db[user]}
188
+
189
+ ret.realm = TRADITIONAL_NAME
190
+ ret.opaque = SecureRandom.alphanumeric(32)
191
+ ret.passwords_hashed = true
192
+
193
+ return ret
194
+ end
195
+ end
196
+
170
197
  def bind_url
171
198
  if $bind_addr.include?(":")
172
199
  addr = "[#{$bind_addr}]" if $bind_addr.include?(":")
@@ -174,7 +201,13 @@ module IPCam
174
201
  addr = $bind_addr
175
202
  end
176
203
 
177
- return "tcp://#{addr}:#{$http_port}"
204
+ if $use_ssl
205
+ ret = "ssl://#{addr}:#{$http_port}?key=#{$ssl_key}&cert=#{$ssl_cert}"
206
+ else
207
+ ret = "tcp://#{addr}:#{$http_port}"
208
+ end
209
+
210
+ return ret
178
211
  end
179
212
  private :bind_url
180
213
 
@@ -106,7 +106,7 @@ module IPCam
106
106
  addr = $bind_addr
107
107
  end
108
108
 
109
- return "tcp://#{addr}:#{$ws_port}"
109
+ return "#{($use_ssl)? "ssl":"tcp"}://#{addr}:#{$ws_port}"
110
110
  end
111
111
  private :bind_url
112
112
 
@@ -121,7 +121,17 @@ module IPCam
121
121
 
122
122
  $logger.info("websock") {"started (#{bind_url()})"}
123
123
 
124
- EM::WebSocket.start(:host => $bind_addr, :port => $ws_port) { |sock|
124
+ opts = {
125
+ :host => $bind_addr,
126
+ :port => $ws_port,
127
+ :secure => $use_ssl,
128
+ :tls_options => {
129
+ :private_key_file => $ssl_key,
130
+ :cert_chain_file => $ssl_cert
131
+ }
132
+ }
133
+
134
+ EM::WebSocket.start(opts) { |sock|
125
135
  peer = Socket.unpack_sockaddr_in(sock.get_peername)
126
136
  addr = peer[1]
127
137
  port = peer[0]
@@ -9,8 +9,6 @@
9
9
  * define constants
10
10
  */
11
11
 
12
- const WS_URL = `ws://${location.hostname}:${parseInt(location.port)+1}/`;
13
-
14
12
  /*
15
13
  * declar package global variabled
16
14
  */
@@ -547,7 +545,7 @@
547
545
  }
548
546
 
549
547
  function initialize() {
550
- session = new Session(WS_URL);
548
+ session = new Session(WEBSOCK_URL);
551
549
  capabilities = null;
552
550
  controls = null;
553
551
  sliders = null;
@@ -18,6 +18,7 @@ body {
18
18
  }
19
19
 
20
20
  div.jumbotron {
21
+ position: relative;
21
22
  padding: 2rem 1rem;
22
23
  margin-bottom: 0px;
23
24
 
@@ -30,6 +31,17 @@ body {
30
31
  }
31
32
  }
32
33
 
34
+ div#version {
35
+ position: absolute;
36
+ right: 5px;
37
+ bottom: 5px;
38
+ opacity: 0.0;
39
+ }
40
+
41
+ div#version:hover {
42
+ opacity: 1.0;
43
+ }
44
+
33
45
  div#switches {
34
46
  width: 15%;
35
47
  height: 100%;
@@ -11,6 +11,7 @@
11
11
  </meta>
12
12
 
13
13
  <script type="text/javascript" src="/js/jquery-3.4.1.min.js"></script>
14
+ <script type="text/javascript" src="/js/const.js"></script>
14
15
  <script type="text/javascript" src="/js/util.js"></script>
15
16
  <script type="text/javascript" src="/js/main.js"></script>
16
17
  </head>
@@ -21,6 +22,10 @@
21
22
  <h3 id="device-file"></h3>
22
23
  <h6 id="device-name"></h3>
23
24
  </div>
25
+
26
+ <div id="version">
27
+ version <%= IPCam::VERSION %>
28
+ </div>
24
29
  </div>
25
30
 
26
31
  <div id="main-area" class="d-flex d-flex-row">
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ipcam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hirosho Kuwagata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-27 00:00:00.000000000 Z
11
+ date: 2019-10-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler