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