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