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  	}