ipcam 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +47 -0
- data/bin/ipcam +50 -0
- data/lib/ipcam/version.rb +1 -1
- data/lib/ipcam/webserver.rb +42 -9
- data/lib/ipcam/websock.rb +12 -2
- data/resource/ipcam/js/main.js +1 -3
- data/resource/ipcam/scss/main/style.scss +12 -0
- data/resource/ipcam/views/main.erb +5 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3646608decd74c82db0631040a7ddd1b4f94ae1feef0ae39272505a4b27e45f
|
4
|
+
data.tar.gz: bf43c0a17f8e99a083776aa4e983ab86525dc1cdf0f3949588549f54dd2662af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
#
|
data/lib/ipcam/version.rb
CHANGED
data/lib/ipcam/webserver.rb
CHANGED
@@ -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.
|
97
|
-
|
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
|
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
|
-
|
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
|
|
data/lib/ipcam/websock.rb
CHANGED
@@ -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
|
-
|
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]
|
data/resource/ipcam/js/main.js
CHANGED
@@ -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(
|
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.
|
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-
|
11
|
+
date: 2019-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|