1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2004-2008 Free Software Foundation Europe e.V.
5 Copyright (C) 2014-2016 Planets Communications B.V.
6 Copyright (C) 2014-2016 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, January MMIV
25 */
26 /**
27 * @file
28 * User Agent Access Control List (ACL) handling
29 */
30
31 #include "include/bareos.h"
32 #include "dird.h"
33 #include "dird/dird_globals.h"
34 #include "lib/edit.h"
35
36 #include <string>
37
38 namespace directordaemon {
39
40 /**
41 * Check if access is permitted to item in acl
42 */
AclAccessOk(int acl,const char * item,bool audit_event)43 bool UaContext::AclAccessOk(int acl, const char *item, bool audit_event)
44 {
45 return AclAccessOk(acl, item, strlen(item), audit_event);
46 }
47
48 /**
49 * Check if this is a regular expression.
50 * A regexp uses the following chars:
51 * ., (, ), [, ], |, ^, $, +, ?, *
52 */
is_regex(std::string string_to_check)53 static bool is_regex(std::string string_to_check)
54 {
55 return std::string::npos != string_to_check.find_first_of(".()[]|^$+?*");
56 }
57
58 /**
59 * Loop over the items in the alist and verify if they match the given item
60 * that access was requested for.
61 */
FindInAclList(alist * list,int acl,const char * item,int len)62 static inline bool FindInAclList(alist *list, int acl, const char *item, int len)
63 {
64 int rc;
65 regex_t preg;
66 int nmatch = 1;
67 bool retval = false;
68 regmatch_t pmatch[1];
69 const char *list_value;
70
71 /*
72 * See if we have an empty list.
73 */
74 if (!list) {
75 /*
76 * Empty list for Where => empty where accept anything.
77 * For any other list, reject everything.
78 */
79 if (len == 0 && acl == Where_ACL) {
80 Dmsg0(1400, "Empty Where_ACL allowing restore anywhere\n");
81 retval = true;
82 }
83 goto bail_out;
84 }
85
86 /*
87 * Search list for item
88 */
89 for (int i = 0; i < list->size(); i++) {
90 list_value = (char *)list->get(i);
91
92 /*
93 * See if this is a deny acl.
94 */
95 if (*list_value == '!') {
96 if (Bstrcasecmp(item, list_value + 1)) {
97 /*
98 * Explicit deny.
99 */
100 Dmsg3(1400, "Deny ACL found %s in %d %s\n", item, acl, list_value);
101 goto bail_out;
102 }
103
104 /*
105 * If we didn't get an exact match see if we can use the pattern as a regex.
106 */
107 if (is_regex(list_value + 1)) {
108 int match_length;
109
110 match_length = strlen(item);
111 rc = regcomp(&preg, list_value + 1, REG_EXTENDED | REG_ICASE);
112 if (rc != 0) {
113 /*
114 * Not a valid regular expression so skip it.
115 */
116 Dmsg1(1400, "Not a valid regex %s, ignoring for regex compare\n", list_value);
117 continue;
118 }
119
120 if (regexec(&preg, item, nmatch, pmatch, 0) == 0) {
121 /*
122 * Make sure its not a partial match but a full match.
123 */
124 Dmsg2(1400, "Found match start offset %d end offset %d\n", pmatch[0].rm_so, pmatch[0].rm_eo);
125 if ((pmatch[0].rm_eo - pmatch[0].rm_so) >= match_length) {
126 Dmsg3(1400, "ACL found %s in %d using regex %s\n", item, acl, list_value);
127 regfree(&preg);
128 goto bail_out;
129 }
130 }
131
132 regfree(&preg);
133 }
134 } else {
135 /*
136 * Special case *all* gives full access
137 */
138 if (Bstrcasecmp("*all*", list_value)) {
139 Dmsg2(1400, "Global ACL found in %d %s\n", acl, list_value);
140 retval = true;
141 goto bail_out;
142 }
143
144 if (Bstrcasecmp(item, list_value)) {
145 Dmsg3(1400, "ACL found %s in %d %s\n", item, acl, list_value);
146 retval = true;
147 goto bail_out;
148 }
149
150 /*
151 * If we didn't get an exact match see if we can use the pattern as a regex.
152 */
153 if (is_regex(list_value)) {
154 int match_length;
155
156 match_length = strlen(item);
157 rc = regcomp(&preg, list_value, REG_EXTENDED | REG_ICASE);
158 if (rc != 0) {
159 /*
160 * Not a valid regular expression so skip it.
161 */
162 Dmsg1(1400, "Not a valid regex %s, ignoring for regex compare\n", list_value);
163 continue;
164 }
165
166 if (regexec(&preg, item, nmatch, pmatch, 0) == 0) {
167 /*
168 * Make sure its not a partial match but a full match.
169 */
170 Dmsg2(1400, "Found match start offset %d end offset %d\n", pmatch[0].rm_so, pmatch[0].rm_eo);
171 if ((pmatch[0].rm_eo - pmatch[0].rm_so) >= match_length) {
172 Dmsg3(1400, "ACL found %s in %d using regex %s\n", item, acl, list_value);
173 retval = true;
174 regfree(&preg);
175 goto bail_out;
176 }
177 }
178
179 regfree(&preg);
180 }
181 }
182 }
183
184 bail_out:
185 return retval;
186 }
187
188 /**
189 * This version expects the length of the item which we must check.
190 */
AclAccessOk(int acl,const char * item,int len,bool audit_event)191 bool UaContext::AclAccessOk(int acl, const char *item, int len, bool audit_event)
192 {
193 bool retval = false;
194
195 /*
196 * The resource name contains nasty characters
197 */
198 switch (acl) {
199 case Where_ACL:
200 case PluginOptions_ACL:
201 break;
202 default:
203 if (!IsNameValid(item)) {
204 Dmsg1(1400, "Access denied for item=%s\n", item);
205 goto bail_out;
206 }
207 break;
208 }
209
210 /*
211 * If no console resource => default console and all is permitted
212 */
213 if (!cons) {
214 Dmsg0(1400, "Root cons access OK.\n");
215 retval = true;
216 goto bail_out;
217 }
218
219 retval = FindInAclList(cons->ACL_lists[acl], acl, item, len);
220
221 /*
222 * If we didn't find a matching ACL try to use the profiles this console is connected to.
223 */
224 if (!retval && cons->profiles && cons->profiles->size()) {
225 ProfileResource *profile = nullptr;
226
227 foreach_alist(profile, cons->profiles) {
228 retval = FindInAclList(profile->ACL_lists[acl], acl, item, len);
229
230 /*
231 * If we found a match break the loop.
232 */
233 if (retval) {
234 break;
235 }
236 }
237 }
238
239 bail_out:
240 if (audit_event && !retval) {
241 LogAuditEventAclFailure(acl, item);
242 }
243
244 return retval;
245 }
246
247 /**
248 * This function returns if the authentication has any acl restrictions for a certain acltype.
249 */
AclNoRestrictions(int acl)250 bool UaContext::AclNoRestrictions(int acl)
251 {
252 const char *list_value;
253 ProfileResource *profile = nullptr;
254
255 /*
256 * If no console resource => default console and all is permitted
257 */
258 if (!cons) {
259 return true;
260 }
261
262 if (cons->ACL_lists[acl]) {
263 for (int i = 0; i < cons->ACL_lists[acl]->size(); i++) {
264 list_value = (char *)cons->ACL_lists[acl]->get(i);
265
266 if (*list_value == '!') {
267 return false;
268 }
269
270 if (Bstrcasecmp("*all*", list_value)) {
271 return true;
272 }
273 }
274 }
275
276 foreach_alist(profile, cons->profiles) {
277 if (profile) {
278 if (profile->ACL_lists[acl]) {
279 for (int i = 0; i < profile->ACL_lists[acl]->size(); i++) {
280 list_value = (char *)profile->ACL_lists[acl]->get(i);
281
282 if (*list_value == '!') {
283 return false;
284 }
285
286 if (Bstrcasecmp("*all*", list_value)) {
287 return true;
288 }
289 } /* for (int i = 0; */
290 } /* if (profile->ACL_lists[acl]) */
291 } /* if (profile) */
292 }
293
294 return false;
295 }
296
RcodeToAcltype(int rcode)297 int UaContext::RcodeToAcltype(int rcode)
298 {
299 int acl = -1;
300
301 switch (rcode) {
302 case R_CLIENT:
303 acl = Client_ACL;
304 break;
305 case R_JOBDEFS:
306 case R_JOB:
307 acl = Job_ACL;
308 break;
309 case R_STORAGE:
310 acl = Storage_ACL;
311 break;
312 case R_CATALOG:
313 acl = Catalog_ACL;
314 break;
315 case R_SCHEDULE:
316 acl = Schedule_ACL;
317 break;
318 case R_FILESET:
319 acl = FileSet_ACL;
320 break;
321 case R_POOL:
322 acl = Pool_ACL;
323 break;
324 default:
325 break;
326 }
327
328 return acl;
329 }
330
331 /**
332 * This checks the right ACL if the UA has access to the wanted resource.
333 */
IsResAllowed(CommonResourceHeader * res)334 bool UaContext::IsResAllowed(CommonResourceHeader *res)
335 {
336 int acl;
337
338 acl = RcodeToAcltype(res->rcode);
339 if (acl == -1) {
340 /*
341 * For all resources for which we don't know an explicit mapping
342 * to the right ACL we check if the Command ACL has access to the
343 * configure command just as we do for suppressing sensitive data.
344 */
345 return AclAccessOk(Command_ACL, "configure", false);
346 }
347
348 return AclAccessOk(acl, res->name, false);
349 }
350
351 /**
352 * Try to get a resource and make sure the current ACL allows it to be retrieved.
353 */
GetResWithName(int rcode,const char * name,bool audit_event,bool lock)354 CommonResourceHeader *UaContext::GetResWithName(int rcode, const char *name, bool audit_event, bool lock)
355 {
356 int acl;
357
358 acl = RcodeToAcltype(rcode);
359 if (acl == -1) {
360 /*
361 * For all resources for which we don't know an explicit mapping
362 * to the right ACL we check if the Command ACL has access to the
363 * configure command just as we do for suppressing sensitive data.
364 */
365 if (!AclAccessOk(Command_ACL, "configure", false)) {
366 goto bail_out;
367 }
368 } else {
369 if (!AclAccessOk(acl, name, audit_event)) {
370 goto bail_out;
371 }
372 }
373
374 return my_config->GetResWithName(rcode, name, lock);
375
376 bail_out:
377 return NULL;
378 }
379
GetPoolResWithName(const char * name,bool audit_event,bool lock)380 PoolResource *UaContext::GetPoolResWithName(const char *name, bool audit_event, bool lock)
381 {
382 return (PoolResource *)GetResWithName(R_POOL, name, audit_event, lock);
383 }
384
GetStoreResWithName(const char * name,bool audit_event,bool lock)385 StorageResource *UaContext::GetStoreResWithName(const char *name, bool audit_event, bool lock)
386 {
387 return (StorageResource *)GetResWithName(R_STORAGE, name, audit_event, lock);
388 }
389
GetStoreResWithId(DBId_t id,bool audit_event,bool lock)390 StorageResource *UaContext::GetStoreResWithId(DBId_t id, bool audit_event, bool lock)
391 {
392 StorageDbRecord storage_dbr;
393 memset(&storage_dbr, 0, sizeof(storage_dbr));
394
395 storage_dbr.StorageId = id;
396 if (db->GetStorageRecord(jcr, &storage_dbr)) {
397 return GetStoreResWithName(storage_dbr.Name, audit_event, lock);
398 }
399 return NULL;
400 }
401
GetClientResWithName(const char * name,bool audit_event,bool lock)402 ClientResource *UaContext::GetClientResWithName(const char *name, bool audit_event, bool lock)
403 {
404 return (ClientResource *)GetResWithName(R_CLIENT, name, audit_event, lock);
405 }
406
GetJobResWithName(const char * name,bool audit_event,bool lock)407 JobResource *UaContext::GetJobResWithName(const char *name, bool audit_event, bool lock)
408 {
409 return (JobResource *)GetResWithName(R_JOB, name, audit_event, lock);
410 }
411
GetFileSetResWithName(const char * name,bool audit_event,bool lock)412 FilesetResource *UaContext::GetFileSetResWithName(const char *name, bool audit_event, bool lock)
413 {
414 return (FilesetResource *)GetResWithName(R_FILESET, name, audit_event, lock);
415 }
416
GetCatalogResWithName(const char * name,bool audit_event,bool lock)417 CatalogResource *UaContext::GetCatalogResWithName(const char *name, bool audit_event, bool lock)
418 {
419 return (CatalogResource *)GetResWithName(R_CATALOG, name, audit_event, lock);
420 }
421
GetScheduleResWithName(const char * name,bool audit_event,bool lock)422 ScheduleResource *UaContext::GetScheduleResWithName(const char *name, bool audit_event, bool lock)
423 {
424 return (ScheduleResource *)GetResWithName(R_SCHEDULE, name, audit_event, lock);
425 }
426
427 } /* namespace directordaemon */
428