1 /*
2 SSSD
3
4 sss_useradd
5
6 Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 2009
7 Copyright (C) Simo Sorce <ssorce@redhat.com> 2009
8
9 This program is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 3 of the License, or
12 (at your option) any later version.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <talloc.h>
26 #include <popt.h>
27 #include <errno.h>
28 #include <unistd.h>
29
30 #include "util/util.h"
31 #include "db/sysdb.h"
32 #include "tools/tools_util.h"
33 #include "tools/sss_sync_ops.h"
34
35 static void get_gid_callback(void *ptr, int error, struct ldb_result *res)
36 {
37 struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx);
38
39 if (error) {
40 tctx->error = error;
41 return;
42 }
43
44 switch (res->count) {
45 case 0:
46 tctx->error = ENOENT;
47 break;
48
49 case 1:
50 tctx->octx->gid = ldb_msg_find_attr_as_uint(res->msgs[0], SYSDB_GIDNUM, 0);
51 if (tctx->octx->gid == 0) {
52 tctx->error = ERANGE;
53 }
54 break;
55
56 default:
57 tctx->error = EFAULT;
58 break;
59 }
60 }
61
62 /* Returns a gid for a given groupname. If a numerical gid
63 * is given, returns that as integer (rationale: shadow-utils)
64 * On error, returns -EINVAL
65 */
66 static int get_gid(struct tools_ctx *tctx, const char *groupname)
67 {
68 char *end_ptr;
69 int ret;
70
71 errno = 0;
72 tctx->octx->gid = strtoul(groupname, &end_ptr, 10);
73 if (groupname == '\0' || *end_ptr != '\0' ||
74 errno != 0 || tctx->octx->gid == 0) {
75 /* Does not look like a gid - find the group name */
76
77 ret = sysdb_getgrnam(tctx->octx, tctx->sysdb,
78 tctx->octx->domain, groupname,
79 get_gid_callback, tctx);
80 if (ret != EOK) {
81 DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
82 goto done;
83 }
84
85 tctx->error = EOK;
86 tctx->octx->gid = 0;
87 while ((tctx->error == EOK) && (tctx->octx->gid == 0)) {
88 tevent_loop_once(tctx->ev);
89 }
90
91 if (tctx->error) {
92 DEBUG(1, ("sysdb_getgrnam failed: %d\n", ret));
93 goto done;
94 }
95 }
96
97 done:
98 return ret;
99 }
100
101 int main(int argc, const char **argv)
102 {
103 uid_t pc_uid = 0;
104 const char *pc_group = NULL;
105 const char *pc_gecos = NULL;
106 const char *pc_home = NULL;
107 char *pc_shell = NULL;
108 int pc_debug = 0;
109 int pc_create_home = 0;
110 const char *pc_username = NULL;
111 const char *pc_skeldir = NULL;
112 const char *pc_selinux_user = NULL;
113 struct poptOption long_options[] = {
114 POPT_AUTOHELP
115 { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL },
116 { "uid", 'u', POPT_ARG_INT, &pc_uid, 0, _("The UID of the user"), NULL },
117 { "gid", 'g', POPT_ARG_STRING, &pc_group, 0, _("The GID or group name of the user"), NULL },
118 { "gecos", 'c', POPT_ARG_STRING, &pc_gecos, 0, _("The comment string"), NULL },
119 { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL },
120 { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL },
121 { "groups", 'G', POPT_ARG_STRING, NULL, 'G', _("Groups"), NULL },
122 { "create-home", 'm', POPT_ARG_NONE, NULL, 'm', _("Create user's directory if it does not exist"), NULL },
123 { "no-create-home", 'M', POPT_ARG_NONE, NULL, 'M', _("Never create user's directory, overrides config"), NULL },
124 { "skel", 'k', POPT_ARG_STRING, &pc_skeldir, 0, _("Specify an alternative skeleton directory"), NULL },
125 { "selinux-user", 'Z', POPT_ARG_STRING, &pc_selinux_user, 0, _("The SELinux user for user's login"), NULL },
126 POPT_TABLEEND
127 };
128 poptContext pc = NULL;
129 struct tools_ctx *tctx = NULL;
130 char *groups = NULL;
131 char *badgroup = NULL;
132 int ret;
133
134 debug_prg_name = argv[0];
135
136 ret = set_locale();
137 if (ret != EOK) {
138 DEBUG(1, ("set_locale failed (%d): %s\n", ret, strerror(ret)));
139 ERROR("Error setting the locale\n");
140 ret = EXIT_FAILURE;
141 goto fini;
142 }
143
144 /* parse parameters */
145 pc = poptGetContext(NULL, argc, argv, long_options, 0);
146 poptSetOtherOptionHelp(pc, "USERNAME");
147 while ((ret = poptGetNextOpt(pc)) > 0) {
148 switch (ret) {
Event unterminated_case: This case (value 71) is not terminated by a 'break' statement.
Also see events: [fallthrough] | |
149 case 'G':
150 groups = poptGetOptArg(pc);
151 if (!groups) goto fini;
152
Event fallthrough: The above case falls through to this one.
Also see events: [unterminated_case] | |
153 case 'm':
154 pc_create_home = DO_CREATE_HOME;
155 break;
156
157 case 'M':
158 pc_create_home = DO_NOT_CREATE_HOME;
159 break;
160 }
161 }
162
163 debug_level = pc_debug;
164
165 if (ret != -1) {
166 usage(pc, poptStrerror(ret));
167 ret = EXIT_FAILURE;
168 goto fini;
169 }
170
171 /* username is an argument without --option */
172 pc_username = poptGetArg(pc);
173 if (pc_username == NULL) {
174 usage(pc, (_("Specify user to add\n")));
175 ret = EXIT_FAILURE;
176 goto fini;
177 }
178
179 CHECK_ROOT(ret, debug_prg_name);
180
181 ret = init_sss_tools(&tctx);
182 if (ret != EOK) {
183 DEBUG(1, ("init_sss_tools failed (%d): %s\n", ret, strerror(ret)));
184 if (ret == ENOENT) {
185 ERROR("Error initializing the tools - no local domain\n");
186 } else {
187 ERROR("Error initializing the tools\n");
188 }
189 ret = EXIT_FAILURE;
190 goto fini;
191 }
192
193 /* if the domain was not given as part of FQDN, default to local domain */
194 ret = parse_name_domain(tctx, pc_username);
195 if (ret != EOK) {
196 ERROR("Invalid domain specified in FQDN\n");
197 ret = EXIT_FAILURE;
198 goto fini;
199 }
200
201 if (groups) {
202 ret = parse_groups(tctx, groups, &tctx->octx->addgroups);
203 if (ret != EOK) {
204 DEBUG(1, ("Cannot parse groups to add the user to\n"));
205 ERROR("Internal error while parsing parameters\n");
206 ret = EXIT_FAILURE;
207 goto fini;
208 }
209
210 ret = parse_group_name_domain(tctx, tctx->octx->addgroups);
211 if (ret != EOK) {
212 DEBUG(1, ("Cannot parse FQDN groups to add the user to\n"));
213 ERROR("Groups must be in the same domain as user\n");
214 ret = EXIT_FAILURE;
215 goto fini;
216 }
217
218 /* Check group names in the LOCAL domain */
219 ret = check_group_names(tctx, tctx->octx->addgroups, &badgroup);
220 if (ret != EOK) {
221 ERROR("Cannot find group %s in local domain\n", badgroup);
222 ret = EXIT_FAILURE;
223 goto fini;
224 }
225 }
226
227 /* Same as shadow-utils useradd, -g can specify gid or group name */
228 if (pc_group != NULL) {
229 ret = get_gid(tctx, pc_group);
230 if (ret != EOK) {
231 ERROR("Cannot get group information for the user\n");
232 ret = EXIT_FAILURE;
233 goto fini;
234 }
235 }
236
237 tctx->octx->uid = pc_uid;
238
239 /*
240 * Fills in defaults for ops_ctx user did not specify.
241 */
242 ret = useradd_defaults(tctx, tctx->confdb, tctx->octx,
243 pc_gecos, pc_home, pc_shell,
244 pc_create_home, pc_skeldir);
245 if (ret != EOK) {
246 ERROR("Cannot set default values\n");
247 ret = EXIT_FAILURE;
248 goto fini;
249 }
250
251 /* arguments processed, go on to actual work */
252 if (id_in_range(tctx->octx->uid, tctx->octx->domain) != EOK) {
253 ERROR("The selected UID is outside the allowed range\n");
254 ret = EXIT_FAILURE;
255 goto fini;
256 }
257
258 start_transaction(tctx);
259 if (tctx->error != EOK) {
260 goto done;
261 }
262
263 /* useradd */
264 ret = useradd(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
265 if (ret != EOK) {
266 tctx->error = ret;
267
268 /* cancel transaction */
269 talloc_zfree(tctx->handle);
270 goto done;
271 }
272
273 end_transaction(tctx);
274
275 /* Set SELinux login context - must be done after transaction is done
276 * b/c libselinux calls getpwnam */
277 ret = set_seuser(tctx->octx->name, pc_selinux_user);
278 if (ret != EOK) {
279 ERROR("Cannot set SELinux login context\n");
280 ret = EXIT_FAILURE;
281 goto fini;
282 }
283
284 /* Create user's home directory and/or mail spool */
285 if (tctx->octx->create_homedir) {
286 /* We need to know the UID and GID of the user, if
287 * sysdb did assign it automatically, do a lookup */
288 if (tctx->octx->uid == 0 || tctx->octx->gid == 0) {
289 ret = sysdb_getpwnam_sync(tctx,
290 tctx->ev,
291 tctx->sysdb,
292 tctx->octx->name,
293 tctx->local,
294 &tctx->octx);
295 if (ret != EOK) {
296 ERROR("Cannot get info about the user\n");
297 ret = EXIT_FAILURE;
298 goto fini;
299 }
300 }
301
302 ret = create_homedir(tctx,
303 tctx->octx->skeldir,
304 tctx->octx->home,
305 tctx->octx->name,
306 tctx->octx->uid,
307 tctx->octx->gid,
308 tctx->octx->umask);
309 if (ret == EEXIST) {
310 ERROR("User's home directory already exists, not copying "
311 "data from skeldir\n");
312 } else if (ret != EOK) {
313 ERROR("Cannot create user's home directory: %s\n", strerror(ret));
314 ret = EXIT_FAILURE;
315 goto fini;
316 }
317
318 ret = create_mail_spool(tctx,
319 tctx->octx->name,
320 tctx->octx->maildir,
321 tctx->octx->uid,
322 tctx->octx->gid);
323 if (ret != EOK) {
324 ERROR("Cannot create user's mail spool: %s\n", strerror(ret));
325 DEBUG(1, ("Cannot create user's mail spool: [%d][%s].\n",
326 ret, strerror(ret)));
327 ret = EXIT_FAILURE;
328 goto fini;
329 }
330 }
331
332 done:
333 if (tctx->error) {
334 switch (tctx->error) {
335 case ERANGE:
336 ERROR("Could not allocate ID for the user - domain full?\n");
337 break;
338
339 case EEXIST:
340 ERROR("A user or group with the same name or ID already exists\n");
341 break;
342
343 default:
344 DEBUG(1, ("sysdb operation failed (%d)[%s]\n",
345 tctx->error, strerror(tctx->error)));
346 ERROR("Transaction error. Could not add user.\n");
347 break;
348 }
349 ret = EXIT_FAILURE;
350 goto fini;
351 }
352
353 ret = EXIT_SUCCESS;
354
355 fini:
356 poptFreeContext(pc);
357 talloc_free(tctx);
358 free(groups);
359 exit(ret);
360 }