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