dc6e637ce60ad1c7dc311dd783cb746850626c0d
[platform/core/security/vasum.git] / server / containers-manager.cpp
1 /*
2  *  Copyright (c) 2014 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *  Contact: Jan Olszak <j.olszak@samsung.com>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License
17  */
18
19 /**
20  * @file
21  * @author  Jan Olszak (j.olszak@samsung.com)
22  * @brief   Definition of the class for managing containers
23  */
24
25 #include "config.hpp"
26
27 #include "container-dbus-definitions.hpp"
28 #include "containers-manager.hpp"
29 #include "container-admin.hpp"
30 #include "exception.hpp"
31
32 #include "utils/paths.hpp"
33 #include "log/logger.hpp"
34 #include "config/manager.hpp"
35 #include "dbus/exception.hpp"
36
37 #include <boost/filesystem.hpp>
38 #include <boost/regex.hpp>
39 #include <cassert>
40 #include <string>
41 #include <climits>
42
43
44 namespace security_containers {
45
46
47 namespace {
48
49 bool regexMatchVector(const std::string& str, const std::vector<boost::regex>& v)
50 {
51     for (const boost::regex& toMatch: v) {
52         if (boost::regex_match(str, toMatch)) {
53             return true;
54         }
55     }
56
57     return false;
58 }
59
60 const std::string HOST_ID = "host";
61 const std::string DBUS_ERROR_NAME_FORBIDDEN = "org.tizen.containers.Error.Forbidden";
62 const std::string DBUS_ERROR_NAME_FORWARDED = "org.tizen.containers.Error.Forwarded";
63 const std::string DBUS_ERROR_NAME_UNKNOWN_TARGET = "org.tizen.containers.Error.UnknownTarget";
64
65 } // namespace
66
67 ContainersManager::ContainersManager(const std::string& managerConfigPath): mDetachOnExit(false)
68 {
69     LOGD("Instantiating ContainersManager object...");
70     config::loadFromFile(managerConfigPath, mConfig);
71
72     mProxyCallPolicy.reset(new ProxyCallPolicy(mConfig.proxyCallRules));
73
74     using namespace std::placeholders;
75     mHostConnection.setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
76                                               this, HOST_ID, _1, _2, _3, _4, _5, _6, _7));
77
78     mHostConnection.setGetContainerDbusesCallback(bind(
79                 &ContainersManager::handleGetContainerDbuses, this, _1));
80
81     for (auto& containerConfig : mConfig.containerConfigs) {
82         std::string containerConfigPath;
83
84         if (containerConfig[0] == '/') {
85             containerConfigPath = containerConfig;
86         } else {
87             std::string baseConfigPath = utils::dirName(managerConfigPath);
88             containerConfigPath = utils::createFilePath(baseConfigPath, "/", containerConfig);
89         }
90
91         LOGD("Creating Container " << containerConfigPath);
92         std::unique_ptr<Container> c(new Container(containerConfigPath,
93                                                    mConfig.runMountPointPrefix));
94         const std::string id = c->getId();
95         if (id == HOST_ID) {
96             throw ContainerOperationException("Cannot use reserved container ID");
97         }
98
99         c->setNotifyActiveContainerCallback(bind(&ContainersManager::notifyActiveContainerHandler,
100                                                  this, id, _1, _2));
101
102         c->setDisplayOffCallback(bind(&ContainersManager::displayOffHandler,
103                                       this, id));
104
105         c->setFileMoveRequestCallback(std::bind(&ContainersManager::handleContainerMoveFileRequest,
106                                                 this, id, _1, _2, _3));
107
108         c->setProxyCallCallback(bind(&ContainersManager::handleProxyCall,
109                                      this, id, _1, _2, _3, _4, _5, _6, _7));
110
111         c->setDbusStateChangedCallback(bind(&ContainersManager::handleDbusStateChanged,
112                                             this, id, _1));
113
114         mContainers.insert(ContainerMap::value_type(id, std::move(c)));
115     }
116
117     // check if default container exists, throw ContainerOperationException if not found
118     if (mContainers.find(mConfig.defaultId) == mContainers.end()) {
119         LOGE("Provided default container ID " << mConfig.defaultId << " is invalid.");
120         throw ContainerOperationException("Provided default container ID " + mConfig.defaultId +
121                                           " is invalid.");
122     }
123
124     LOGD("ContainersManager object instantiated");
125
126     if (mConfig.inputConfig.enabled) {
127         LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
128         mSwitchingSequenceMonitor.reset(
129                 new InputMonitor(mConfig.inputConfig,
130                                  std::bind(&ContainersManager::switchingSequenceMonitorNotify,
131                                            this)));
132     }
133 }
134
135
136 ContainersManager::~ContainersManager()
137 {
138     LOGD("Destroying ContainersManager object...");
139
140     if (!mDetachOnExit) {
141         try {
142             stopAll();
143         } catch (ServerException&) {
144             LOGE("Failed to stop all of the containers");
145         }
146     }
147
148     LOGD("ContainersManager object destroyed");
149 }
150
151
152 void ContainersManager::focus(const std::string& containerId)
153 {
154     /* try to access the object first to throw immediately if it doesn't exist */
155     ContainerMap::mapped_type& foregroundContainer = mContainers.at(containerId);
156
157     for (auto& container : mContainers) {
158         LOGD(container.second->getId() << ": being sent to background");
159         container.second->goBackground();
160     }
161     mConfig.foregroundId = foregroundContainer->getId();
162     LOGD(mConfig.foregroundId << ": being sent to foreground");
163     foregroundContainer->goForeground();
164 }
165
166
167 void ContainersManager::startAll()
168 {
169     LOGI("Starting all containers");
170
171     bool isForegroundFound = false;
172
173     for (auto& container : mContainers) {
174         container.second->start();
175
176         if (container.first == mConfig.foregroundId) {
177             isForegroundFound = true;
178             LOGI(container.second->getId() << ": set as the foreground container");
179             container.second->goForeground();
180         }
181     }
182
183     if (!isForegroundFound) {
184         auto foregroundIterator = std::min_element(mContainers.begin(), mContainers.end(),
185                                                    [](ContainerMap::value_type &c1, ContainerMap::value_type &c2) {
186                                                        return c1.second->getPrivilege() < c2.second->getPrivilege();
187                                                    });
188
189         mConfig.foregroundId = foregroundIterator->second->getId();
190         LOGI(mConfig.foregroundId << ": no foreground container configured, setting one with highest priority");
191         foregroundIterator->second->goForeground();
192     }
193 }
194
195
196 void ContainersManager::stopAll()
197 {
198     LOGI("Stopping all containers");
199
200     for (auto& container : mContainers) {
201         container.second->stop();
202     }
203 }
204
205
206 std::string ContainersManager::getRunningForegroundContainerId()
207 {
208     for (auto& container : mContainers) {
209         if (container.first == mConfig.foregroundId &&
210             container.second->isRunning()) {
211             return container.first;
212         }
213     }
214     return std::string();
215 }
216
217 void ContainersManager::switchingSequenceMonitorNotify()
218 {
219     LOGI("switchingSequenceMonitorNotify() called");
220     // TODO: implement
221 }
222
223
224 void ContainersManager::setContainersDetachOnExit()
225 {
226     mDetachOnExit = true;
227
228     for (auto& container : mContainers) {
229         container.second->setDetachOnExit();
230     }
231 }
232
233 void ContainersManager::notifyActiveContainerHandler(const std::string& caller,
234                                                      const std::string& application,
235                                                      const std::string& message)
236 {
237     LOGI("notifyActiveContainerHandler(" << caller << ", " << application << ", " << message
238          << ") called");
239     try {
240         const std::string activeContainer = getRunningForegroundContainerId();
241         if (!activeContainer.empty() && caller != activeContainer) {
242             mContainers[activeContainer]->sendNotification(caller, application, message);
243         }
244     } catch(const SecurityContainersException&) {
245         LOGE("Notification from " << caller << " hasn't been sent");
246     }
247 }
248
249 void ContainersManager::displayOffHandler(const std::string& /*caller*/)
250 {
251     // get config of currently set container and switch if switchToDefaultAfterTimeout is true
252     const std::string activeContainerName = getRunningForegroundContainerId();
253     const auto& activeContainer = mContainers.find(activeContainerName);
254
255     if (activeContainer != mContainers.end() &&
256         activeContainer->second->isSwitchToDefaultAfterTimeoutAllowed()) {
257         LOGI("Switching to default container " << mConfig.defaultId);
258         focus(mConfig.defaultId);
259     }
260 }
261
262 void ContainersManager::handleContainerMoveFileRequest(const std::string& srcContainerId,
263                                                        const std::string& dstContainerId,
264                                                        const std::string& path,
265                                                        dbus::MethodResultBuilder::Pointer result)
266 {
267     // TODO: this implementation is only a placeholder.
268     // There are too many unanswered questions and security concerns:
269     // 1. What about mount namespace, host might not see the source/destination
270     //    file. The file might be a different file from a host perspective.
271     // 2. Copy vs move (speed and security concerns over already opened FDs)
272     // 3. Access to source and destination files - DAC, uid/gig
273     // 4. Access to source and destintation files - MAC, smack
274     // 5. Destination file uid/gid assignment
275     // 6. Destination file smack label assignment
276     // 7. Verifiability of the source path
277
278     // NOTE: other possible implementations include:
279     // 1. Sending file descriptors opened directly in each container through DBUS
280     //    using something like g_dbus_message_set_unix_fd_list()
281     // 2. SCS forking and calling setns(MNT) in each container and opening files
282     //    by itself, then passing FDs to the main process
283     // Now when the main process has obtained FDs (by either of those methods)
284     // it can do the copying by itself.
285
286     LOGI("File move requested\n"
287          << "src: " << srcContainerId << "\n"
288          << "dst: " << dstContainerId << "\n"
289          << "path: " << path);
290
291     ContainerMap::const_iterator srcIter = mContainers.find(srcContainerId);
292     if (srcIter == mContainers.end()) {
293         LOGE("Source container '" << srcContainerId << "' not found");
294         return;
295     }
296     Container& srcContainer = *srcIter->second;
297
298     ContainerMap::const_iterator dstIter = mContainers.find(dstContainerId);
299     if (dstIter == mContainers.end()) {
300         LOGE("Destination container '" << dstContainerId << "' not found");
301         result->set(g_variant_new("(s)", api::FILE_MOVE_DESTINATION_NOT_FOUND.c_str()));
302         return;
303     }
304     Container& dstContanier = *dstIter->second;
305
306     if (srcContainerId == dstContainerId) {
307         LOGE("Cannot send a file to yourself");
308         result->set(g_variant_new("(s)", api::FILE_MOVE_WRONG_DESTINATION.c_str()));
309         return;
310     }
311
312     if (!regexMatchVector(path, srcContainer.getPermittedToSend())) {
313         LOGE("Source container has no permissions to send the file: " << path);
314         result->set(g_variant_new("(s)", api::FILE_MOVE_NO_PERMISSIONS_SEND.c_str()));
315         return;
316     }
317
318     if (!regexMatchVector(path, dstContanier.getPermittedToRecv())) {
319         LOGE("Destination container has no permissions to receive the file: " << path);
320         result->set(g_variant_new("(s)", api::FILE_MOVE_NO_PERMISSIONS_RECEIVE.c_str()));
321         return;
322     }
323
324     namespace fs = boost::filesystem;
325     std::string srcPath = fs::absolute(srcContainerId, mConfig.containersPath).string() + path;
326     std::string dstPath = fs::absolute(dstContainerId, mConfig.containersPath).string() + path;
327
328     if (!utils::moveFile(srcPath, dstPath)) {
329         LOGE("Failed to move the file: " << path);
330         result->set(g_variant_new("(s)", api::FILE_MOVE_FAILED.c_str()));
331     } else {
332         result->set(g_variant_new("(s)", api::FILE_MOVE_SUCCEEDED.c_str()));
333         try {
334             dstContanier.sendNotification(srcContainerId, path, api::FILE_MOVE_SUCCEEDED);
335         } catch (ServerException&) {
336             LOGE("Notification to '" << dstContainerId << "' has not been sent");
337         }
338     }
339 }
340
341 void ContainersManager::handleProxyCall(const std::string& caller,
342                                         const std::string& target,
343                                         const std::string& targetBusName,
344                                         const std::string& targetObjectPath,
345                                         const std::string& targetInterface,
346                                         const std::string& targetMethod,
347                                         GVariant* parameters,
348                                         dbus::MethodResultBuilder::Pointer result)
349 {
350     if (!mProxyCallPolicy->isProxyCallAllowed(caller,
351                                               target,
352                                               targetBusName,
353                                               targetObjectPath,
354                                               targetInterface,
355                                               targetMethod)) {
356         LOGW("Forbidden proxy call; " << caller << " -> " << target << "; " << targetBusName
357                 << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
358         result->setError(DBUS_ERROR_NAME_FORBIDDEN, "Proxy call forbidden");
359         return;
360     }
361
362     LOGI("Proxy call; " << caller << " -> " << target << "; " << targetBusName
363             << "; " << targetObjectPath << "; " << targetInterface << "; " << targetMethod);
364
365     auto asyncResultCallback = [result](dbus::AsyncMethodCallResult& asyncMethodCallResult) {
366         try {
367             GVariant* targetResult = asyncMethodCallResult.get();
368             result->set(g_variant_new("(v)", targetResult));
369         } catch (dbus::DbusException& e) {
370             result->setError(DBUS_ERROR_NAME_FORWARDED, e.what());
371         }
372     };
373
374     if (target == HOST_ID) {
375         mHostConnection.proxyCallAsync(targetBusName,
376                                        targetObjectPath,
377                                        targetInterface,
378                                        targetMethod,
379                                        parameters,
380                                        asyncResultCallback);
381         return;
382     }
383
384     ContainerMap::const_iterator targetIter = mContainers.find(target);
385     if (targetIter == mContainers.end()) {
386         LOGE("Target container '" << target << "' not found");
387         result->setError(DBUS_ERROR_NAME_UNKNOWN_TARGET, "Unknown proxy call target");
388         return;
389     }
390
391     Container& targetContainer = *targetIter->second;
392     targetContainer.proxyCallAsync(targetBusName,
393                                    targetObjectPath,
394                                    targetInterface,
395                                    targetMethod,
396                                    parameters,
397                                    asyncResultCallback);
398 }
399
400 void ContainersManager::handleGetContainerDbuses(dbus::MethodResultBuilder::Pointer result)
401 {
402     std::vector<GVariant*> entries;
403     for (auto& container : mContainers) {
404         GVariant* containerId = g_variant_new_string(container.first.c_str());
405         GVariant* dbusAddress = g_variant_new_string(container.second->getDbusAddress().c_str());
406         GVariant* entry = g_variant_new_dict_entry(containerId, dbusAddress);
407         entries.push_back(entry);
408     }
409     GVariant* dict = g_variant_new_array(G_VARIANT_TYPE("{ss}"), entries.data(), entries.size());
410     result->set(g_variant_new("(*)", dict));
411 }
412
413 void ContainersManager::handleDbusStateChanged(const std::string& containerId,
414                                                const std::string& dbusAddress)
415 {
416     mHostConnection.signalContainerDbusState(containerId, dbusAddress);
417 }
418
419
420 } // namespace security_containers