]> granicus.if.org Git - shadow/blob - lib/subordinateio.c
dd779c5966f920616c2af97caaa6793d3de34892
[shadow] / lib / subordinateio.c
1 /*
2  * Copyright (c) 2012 - Eric Biederman
3  */
4
5 #include <config.h>
6
7 #ifdef ENABLE_SUBIDS
8
9 #include "prototypes.h"
10 #include "defines.h"
11 #include <stdio.h>
12 #include "commonio.h"
13 #include "subordinateio.h"
14 #include <sys/types.h>
15 #include <pwd.h>
16
17 struct subordinate_range {
18         const char *owner;
19         unsigned long start;
20         unsigned long count;
21 };
22
23 #define NFIELDS 3
24
25 /*
26  * subordinate_dup: create a duplicate range
27  *
28  * @ent: a pointer to a subordinate_range struct
29  *
30  * Returns a pointer to a newly allocated duplicate subordinate_range struct
31  * or NULL on failure
32  */
33 static /*@null@*/ /*@only@*/void *subordinate_dup (const void *ent)
34 {
35         const struct subordinate_range *rangeent = ent;
36         struct subordinate_range *range;
37
38         range = (struct subordinate_range *) malloc (sizeof *range);
39         if (NULL == range) {
40                 return NULL;
41         }
42         range->owner = strdup (rangeent->owner);
43         if (NULL == range->owner) {
44                 free(range);
45                 return NULL;
46         }
47         range->start = rangeent->start;
48         range->count = rangeent->count;
49
50         return range;
51 }
52
53 /*
54  * subordinate_free: free a subordinate_range struct
55  *
56  * @ent: pointer to a subordinate_range struct to free.
57  */
58 static void subordinate_free (/*@out@*/ /*@only@*/void *ent)
59 {
60         struct subordinate_range *rangeent = ent;
61         
62         free ((void *)(rangeent->owner));
63         free (rangeent);
64 }
65
66 /*
67  * subordinate_parse:
68  *
69  * @line: a line to parse
70  *
71  * Returns a pointer to a subordinate_range struct representing the values
72  * in @line, or NULL on failure.  Note that the returned value should not
73  * be freed by the caller.
74  */
75 static void *subordinate_parse (const char *line)
76 {
77         static struct subordinate_range range;
78         static char rangebuf[1024];
79         int i;
80         char *cp;
81         char *fields[NFIELDS];
82
83         /*
84          * Copy the string to a temporary buffer so the substrings can
85          * be modified to be NULL terminated.
86          */
87         if (strlen (line) >= sizeof rangebuf)
88                 return NULL;    /* fail if too long */
89         strcpy (rangebuf, line);
90
91         /*
92          * Save a pointer to the start of each colon separated
93          * field.  The fields are converted into NUL terminated strings.
94          */
95
96         for (cp = rangebuf, i = 0; (i < NFIELDS) && (NULL != cp); i++) {
97                 fields[i] = cp;
98                 while (('\0' != *cp) && (':' != *cp)) {
99                         cp++;
100                 }
101
102                 if ('\0' != *cp) {
103                         *cp = '\0';
104                         cp++;
105                 } else {
106                         cp = NULL;
107                 }
108         }
109
110         /*
111          * There must be exactly NFIELDS colon separated fields or
112          * the entry is invalid.  Also, fields must be non-blank.
113          */
114         if (i != NFIELDS || *fields[0] == '\0' || *fields[1] == '\0' || *fields[2] == '\0')
115                 return NULL;
116         range.owner = fields[0];
117         if (getulong (fields[1], &range.start) == 0)
118                 return NULL;
119         if (getulong (fields[2], &range.count) == 0)
120                 return NULL;
121
122         return &range;
123 }
124
125 /*
126  * subordinate_put: print a subordinate_range value to a file
127  *
128  * @ent: a pointer to a subordinate_range struct to print out.
129  * @file: file to which to print.
130  *
131  * Returns 0 on success, -1 on error.
132  */
133 static int subordinate_put (const void *ent, FILE * file)
134 {
135         const struct subordinate_range *range = ent;
136
137         return fprintf(file, "%s:%lu:%lu\n",
138                                range->owner,
139                                range->start,
140                                range->count) < 0 ? -1  : 0;
141 }
142
143 static struct commonio_ops subordinate_ops = {
144         subordinate_dup,        /* dup */
145         subordinate_free,       /* free */
146         NULL,                   /* getname */
147         subordinate_parse,      /* parse */
148         subordinate_put,        /* put */
149         fgets,                  /* fgets */
150         fputs,                  /* fputs */
151         NULL,                   /* open_hook */
152         NULL,                   /* close_hook */
153 };
154
155 static /*@observer@*/ /*@null*/const struct subordinate_range *subordinate_next(struct commonio_db *db)
156 {
157         return (const struct subordinate_range *)commonio_next (db);
158 }
159
160 /*
161  * range_exists: Check whether @owner owns any ranges
162  *
163  * @db: database to query
164  * @owner: owner being queried
165  *
166  * Returns true if @owner owns any subuid ranges, false otherwise.
167  */
168 static const bool range_exists(struct commonio_db *db, const char *owner)
169 {
170         const struct subordinate_range *range;
171         commonio_rewind(db);
172         while ((range = commonio_next(db)) != NULL) {
173                 if (0 == strcmp(range->owner, owner))
174                         return true;
175         }
176         return false;
177 }
178
179 /*
180  * find_range: find a range which @owner is authorized to use which includes
181  *             subuid @val.
182  *
183  * @db: database to query
184  * @owner: owning uid being queried
185  * @val: subuid being searched for.
186  *
187  * Returns a range of subuids belonging to @owner and including the subuid
188  * @val, or NULL if no such range exists.
189  */
190 static const struct subordinate_range *find_range(struct commonio_db *db,
191                                                   const char *owner, unsigned long val)
192 {
193         const struct subordinate_range *range;
194
195         /*
196          * Search for exact username/group specification
197          *
198          * This is the original method - go fast through the db, doing only
199          * exact username/group string comparison. Therefore we leave it as-is
200          * for the time being, in order to keep it equally fast as it was
201          * before.
202          */
203         commonio_rewind(db);
204         while ((range = commonio_next(db)) != NULL) {
205                 unsigned long first = range->start;
206                 unsigned long last = first + range->count - 1;
207
208                 if (0 != strcmp(range->owner, owner))
209                         continue;
210
211                 if ((val >= first) && (val <= last))
212                         return range;
213         }
214
215
216         /*
217          * We only do special handling for these two files
218          */
219         if ((0 != strcmp(db->filename, "/etc/subuid")) && (0 != strcmp(db->filename, "/etc/subgid")))
220                 return NULL;
221
222         /*
223          * Search loop above did not produce any result. Let's rerun it,
224          * but this time try to match actual UIDs. The first entry that
225          * matches is considered a success.
226          * (It may be specified as literal UID or as another username which
227          * has the same UID as the username we are looking for.)
228          */
229         struct passwd *pwd;
230         uid_t          owner_uid;
231         char           owner_uid_string[33] = "";
232
233
234         /* Get UID of the username we are looking for */
235         pwd = getpwnam(owner);
236         if (NULL == pwd) {
237                 /* Username not defined in /etc/passwd, or error occured during lookup */
238                 return NULL;
239         }
240         owner_uid = pwd->pw_uid;
241         sprintf(owner_uid_string, "%lu", (unsigned long int)owner_uid);
242
243         commonio_rewind(db);
244         while ((range = commonio_next(db)) != NULL) {
245                 unsigned long first = range->start;
246                 unsigned long last = first + range->count - 1;
247
248                 /* For performance reasons check range before using getpwnam() */
249                 if ((val < first) || (val > last)) {
250                         continue;
251                 }
252
253                 /*
254                  * Range matches. Check if range owner is specified
255                  * as numeric UID and if it matches.
256                  */
257                 if (0 == strcmp(range->owner, owner_uid_string)) {
258                         return range;
259                 }
260
261                 /*
262                  * Ok, this range owner is not specified as numeric UID
263                  * we are looking for. It may be specified as another
264                  * UID or as a literal username.
265                  *
266                  * If specified as another UID, the call to getpwnam()
267                  * will return NULL.
268                  *
269                  * If specified as literal username, we will get its
270                  * UID and compare that to UID we are looking for.
271                  */
272                 const struct passwd *range_owner_pwd;
273
274                 range_owner_pwd = getpwnam(range->owner);
275                 if (NULL == range_owner_pwd) {
276                         continue;
277                 }
278
279                 if (owner_uid == range_owner_pwd->pw_uid) {
280                         return range;
281                 }
282         }
283
284         return NULL;
285 }
286
287 /*
288  * have_range: check whether @owner is authorized to use the range
289  *             (@start .. @start+@count-1).
290  * @db: database to check
291  * @owner: owning uid being queried
292  * @start: start of range
293  * @count: number of uids in range
294  *
295  * Returns true if @owner is authorized to use the range, false otherwise.
296  */
297 static bool have_range(struct commonio_db *db,
298                        const char *owner, unsigned long start, unsigned long count)
299 {
300         const struct subordinate_range *range;
301         unsigned long end;
302
303         if (count == 0)
304                 return false;
305
306         end = start + count - 1;
307         range = find_range (db, owner, start);
308         while (range) {
309                 unsigned long last; 
310
311                 last = range->start + range->count - 1;
312                 if (last >= (start + count - 1))
313                         return true;
314
315                 count = end - last;
316                 start = last + 1;
317                 range = find_range(db, owner, start);
318         }
319         return false;
320 }
321
322 /*
323  * subordinate_range_cmp: compare uid ranges
324  *
325  * @p1: pointer to a commonio_entry struct to compare
326  * @p2: pointer to second commonio_entry struct to compare
327  *
328  * Returns 0 if the entries are the same.  Otherwise return -1
329  * if the range in p1 is lower than that in p2, or (if the ranges are
330  * equal) if the owning uid in p1 is lower than p2's.  Return 1 if p1's
331  * range or owning uid is great than p2's.
332  */
333 static int subordinate_range_cmp (const void *p1, const void *p2)
334 {
335         struct subordinate_range *range1, *range2;
336
337         if ((*(struct commonio_entry **) p1)->eptr == NULL)
338                 return 1;
339         if ((*(struct commonio_entry **) p2)->eptr == NULL)
340                 return -1;
341
342         range1 = ((struct subordinate_range *) (*(struct commonio_entry **) p1)->eptr);
343         range2 = ((struct subordinate_range *) (*(struct commonio_entry **) p2)->eptr);
344
345         if (range1->start < range2->start)
346                 return -1;
347         else if (range1->start > range2->start)
348                 return 1;
349         else if (range1->count < range2->count)
350                 return -1;
351         else if (range1->count > range2->count)
352                 return 1;
353         else
354                 return strcmp(range1->owner, range2->owner);
355 }
356
357 /*
358  * find_free_range: find an unused consecutive sequence of ids to allocate
359  *                  to a user.
360  * @db: database to search
361  * @min: the first uid in the range to find
362  * @max: the highest uid to find
363  * @count: the number of uids needed
364  *
365  * Return the lowest new uid, or ULONG_MAX on failure.
366  */
367 static unsigned long find_free_range(struct commonio_db *db,
368                                      unsigned long min, unsigned long max,
369                                      unsigned long count)
370 {
371         const struct subordinate_range *range;
372         unsigned long low, high;
373
374         /* When given invalid parameters fail */
375         if ((count == 0) || (max < min))
376                 goto fail;
377
378         /* Sort by range then by owner */
379         commonio_sort (db, subordinate_range_cmp);
380         commonio_rewind(db);
381
382         low = min;
383         while ((range = commonio_next(db)) != NULL) {
384                 unsigned long first = range->start;
385                 unsigned long last = first + range->count - 1;
386
387                 /* Find the top end of the hole before this range */
388                 high = first;
389
390                 /* Don't allocate IDs after max (included) */
391                 if (high > max + 1) {
392                         high = max + 1;
393                 }
394
395                 /* Is the hole before this range large enough? */
396                 if ((high > low) && ((high - low) >= count))
397                         return low;
398
399                 /* Compute the low end of the next hole */
400                 if (low < (last + 1))
401                         low = last + 1;
402                 if (low > max)
403                         goto fail;
404         }
405
406         /* Is the remaining unclaimed area large enough? */
407         if (((max - low) + 1) >= count)
408                 return low;
409 fail:
410         return ULONG_MAX;
411 }
412
413 /*
414  * add_range: add a subuid range to an owning uid's list of authorized
415  *            subuids.
416  * @db: database to which to add
417  * @owner: uid which owns the subuid
418  * @start: the first uid in the owned range
419  * @count: the number of uids in the range
420  *
421  * Return 1 if the range is already present or on success.  On error
422  * return 0 and set errno appropriately.
423  */
424 static int add_range(struct commonio_db *db,
425         const char *owner, unsigned long start, unsigned long count)
426 {
427         struct subordinate_range range;
428         range.owner = owner;
429         range.start = start;
430         range.count = count;
431
432         /* See if the range is already present */
433         if (have_range(db, owner, start, count))
434                 return 1;
435
436         /* Otherwise append the range */
437         return commonio_append(db, &range);
438 }
439
440 /*
441  * remove_range:  remove a range of subuids from an owning uid's list
442  *                of authorized subuids.
443  * @db: database to work on
444  * @owner: owning uid whose range is being removed
445  * @start: start of the range to be removed
446  * @count: number of uids in the range.
447  *
448  * Returns 0 on failure, 1 on success.  Failure means that we needed to
449  * create a new range to represent the new limits, and failed doing so.
450  */
451 static int remove_range (struct commonio_db *db,
452                          const char *owner,
453                          unsigned long start, unsigned long count)
454 {
455         struct commonio_entry *ent;
456         unsigned long end;
457
458         if (count == 0) {
459                 return 1;
460         }
461
462         end = start + count - 1;
463         for (ent = db->head; NULL != ent; ent = ent->next) {
464                 struct subordinate_range *range = ent->eptr;
465                 unsigned long first;
466                 unsigned long last;
467
468                 /* Skip unparsed entries */
469                 if (NULL == range) {
470                         continue;
471                 }
472
473                 first = range->start;
474                 last = first + range->count - 1;
475
476                 /* Skip entries with a different owner */
477                 if (0 != strcmp (range->owner, owner)) {
478                         continue;
479                 }
480
481                 /* Skip entries outside of the range to remove */
482                 if ((end < first) || (start > last)) {
483                         continue;
484                 }
485
486                 if (start <= first) {
487                         if (end >= last) {
488                                 /* to be removed: [start,      end]
489                                  * range:           [first, last] */
490                                 /* entry completely contained in the
491                                  * range to remove */
492                                 commonio_del_entry (db, ent);
493                         } else {
494                                 /* to be removed: [start,  end]
495                                  * range:           [first, last] */
496                                 /* Remove only the start of the entry */
497                                 range->start = end + 1;
498                                 range->count = (last - range->start) + 1;
499
500                                 ent->changed = true;
501                                 db->changed = true;
502                         }
503                 } else {
504                         if (end >= last) {
505                                 /* to be removed:   [start,  end]
506                                  * range:         [first, last] */
507                                 /* Remove only the end of the entry */
508                                 range->count = start - range->start;
509
510                                 ent->changed = true;
511                                 db->changed = true;
512                         } else {
513                                 /* to be removed:   [start, end]
514                                  * range:         [first,    last] */
515                                 /* Remove the middle of the range
516                                  * This requires to create a new range */
517                                 struct subordinate_range tail;
518                                 tail.owner = range->owner;
519                                 tail.start = end + 1;
520                                 tail.count = (last - tail.start) + 1;
521
522                                 if (commonio_append (db, &tail) == 0) {
523                                         return 0;
524                                 }
525
526                                 range->count = start - range->start;
527
528                                 ent->changed = true;
529                                 db->changed = true;
530                         }
531                 }
532         }
533
534         return 1;
535 }
536
537 static struct commonio_db subordinate_uid_db = {
538         "/etc/subuid",          /* filename */
539         &subordinate_ops,       /* ops */
540         NULL,                   /* fp */
541 #ifdef WITH_SELINUX
542         NULL,                   /* scontext */
543 #endif
544         0644,                   /* st_mode */
545         0,                      /* st_uid */
546         0,                      /* st_gid */
547         NULL,                   /* head */
548         NULL,                   /* tail */
549         NULL,                   /* cursor */
550         false,                  /* changed */
551         false,                  /* isopen */
552         false,                  /* locked */
553         false,                  /* readonly */
554         false                   /* setname */
555 };
556
557 int sub_uid_setdbname (const char *filename)
558 {
559         return commonio_setname (&subordinate_uid_db, filename);
560 }
561
562 /*@observer@*/const char *sub_uid_dbname (void)
563 {
564         return subordinate_uid_db.filename;
565 }
566
567 bool sub_uid_file_present (void)
568 {
569         return commonio_present (&subordinate_uid_db);
570 }
571
572 int sub_uid_lock (void)
573 {
574         return commonio_lock (&subordinate_uid_db);
575 }
576
577 int sub_uid_open (int mode)
578 {
579         return commonio_open (&subordinate_uid_db, mode);
580 }
581
582 bool sub_uid_assigned(const char *owner)
583 {
584         return range_exists (&subordinate_uid_db, owner);
585 }
586
587 bool have_sub_uids(const char *owner, uid_t start, unsigned long count)
588 {
589         return have_range (&subordinate_uid_db, owner, start, count);
590 }
591
592 int sub_uid_add (const char *owner, uid_t start, unsigned long count)
593 {
594         return add_range (&subordinate_uid_db, owner, start, count);
595 }
596
597 int sub_uid_remove (const char *owner, uid_t start, unsigned long count)
598 {
599         return remove_range (&subordinate_uid_db, owner, start, count);
600 }
601
602 int sub_uid_close (void)
603 {
604         return commonio_close (&subordinate_uid_db);
605 }
606
607 int sub_uid_unlock (void)
608 {
609         return commonio_unlock (&subordinate_uid_db);
610 }
611
612 uid_t sub_uid_find_free_range(uid_t min, uid_t max, unsigned long count)
613 {
614         unsigned long start;
615         start = find_free_range (&subordinate_uid_db, min, max, count);
616         return start == ULONG_MAX ? (uid_t) -1 : start;
617 }
618
619 static struct commonio_db subordinate_gid_db = {
620         "/etc/subgid",          /* filename */
621         &subordinate_ops,       /* ops */
622         NULL,                   /* fp */
623 #ifdef WITH_SELINUX
624         NULL,                   /* scontext */
625 #endif
626         0644,                   /* st_mode */
627         0,                      /* st_uid */
628         0,                      /* st_gid */
629         NULL,                   /* head */
630         NULL,                   /* tail */
631         NULL,                   /* cursor */
632         false,                  /* changed */
633         false,                  /* isopen */
634         false,                  /* locked */
635         false,                  /* readonly */
636         false                   /* setname */
637 };
638
639 int sub_gid_setdbname (const char *filename)
640 {
641         return commonio_setname (&subordinate_gid_db, filename);
642 }
643
644 /*@observer@*/const char *sub_gid_dbname (void)
645 {
646         return subordinate_gid_db.filename;
647 }
648
649 bool sub_gid_file_present (void)
650 {
651         return commonio_present (&subordinate_gid_db);
652 }
653
654 int sub_gid_lock (void)
655 {
656         return commonio_lock (&subordinate_gid_db);
657 }
658
659 int sub_gid_open (int mode)
660 {
661         return commonio_open (&subordinate_gid_db, mode);
662 }
663
664 bool have_sub_gids(const char *owner, gid_t start, unsigned long count)
665 {
666         return have_range(&subordinate_gid_db, owner, start, count);
667 }
668
669 bool sub_gid_assigned(const char *owner)
670 {
671         return range_exists (&subordinate_gid_db, owner);
672 }
673
674 int sub_gid_add (const char *owner, gid_t start, unsigned long count)
675 {
676         return add_range (&subordinate_gid_db, owner, start, count);
677 }
678
679 int sub_gid_remove (const char *owner, gid_t start, unsigned long count)
680 {
681         return remove_range (&subordinate_gid_db, owner, start, count);
682 }
683
684 int sub_gid_close (void)
685 {
686         return commonio_close (&subordinate_gid_db);
687 }
688
689 int sub_gid_unlock (void)
690 {
691         return commonio_unlock (&subordinate_gid_db);
692 }
693
694 gid_t sub_gid_find_free_range(gid_t min, gid_t max, unsigned long count)
695 {
696         unsigned long start;
697         start = find_free_range (&subordinate_gid_db, min, max, count);
698         return start == ULONG_MAX ? (gid_t) -1 : start;
699 }
700 #else                           /* !ENABLE_SUBIDS */
701 extern int errno;               /* warning: ANSI C forbids an empty source file */
702 #endif                          /* !ENABLE_SUBIDS */
703