Merge lp:~alan-griffiths/mir/fix-1314574 into lp:mir
- fix-1314574
- Merge into development-branch
Status: | Merged |
---|---|
Approved by: | Alan Griffiths |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1644 |
Proposed branch: | lp:~alan-griffiths/mir/fix-1314574 |
Merge into: | lp:mir |
Prerequisite: | lp:~alan-griffiths/mir/basic-test-fixture |
Diff against target: |
242 lines (+94/-24) 6 files modified
include/server/mir/frontend/session_credentials.h (+0/-3) src/server/frontend/published_socket_connector.cpp (+6/-0) src/server/frontend/session_credentials.cpp (+0/-18) src/server/frontend/socket_messenger.cpp (+54/-3) src/server/frontend/socket_messenger.h (+6/-0) tests/acceptance-tests/test_trust_session_helper.cpp (+28/-0) |
To merge this branch: | bzr merge lp:~alan-griffiths/mir/fix-1314574 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot (community) | continuous-integration | Approve | |
Kevin DuBois (community) | Approve | ||
Alexandros Frantzis (community) | Approve | ||
Review via email: mp+219814@code.launchpad.net |
Commit message
frontend: get the credentials of the real client process when it connects
Description of the change
frontend: get the credentials of the real client process when it connects
Posix mandates that with previously used SO_PEERCRED "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)."
Note that privileged processes *can* spoof their SCM_CREDENTIALS as I don't think it matters for our use cases. But this seems to be the best we can achieve without relying on non-portable techniques (e.g. apparmor security contexts).
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1638
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alan Griffiths (alan-griffiths) wrote : | # |
Fetched 19.1 MB in 33s (573 kB/s)
W: Failed to fetch bzip2:/
W: Failed to fetch bzip2:/
Triggering rebuild
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1638
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1638
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alexandros Frantzis (afrantzis) wrote : | # |
153 + auto const ucredp = (struct ucred *) CMSG_DATA(
C cast.
210 +// Disabled as we can't be sure the mir_demo_
Why depend on mir_demo_
Alan Griffiths (alan-griffiths) wrote : | # |
> 153 + auto const ucredp = (struct ucred *)
> CMSG_DATA(
>
> C cast.
Done
> 210 +// Disabled as we can't be sure the mir_demo_
>
> Why depend on mir_demo_
> running mir_connect() ourselves? We can also record the actual client PID this
> way, and compare it directly with what the server got.
I did consider this - but wasn't sure I wanted to get involved with forking a process that already has a mir server running in it (and a socket pair). (Or communicating with one forked earlier after obtaining the socket.) I've been thinking that a small scriptable client might be good for several tests. But including that work in this MP seemed disproportionate.
Alexandros Frantzis (afrantzis) wrote : | # |
> I did consider this - but wasn't sure I wanted to get involved with forking a process that already
> has a mir server running in it (and a socket pair). (Or communicating with one forked earlier after
> obtaining the socket.) I've been thinking that a small scriptable client might be good for several
> tests. But including that work in this MP seemed disproportionate.
The scriptable client would be a solution for some cases, but I prefer the flexebility of running completely custom code for clients if possible (especially if some synchronization is needed).
I think that an "exec_in_
Anyway, I am happy as long as we have a plan for this for the short(-ish)-term
Alan Griffiths (alan-griffiths) wrote : | # |
> Anyway, I am happy as long as we have a plan for this for the short(-ish)-term
We have some convergent thoughts, not a plan. Marking with a "TODO" comment
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1639
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1641
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
PS Jenkins bot (ps-jenkins) : | # |
Preview Diff
1 | === modified file 'include/server/mir/frontend/session_credentials.h' | |||
2 | --- include/server/mir/frontend/session_credentials.h 2014-05-19 02:55:29 +0000 | |||
3 | +++ include/server/mir/frontend/session_credentials.h 2014-05-20 14:06:48 +0000 | |||
4 | @@ -27,8 +27,6 @@ | |||
5 | 27 | class SessionCredentials | 27 | class SessionCredentials |
6 | 28 | { | 28 | { |
7 | 29 | public: | 29 | public: |
8 | 30 | static SessionCredentials from_socket_fd(int socket_fd); | ||
9 | 31 | |||
10 | 32 | SessionCredentials(pid_t pid, uid_t uid, gid_t gid); | 30 | SessionCredentials(pid_t pid, uid_t uid, gid_t gid); |
11 | 33 | 31 | ||
12 | 34 | pid_t pid() const; | 32 | pid_t pid() const; |
13 | @@ -41,7 +39,6 @@ | |||
14 | 41 | pid_t the_pid; | 39 | pid_t the_pid; |
15 | 42 | uid_t the_uid; | 40 | uid_t the_uid; |
16 | 43 | gid_t the_gid; | 41 | gid_t the_gid; |
17 | 44 | |||
18 | 45 | }; | 42 | }; |
19 | 46 | } | 43 | } |
20 | 47 | } | 44 | } |
21 | 48 | 45 | ||
22 | === modified file 'src/server/frontend/published_socket_connector.cpp' | |||
23 | --- src/server/frontend/published_socket_connector.cpp 2014-05-19 02:55:29 +0000 | |||
24 | +++ src/server/frontend/published_socket_connector.cpp 2014-05-20 14:06:48 +0000 | |||
25 | @@ -205,6 +205,12 @@ | |||
26 | 205 | std::function<void(std::shared_ptr<Session> const& session)> const& connect_handler) const | 205 | std::function<void(std::shared_ptr<Session> const& session)> const& connect_handler) const |
27 | 206 | { | 206 | { |
28 | 207 | report->creating_session_for(server_socket->native_handle()); | 207 | report->creating_session_for(server_socket->native_handle()); |
29 | 208 | |||
30 | 209 | /* We set the SO_PASSCRED socket option in order to receive credentials */ | ||
31 | 210 | auto const optval = 1; | ||
32 | 211 | if (setsockopt(server_socket->native_handle(), SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1) | ||
33 | 212 | BOOST_THROW_EXCEPTION(std::runtime_error("Failed to set SO_PASSCRED")); | ||
34 | 213 | |||
35 | 208 | connection_creator->create_connection_for(server_socket, {connect_handler, this}); | 214 | connection_creator->create_connection_for(server_socket, {connect_handler, this}); |
36 | 209 | } | 215 | } |
37 | 210 | 216 | ||
38 | 211 | 217 | ||
39 | === modified file 'src/server/frontend/session_credentials.cpp' | |||
40 | --- src/server/frontend/session_credentials.cpp 2014-05-19 02:55:29 +0000 | |||
41 | +++ src/server/frontend/session_credentials.cpp 2014-05-20 14:06:48 +0000 | |||
42 | @@ -17,26 +17,8 @@ | |||
43 | 17 | 17 | ||
44 | 18 | #include "mir/frontend/session_credentials.h" | 18 | #include "mir/frontend/session_credentials.h" |
45 | 19 | 19 | ||
46 | 20 | #include <sys/socket.h> | ||
47 | 21 | |||
48 | 22 | #include <stdexcept> | ||
49 | 23 | #include <boost/throw_exception.hpp> | ||
50 | 24 | |||
51 | 25 | namespace mf = mir::frontend; | 20 | namespace mf = mir::frontend; |
52 | 26 | 21 | ||
53 | 27 | mf::SessionCredentials mf::SessionCredentials::from_socket_fd(int socket_fd) | ||
54 | 28 | { | ||
55 | 29 | struct ucred cr; | ||
56 | 30 | socklen_t cl = sizeof(cr); | ||
57 | 31 | |||
58 | 32 | auto status = getsockopt(socket_fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl); | ||
59 | 33 | |||
60 | 34 | if (status) | ||
61 | 35 | BOOST_THROW_EXCEPTION(std::runtime_error("Failed to query client socket credentials")); | ||
62 | 36 | |||
63 | 37 | return {cr.pid, cr.uid, cr.gid}; | ||
64 | 38 | } | ||
65 | 39 | |||
66 | 40 | mf::SessionCredentials::SessionCredentials(pid_t pid, uid_t uid, gid_t gid) : | 22 | mf::SessionCredentials::SessionCredentials(pid_t pid, uid_t uid, gid_t gid) : |
67 | 41 | the_pid{pid}, | 23 | the_pid{pid}, |
68 | 42 | the_uid{uid}, | 24 | the_uid{uid}, |
69 | 43 | 25 | ||
70 | === modified file 'src/server/frontend/socket_messenger.cpp' | |||
71 | --- src/server/frontend/socket_messenger.cpp 2014-05-20 08:38:05 +0000 | |||
72 | +++ src/server/frontend/socket_messenger.cpp 2014-05-20 14:06:48 +0000 | |||
73 | @@ -1,5 +1,5 @@ | |||
74 | 1 | /* | 1 | /* |
76 | 2 | * Copyright © 2013 Canonical Ltd. | 2 | * Copyright © 2013-2014 Canonical Ltd. |
77 | 3 | * | 3 | * |
78 | 4 | * This program is free software: you can redistribute it and/or modify | 4 | * This program is free software: you can redistribute it and/or modify |
79 | 5 | * it under the terms of the GNU General Public License version 3 as | 5 | * it under the terms of the GNU General Public License version 3 as |
80 | @@ -21,6 +21,8 @@ | |||
81 | 21 | #include "mir/frontend/session_credentials.h" | 21 | #include "mir/frontend/session_credentials.h" |
82 | 22 | #include "mir/variable_length_array.h" | 22 | #include "mir/variable_length_array.h" |
83 | 23 | 23 | ||
84 | 24 | #include <boost/throw_exception.hpp> | ||
85 | 25 | |||
86 | 24 | #include <errno.h> | 26 | #include <errno.h> |
87 | 25 | #include <string.h> | 27 | #include <string.h> |
88 | 26 | 28 | ||
89 | @@ -36,9 +38,27 @@ | |||
90 | 36 | { | 38 | { |
91 | 37 | } | 39 | } |
92 | 38 | 40 | ||
93 | 41 | mf::SessionCredentials mfd::SocketMessenger::creator_creds() const | ||
94 | 42 | { | ||
95 | 43 | struct ucred cr; | ||
96 | 44 | socklen_t cl = sizeof(cr); | ||
97 | 45 | |||
98 | 46 | auto status = getsockopt(socket->native_handle(), SOL_SOCKET, SO_PEERCRED, &cr, &cl); | ||
99 | 47 | |||
100 | 48 | if (status) | ||
101 | 49 | BOOST_THROW_EXCEPTION(std::runtime_error("Failed to query client socket credentials")); | ||
102 | 50 | |||
103 | 51 | return {cr.pid, cr.uid, cr.gid}; | ||
104 | 52 | } | ||
105 | 53 | |||
106 | 39 | mf::SessionCredentials mfd::SocketMessenger::client_creds() | 54 | mf::SessionCredentials mfd::SocketMessenger::client_creds() |
107 | 40 | { | 55 | { |
109 | 41 | return mf::SessionCredentials::from_socket_fd(socket->native_handle()); | 56 | if (session_creds.pid() != 0) |
110 | 57 | return session_creds; | ||
111 | 58 | |||
112 | 59 | // We've not got the credentials from client yet. | ||
113 | 60 | // Return the credentials that created the socket instead. | ||
114 | 61 | return creator_creds(); | ||
115 | 42 | } | 62 | } |
116 | 43 | 63 | ||
117 | 44 | void mfd::SocketMessenger::send(char const* data, size_t length, FdSets const& fd_set) | 64 | void mfd::SocketMessenger::send(char const* data, size_t length, FdSets const& fd_set) |
118 | @@ -92,7 +112,7 @@ | |||
119 | 92 | message->cmsg_len = header.msg_controllen; | 112 | message->cmsg_len = header.msg_controllen; |
120 | 93 | message->cmsg_level = SOL_SOCKET; | 113 | message->cmsg_level = SOL_SOCKET; |
121 | 94 | message->cmsg_type = SCM_RIGHTS; | 114 | message->cmsg_type = SCM_RIGHTS; |
123 | 95 | int *data = (int *)CMSG_DATA(message); | 115 | int *data = reinterpret_cast<int*>(CMSG_DATA(message)); |
124 | 96 | int i = 0; | 116 | int i = 0; |
125 | 97 | for (auto &fd: fds) | 117 | for (auto &fd: fds) |
126 | 98 | data[i++] = fd; | 118 | data[i++] = fd; |
127 | @@ -128,7 +148,38 @@ | |||
128 | 128 | 148 | ||
129 | 129 | size_t mfd::SocketMessenger::available_bytes() | 149 | size_t mfd::SocketMessenger::available_bytes() |
130 | 130 | { | 150 | { |
131 | 151 | // We call available_bytes() once the client is talking to us | ||
132 | 152 | // so this is a pragmatic place to grab the session credentials | ||
133 | 153 | if (session_creds.pid() == 0) | ||
134 | 154 | update_session_creds(); | ||
135 | 155 | |||
136 | 131 | boost::asio::socket_base::bytes_readable command{true}; | 156 | boost::asio::socket_base::bytes_readable command{true}; |
137 | 132 | socket->io_control(command); | 157 | socket->io_control(command); |
138 | 133 | return command.get(); | 158 | return command.get(); |
139 | 134 | } | 159 | } |
140 | 160 | |||
141 | 161 | void mfd::SocketMessenger::update_session_creds() | ||
142 | 162 | { | ||
143 | 163 | union { | ||
144 | 164 | struct cmsghdr cmh; | ||
145 | 165 | char control[CMSG_SPACE(sizeof(ucred))]; | ||
146 | 166 | } control_un; | ||
147 | 167 | |||
148 | 168 | control_un.cmh.cmsg_len = CMSG_LEN(sizeof(ucred)); | ||
149 | 169 | control_un.cmh.cmsg_level = SOL_SOCKET; | ||
150 | 170 | control_un.cmh.cmsg_type = SCM_CREDENTIALS; | ||
151 | 171 | |||
152 | 172 | msghdr msgh; | ||
153 | 173 | msgh.msg_name = nullptr; | ||
154 | 174 | msgh.msg_namelen = 0; | ||
155 | 175 | msgh.msg_iov = nullptr; | ||
156 | 176 | msgh.msg_iovlen = 0; | ||
157 | 177 | msgh.msg_control = control_un.control; | ||
158 | 178 | msgh.msg_controllen = sizeof(control_un.control); | ||
159 | 179 | |||
160 | 180 | if (recvmsg(socket->native_handle(), &msgh, MSG_PEEK) != -1) | ||
161 | 181 | { | ||
162 | 182 | auto const ucredp = reinterpret_cast<ucred*>(CMSG_DATA(CMSG_FIRSTHDR(&msgh))); | ||
163 | 183 | session_creds = {ucredp->pid, ucredp->uid, ucredp->gid}; | ||
164 | 184 | } | ||
165 | 185 | } | ||
166 | 135 | 186 | ||
167 | === modified file 'src/server/frontend/socket_messenger.h' | |||
168 | --- src/server/frontend/socket_messenger.h 2014-05-16 16:46:13 +0000 | |||
169 | +++ src/server/frontend/socket_messenger.h 2014-05-20 14:06:48 +0000 | |||
170 | @@ -20,6 +20,7 @@ | |||
171 | 20 | #define MIR_FRONTEND_SOCKET_MESSENGER_H_ | 20 | #define MIR_FRONTEND_SOCKET_MESSENGER_H_ |
172 | 21 | #include "message_sender.h" | 21 | #include "message_sender.h" |
173 | 22 | #include "message_receiver.h" | 22 | #include "message_receiver.h" |
174 | 23 | #include "mir/frontend/session_credentials.h" | ||
175 | 23 | #include <mutex> | 24 | #include <mutex> |
176 | 24 | 25 | ||
177 | 25 | namespace mir | 26 | namespace mir |
178 | @@ -42,9 +43,14 @@ | |||
179 | 42 | SessionCredentials client_creds() override; | 43 | SessionCredentials client_creds() override; |
180 | 43 | 44 | ||
181 | 44 | private: | 45 | private: |
182 | 46 | void update_session_creds(); | ||
183 | 47 | SessionCredentials creator_creds() const; | ||
184 | 48 | |||
185 | 45 | std::shared_ptr<boost::asio::local::stream_protocol::socket> socket; | 49 | std::shared_ptr<boost::asio::local::stream_protocol::socket> socket; |
186 | 46 | 50 | ||
187 | 47 | std::mutex message_lock; | 51 | std::mutex message_lock; |
188 | 52 | SessionCredentials session_creds{0, 0, 0}; | ||
189 | 53 | |||
190 | 48 | void send_fds_locked(std::unique_lock<std::mutex> const& lock, std::vector<int32_t> const& fds); | 54 | void send_fds_locked(std::unique_lock<std::mutex> const& lock, std::vector<int32_t> const& fds); |
191 | 49 | }; | 55 | }; |
192 | 50 | } | 56 | } |
193 | 51 | 57 | ||
194 | === modified file 'tests/acceptance-tests/test_trust_session_helper.cpp' | |||
195 | --- tests/acceptance-tests/test_trust_session_helper.cpp 2014-05-19 02:55:29 +0000 | |||
196 | +++ tests/acceptance-tests/test_trust_session_helper.cpp 2014-05-20 14:06:48 +0000 | |||
197 | @@ -27,6 +27,7 @@ | |||
198 | 27 | #include "mir_test_framework/stubbed_server_configuration.h" | 27 | #include "mir_test_framework/stubbed_server_configuration.h" |
199 | 28 | #include "mir_test_framework/basic_client_server_fixture.h" | 28 | #include "mir_test_framework/basic_client_server_fixture.h" |
200 | 29 | #include "mir_test_doubles/fake_ipc_factory.h" | 29 | #include "mir_test_doubles/fake_ipc_factory.h" |
201 | 30 | #include "mir_test/popen.h" | ||
202 | 30 | 31 | ||
203 | 31 | #include <gtest/gtest.h> | 32 | #include <gtest/gtest.h> |
204 | 32 | #include <gmock/gmock.h> | 33 | #include <gmock/gmock.h> |
205 | @@ -149,6 +150,8 @@ | |||
206 | 149 | sprintf(client_connect_string, "fd://%d", fd); | 150 | sprintf(client_connect_string, "fd://%d", fd); |
207 | 150 | return client_connect_string; | 151 | return client_connect_string; |
208 | 151 | } | 152 | } |
209 | 153 | |||
210 | 154 | MOCK_METHOD1(process_line, void(std::string const&)); | ||
211 | 152 | }; | 155 | }; |
212 | 153 | 156 | ||
213 | 154 | void client_fd_callback(MirConnection*, size_t count, int const* fds, void* context) | 157 | void client_fd_callback(MirConnection*, size_t count, int const* fds, void* context) |
214 | @@ -185,3 +188,28 @@ | |||
215 | 185 | 188 | ||
216 | 186 | mir_connection_release(client_connection); | 189 | mir_connection_release(client_connection); |
217 | 187 | } | 190 | } |
218 | 191 | |||
219 | 192 | // TODO we need a nice way to run this (and similar tests that require a separate client process) in CI | ||
220 | 193 | // Disabled as we can't be sure the mir_demo_client_basic is about | ||
221 | 194 | TEST_F(TrustSessionHelper, DISABLED_client_pid_is_associated_with_session) | ||
222 | 195 | { | ||
223 | 196 | auto const server_pid = getpid(); | ||
224 | 197 | |||
225 | 198 | mir_connection_new_fds_for_trusted_clients(connection, 1, &client_fd_callback, this); | ||
226 | 199 | wait_for_callback(std::chrono::milliseconds(500)); | ||
227 | 200 | |||
228 | 201 | InSequence seq; | ||
229 | 202 | EXPECT_CALL(*this, process_line(StrEq("Starting"))); | ||
230 | 203 | EXPECT_CALL(*this, process_line(StrEq("Connected"))); | ||
231 | 204 | EXPECT_CALL(*this, process_line(StrEq("Surface created"))); | ||
232 | 205 | EXPECT_CALL(*this, process_line(StrEq("Surface released"))); | ||
233 | 206 | EXPECT_CALL(*this, process_line(StrEq("Connection released"))); | ||
234 | 207 | |||
235 | 208 | mir::test::Popen output(std::string("bin/mir_demo_client_basic -m ") + fd_connect_string(actual_fds[0])); | ||
236 | 209 | |||
237 | 210 | std::string line; | ||
238 | 211 | while (output.get_line(line)) process_line(line); | ||
239 | 212 | |||
240 | 213 | EXPECT_THAT(trusted_helper_mediator->client_pid, Ne(0)); | ||
241 | 214 | EXPECT_THAT(trusted_helper_mediator->client_pid, Ne(server_pid)); | ||
242 | 215 | } |
PASSED: Continuous integration, rev:1637 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- ci/1647/ jenkins. qa.ubuntu. com/job/ mir-android- utopic- i386-build/ 218 jenkins. qa.ubuntu. com/job/ mir-clang- utopic- amd64-build/ 219 jenkins. qa.ubuntu. com/job/ mir-mediumtests -utopic- touch/219 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 166 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- amd64-ci/ 166/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- armhf-ci/ 165 jenkins. qa.ubuntu. com/job/ mir-team- mir-development -branch- utopic- armhf-ci/ 165/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- armhf/557 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- armhf/557/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ mir-mediumtests -runner- mako/1471 s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 7261
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/mir- team-mir- development- branch- ci/1647/ rebuild
http://