xref: /freebsd/usr.bin/mdo/mdo.c (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
1 /*-
2  * Copyright(c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  */
6 
7 #include <sys/limits.h>
8 #include <sys/ucred.h>
9 
10 #include <err.h>
11 #include <paths.h>
12 #include <pwd.h>
13 #include <stdbool.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18 
19 static void
20 usage(void)
21 {
22 	fprintf(stderr, "usage: mdo [-u username] [-i] [--] [command [args]]\n");
23 	exit(EXIT_FAILURE);
24 }
25 
26 int
27 main(int argc, char **argv)
28 {
29 	struct passwd *pw;
30 	const char *username = "root";
31 	struct setcred wcred = SETCRED_INITIALIZER;
32 	u_int setcred_flags = 0;
33 	bool uidonly = false;
34 	int ch;
35 
36 	while ((ch = getopt(argc, argv, "u:i")) != -1) {
37 		switch (ch) {
38 		case 'u':
39 			username = optarg;
40 			break;
41 		case 'i':
42 			uidonly = true;
43 			break;
44 		default:
45 			usage();
46 		}
47 	}
48 	argc -= optind;
49 	argv += optind;
50 
51 	if ((pw = getpwnam(username)) == NULL) {
52 		if (strspn(username, "0123456789") == strlen(username)) {
53 			const char *errp = NULL;
54 			uid_t uid = strtonum(username, 0, UID_MAX, &errp);
55 			if (errp != NULL)
56 				err(EXIT_FAILURE, "invalid user ID '%s'",
57 				    username);
58 			pw = getpwuid(uid);
59 		}
60 		if (pw == NULL)
61 			err(EXIT_FAILURE, "invalid username '%s'", username);
62 	}
63 
64 	wcred.sc_uid = wcred.sc_ruid = wcred.sc_svuid = pw->pw_uid;
65 	setcred_flags |= SETCREDF_UID | SETCREDF_RUID | SETCREDF_SVUID;
66 
67 	if (!uidonly) {
68 		/*
69 		 * If there are too many groups specified for some UID, setting
70 		 * the groups will fail.  We preserve this condition by
71 		 * allocating one more group slot than allowed, as
72 		 * getgrouplist() itself is just some getter function and thus
73 		 * doesn't (and shouldn't) check the limit, and to allow
74 		 * setcred() to actually check for overflow.
75 		 */
76 		const long ngroups_alloc = sysconf(_SC_NGROUPS_MAX) + 2;
77 		gid_t *const groups = malloc(sizeof(*groups) * ngroups_alloc);
78 		int ngroups = ngroups_alloc;
79 
80 		if (groups == NULL)
81 			err(EXIT_FAILURE, "cannot allocate memory for groups");
82 
83 		getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
84 
85 		wcred.sc_gid = wcred.sc_rgid = wcred.sc_svgid = pw->pw_gid;
86 		wcred.sc_supp_groups = groups + 1;
87 		wcred.sc_supp_groups_nb = ngroups - 1;
88 		setcred_flags |= SETCREDF_GID | SETCREDF_RGID | SETCREDF_SVGID |
89 		    SETCREDF_SUPP_GROUPS;
90 	}
91 
92 	if (setcred(setcred_flags, &wcred, sizeof(wcred)) != 0)
93 		err(EXIT_FAILURE, "calling setcred() failed");
94 
95 	if (*argv == NULL) {
96 		const char *sh = getenv("SHELL");
97 		if (sh == NULL)
98 			sh = _PATH_BSHELL;
99 		execlp(sh, sh, "-i", NULL);
100 	} else {
101 		execvp(argv[0], argv);
102 	}
103 	err(EXIT_FAILURE, "exec failed");
104 }
105