Music Hub  ..
A session-wide music playback service
ubuntu.cpp
Go to the documentation of this file.
1 /*
2  * Copyright © 2014 Canonical Ltd.
3  *
4  * This program is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU Lesser General Public License version 3,
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  *
16  * Authored by: Thomas Voß <thomas.voss@canonical.com>
17  */
18 
20 
22 
24 
25 #include <regex>
26 #include <unistd.h> // geteuid()
27 
29 namespace media = core::ubuntu::media;
30 namespace ubuntu = apparmor::ubuntu;
31 
32 namespace
33 {
34 struct Uri
35 {
36  std::string scheme;
37  std::string authority;
38  std::string path;
39  std::string query;
40  std::string fragment;
41 };
42 
43 // Poor mans version of a uri parser.
44 // See https://tools.ietf.org/html/rfc3986#appendix-B
45 Uri parse_uri(const std::string& s)
46 {
47  // Indices into the regex match go here.
48  struct Index
49  {
50  const std::size_t scheme{2};
51  const std::size_t authority{4};
52  const std::size_t path{5};
53  const std::size_t query{7};
54  const std::size_t fragment{9};
55  } static index;
56 
57  static const std::regex regex{R"delim(^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)delim"};
58  std::smatch match;
59 
60  if (not std::regex_match(s, match, regex)) throw std::runtime_error
61  {
62  "Not a valid URI: " + s
63  };
64 
65  return Uri
66  {
67  match.str(index.scheme),
68  match.str(index.authority),
69  match.str(index.path),
70  match.str(index.query),
71  match.str(index.fragment)
72  };
73 }
74 
75 static constexpr std::size_t index_package{1};
76 static constexpr std::size_t index_app{2};
77 static const std::string unity_name{"unity8-dash"};
78 static const std::string unity8_snap_name{"snap.unity8-session.unity8-session"};
79 
80 // ad-hoc for mediaplayer-app/music-app until it settles down with proper handling
81 // Bug #1642611
82 static const std::string mediaplayer_snap_name{"snap.mediaplayer-app.mediaplayer-app"};
83 static const std::string music_snap_name{"snap.music-app.music-app"};
84 // Returns true if the context name is a valid Ubuntu app id.
85 // If it is, out is populated with the package and app name.
86 bool process_context_name(const std::string& s, std::smatch& out,
87  std::string& pkg_name)
88 {
89  // See https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId.
90  static const std::regex short_re{"(.*)_(.*)"};
91  static const std::regex full_re{"(.*)_(.*)_(.*)"};
92  static const std::regex trust_store_re{"(.*)-(.*)"};
93 
94  if ((s == "messaging-app" or s == unity_name or s == unity8_snap_name or
95  s == mediaplayer_snap_name or s == music_snap_name)
96  and std::regex_match(s, out, trust_store_re))
97  {
98  pkg_name = s;
99  return true;
100  }
101 
102  if (std::regex_match(s, out, full_re) or std::regex_match(s, out, short_re))
103  {
104  pkg_name = out[index_package];
105  return true;
106  }
107 
108  return false;
109 }
110 }
111 
112 apparmor::ubuntu::Context::Context(const std::string& name)
113  : apparmor::Context{name},
114  unconfined_{str() == ubuntu::unconfined},
115  unity_{name == unity_name || name == unity8_snap_name},
116  has_package_name_{process_context_name(str(), match_, pkg_name_)}
117 {
118  MH_DEBUG("apparmor profile name: %s", name);
119  MH_DEBUG("is_unconfined(): %s", (is_unconfined() ? "true" : "false"));
120  MH_DEBUG("has_package_name(): %s", (has_package_name() ? "true" : "false"));
121  if (not is_unconfined() and not is_unity() and not has_package_name())
122  throw std::logic_error
123  {
124  "apparmor::ubuntu::Context: Invalid profile name " + str()
125  };
126 }
127 
129 {
130  return unconfined_;
131 }
132 
134 {
135  return unity_;
136 }
137 
139 {
140  return has_package_name_;
141 }
142 
144 {
145  return pkg_name_;
146 }
147 
149 {
150  return std::string{match_[index_package]} + "-" + std::string{match_[index_app]};
151 }
152 
154 {
155 }
156 
158  const std::string& name,
160 {
161  dbus_daemon.get_connection_app_armor_security_async(name, [cb](const std::string& context_name)
162  {
163  cb(apparmor::ubuntu::Context{context_name});
164  });
165 }
166 
168 {
169  if (context.is_unconfined())
170  return Result{true, "Client allowed access since it's unconfined"};
171 
172  Uri parsed_uri = parse_uri(uri);
173 
174  MH_DEBUG("context.profile_name(): %s", context.profile_name());
175  MH_DEBUG("parsed_uri.path: %s", parsed_uri.path);
176 
177  // All confined apps can access their own files
178  if (parsed_uri.path.find(std::string(".local/share/" + context.package_name() + "/")) != std::string::npos ||
179  parsed_uri.path.find(std::string(".cache/" + context.package_name() + "/")) != std::string::npos ||
180  parsed_uri.path.find(std::string("/run/user/" + std::to_string(geteuid()) + "/confined/" + context.package_name())) != std::string::npos)
181  {
182  return Result
183  {
184  true,
185  "Client can access content in ~/.local/share/" + context.package_name() + " or ~/.cache/" + context.package_name()
186  };
187  }
188  // Check for trust-store compatible path name using full messaging-app profile_name
189  else if (context.profile_name() == "messaging-app" &&
190  /* Since the full APP_ID is not available yet (see aa_query_file_path()), add an exception: */
191  (parsed_uri.path.find(std::string(".local/share/com.ubuntu." + context.profile_name() + "/")) != std::string::npos ||
192  parsed_uri.path.find(std::string(".cache/com.ubuntu." + context.profile_name() + "/")) != std::string::npos))
193  {
194  return Result
195  {
196  true,
197  "Client can access content in ~/.local/share/" + context.profile_name() + " or ~/.cache/" + context.profile_name()
198  };
199  }
200  else if (parsed_uri.path.find(std::string("opt/click.ubuntu.com/")) != std::string::npos &&
201  parsed_uri.path.find(context.package_name()) != std::string::npos)
202  {
203  return Result{true, "Client can access content in own opt directory"};
204  }
205  else if ((parsed_uri.path.find(std::string("/system/media/audio/ui/")) != std::string::npos ||
206  parsed_uri.path.find(std::string("/android/system/media/audio/ui/")) != std::string::npos) &&
207  context.package_name() == "com.ubuntu.camera")
208  {
209  return Result{true, "Camera app can access ui sounds"};
210  }
211 
212  // TODO: Check if the trust store previously allowed direct access to uri
213 
214  // Check in ~/Music and ~/Videos
215  // TODO: when the trust store lands, check it to see if this app can access the dirs and
216  // then remove the explicit whitelist of the music-app, and gallery-app
217  else if ((context.package_name() == "com.ubuntu.music" || context.package_name() == "com.ubuntu.gallery" ||
218  context.profile_name() == unity_name || context.profile_name() == unity8_snap_name ||
219  context.profile_name() == mediaplayer_snap_name || context.profile_name() == music_snap_name) &&
220  (parsed_uri.path.find(std::string("Music/")) != std::string::npos ||
221  parsed_uri.path.find(std::string("Videos/")) != std::string::npos ||
222  parsed_uri.path.find(std::string("/media")) != std::string::npos))
223  {
224  return Result{true, "Client can access content in ~/Music or ~/Videos"};
225  }
226  else if (parsed_uri.path.find(std::string("/usr/share/sounds")) != std::string::npos)
227  {
228  return Result{true, "Client can access content in /usr/share/sounds"};
229  }
230  else if (parsed_uri.scheme == "http" ||
231  parsed_uri.scheme == "https" ||
232  parsed_uri.scheme == "rtsp")
233  {
234  return Result{true, "Client can access streaming content"};
235  }
236 
237  return Result{false, "Client is not allowed to access: " + uri};
238 }
239 
240 // Returns the platform-default implementation of RequestContextResolver.
242 {
243  return std::make_shared<apparmor::ubuntu::DBusDaemonRequestContextResolver>(es.session);
244 }
245 
246 // Returns the platform-default implementation of RequestAuthenticator.
248 {
249  return std::make_shared<apparmor::ubuntu::ExistingAuthenticator>();
250 }
RequestAuthenticator::Ptr make_platform_default_request_authenticator()
const std::string & str() const
Definition: context.cpp:31
std::shared_ptr< RequestContextResolver > Ptr
Definition: ubuntu.h:94
#define MH_DEBUG(...)
Definition: logger.h:123
RequestContextResolver::Ptr make_platform_default_request_context_resolver(helper::ExternalServices &es)
void resolve_context_for_dbus_name_async(const std::string &name, ResolveCallback) override
Definition: ubuntu.cpp:157
void get_connection_app_armor_security_async(const std::string &name, std::function< void(const std::string &)> handler)
Definition: dbus.h:76
std::function< void(const Context &)> ResolveCallback
Definition: ubuntu.h:97
Result authenticate_open_uri_request(const Context &, const std::string &uri) override
Definition: ubuntu.cpp:167
virtual std::string package_name() const
Definition: ubuntu.cpp:143
std::shared_ptr< RequestAuthenticator > Ptr
Definition: ubuntu.h:134
virtual std::string profile_name() const
Definition: ubuntu.cpp:148