]> granicus.if.org Git - shadow/blob - libmisc/find_new_ids.c
* libmisc/getlong.c: Reset errno before calling strtol().
[shadow] / libmisc / find_new_ids.c
1 /*
2  * Copyright (c) 1991 - 1994, Julianne Frances Haugh
3  * Copyright (c) 2008       , Nicolas François
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the copyright holders or contributors may not be used to
15  *    endorse or promote products derived from this software without
16  *    specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
21  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
22  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include <config.h>
32
33 #include <assert.h>
34 #include <stdio.h>
35
36 #include "prototypes.h"
37 #include "pwio.h"
38 #include "groupio.h"
39 #include "getdef.h"
40
41 /*
42  * find_new_uid - Find a new unused UID.
43  *
44  * If successful, find_new_uid provides an unused user ID in the
45  * [UID_MIN:UID_MAX] range.
46  * This ID should be higher than all the used UID, but if not possible,
47  * the lowest unused ID in the range will be returned.
48  * 
49  * Return 0 on success, -1 if no unused UIDs are available.
50  */
51 int find_new_uid (bool sys_user, uid_t *uid, uid_t const *preferred_uid)
52 {
53         const struct passwd *pwd;
54         uid_t uid_min, uid_max, user_id;
55
56         assert (uid != NULL);
57
58         if (!sys_user) {
59                 uid_min = getdef_ulong ("UID_MIN", 1000L);
60                 uid_max = getdef_ulong ("UID_MAX", 60000L);
61         } else {
62                 uid_min = getdef_ulong ("SYS_UID_MIN", 1L);
63                 uid_max = getdef_ulong ("UID_MIN", 1000L) - 1;
64                 uid_max = getdef_ulong ("SYS_UID_MAX", (unsigned long) uid_max);
65         }
66
67         if (   (NULL != preferred_uid)
68             && (*preferred_uid >= uid_min)
69             && (*preferred_uid <= uid_max)
70             /* Check if the user exists according to NSS */
71             && (getpwuid (*preferred_uid) == NULL)
72             /* Check also the local database in case of uncommitted
73              * changes */
74             && (pw_locate_uid (*preferred_uid) == NULL)) {
75                 *uid = *preferred_uid;
76                 return 0;
77         }
78
79
80         user_id = uid_min;
81
82         /*
83          * Search the entire password file,
84          * looking for the largest unused value.
85          *
86          * We check the list of users according to NSS (setpwent/getpwent),
87          * but we also check the local database (pw_rewind/pw_next) in case
88          * some users were created but the changes were not committed yet.
89          */
90         setpwent ();
91         pw_rewind ();
92         while (   ((pwd = getpwent ()) != NULL)
93                || ((pwd = pw_next ()) != NULL)) {
94                 if ((pwd->pw_uid >= user_id) && (pwd->pw_uid <= uid_max)) {
95                         user_id = pwd->pw_uid + 1;
96                 }
97         }
98         endpwent ();
99
100         /*
101          * If a user with UID equal to UID_MAX exists, the above algorithm
102          * will give us UID_MAX+1 even if not unique. Search for the first
103          * free UID starting with UID_MIN (it's O(n*n) but can be avoided
104          * by not having users with UID equal to UID_MAX).  --marekm
105          */
106         if (user_id == uid_max + 1) {
107                 for (user_id = uid_min; user_id < uid_max; user_id++) {
108                         /* local, no need for xgetpwuid */
109                         if (   (getpwuid (user_id) == NULL)
110                             && (pw_locate_uid (user_id) == NULL)) {
111                                 break;
112                         }
113                 }
114                 if (user_id == uid_max) {
115                         fputs (_("Can't get unique UID (no more available UIDs)\n"), stderr);
116                         return -1;
117                 }
118         }
119
120         *uid = user_id;
121         return 0;
122 }
123
124 /*
125  * find_new_gid - Find a new unused GID.
126  *
127  * If successful, find_new_gid provides an unused group ID in the
128  * [GID_MIN:GID_MAX] range.
129  * This ID should be higher than all the used GID, but if not possible,
130  * the lowest unused ID in the range will be returned.
131  * 
132  * Return 0 on success, -1 if no unused GIDs are available.
133  */
134 int find_new_gid (bool sys_group, gid_t *gid, gid_t const *preferred_gid)
135 {
136         const struct group *grp;
137         gid_t gid_min, gid_max, group_id;
138
139         assert (gid != NULL);
140
141         if (!sys_group) {
142                 gid_min = getdef_ulong ("GID_MIN", 1000L);
143                 gid_max = getdef_ulong ("GID_MAX", 60000L);
144         } else {
145                 gid_min = getdef_ulong ("SYS_GID_MIN", 1L);
146                 gid_max = getdef_ulong ("GID_MIN", 1000L) - 1;
147                 gid_max = getdef_ulong ("SYS_GID_MAX", (unsigned long) gid_max);
148         }
149
150         if (   (NULL != preferred_gid)
151             && (*preferred_gid >= gid_min)
152             && (*preferred_gid <= gid_max)
153             /* Check if the user exists according to NSS */
154             && (getgrgid (*preferred_gid) == NULL)
155             /* Check also the local database in case of uncommitted
156              * changes */
157             && (gr_locate_gid (*preferred_gid) == NULL)) {
158                 *gid = *preferred_gid;
159                 return 0;
160         }
161
162         group_id = gid_min;
163
164         /*
165          * Search the entire group file,
166          * looking for the largest unused value.
167          *
168          * We check the list of users according to NSS (setpwent/getpwent),
169          * but we also check the local database (pw_rewind/pw_next) in case
170          * some groups were created but the changes were not committed yet.
171          */
172         setgrent ();
173         gr_rewind ();
174         while (   ((grp = getgrent ()) != NULL)
175                || ((grp = gr_next ()) != NULL)) {
176                 if ((grp->gr_gid >= group_id) && (grp->gr_gid <= gid_max)) {
177                         group_id = grp->gr_gid + 1;
178                 }
179         }
180         endgrent ();
181
182         /*
183          * If a group with GID equal to GID_MAX exists, the above algorithm
184          * will give us GID_MAX+1 even if not unique. Search for the first
185          * free GID starting with GID_MIN (it's O(n*n) but can be avoided
186          * by not having users with GID equal to GID_MAX).  --marekm
187          */
188         if (group_id == gid_max + 1) {
189                 for (group_id = gid_min; group_id < gid_max; group_id++) {
190                         /* local, no need for xgetgrgid */
191                         if (   (getgrgid (group_id) == NULL)
192                             && (gr_locate_gid (group_id) == NULL)) {
193                                 break;
194                         }
195                 }
196                 if (group_id == gid_max) {
197                         fputs (_("Can't get unique GID (no more available GIDs)\n"), stderr);
198                         return -1;
199                 }
200         }
201
202         *gid = group_id;
203         return 0;
204 }
205