xref: /haiku/src/bin/multiuser/useradd.cpp (revision 9f81ca838ce7b92b5689e57d3f86765db4705a7b)
1f5e8e689SIngo Weinhold /*
2c13744f4SIngo Weinhold  * Copyright 2008-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3f5e8e689SIngo Weinhold  * Distributed under the terms of the MIT License.
4f5e8e689SIngo Weinhold  */
5f5e8e689SIngo Weinhold 
6f5e8e689SIngo Weinhold #include <errno.h>
7f5e8e689SIngo Weinhold #include <getopt.h>
8f5e8e689SIngo Weinhold #include <pwd.h>
9f5e8e689SIngo Weinhold #include <shadow.h>
10f5e8e689SIngo Weinhold #include <stdio.h>
11f5e8e689SIngo Weinhold #include <stdlib.h>
12f5e8e689SIngo Weinhold #include <string.h>
13f5e8e689SIngo Weinhold #include <termios.h>
1489d327d6SIngo Weinhold #include <time.h>
15f5e8e689SIngo Weinhold #include <unistd.h>
16f5e8e689SIngo Weinhold 
17f5e8e689SIngo Weinhold #include <OS.h>
18f5e8e689SIngo Weinhold #include <parsedate.h>
19f5e8e689SIngo Weinhold 
20f5e8e689SIngo Weinhold #include <RegistrarDefs.h>
21f5e8e689SIngo Weinhold #include <user_group.h>
22f5e8e689SIngo Weinhold #include <util/KMessage.h>
23f5e8e689SIngo Weinhold 
24f5e8e689SIngo Weinhold #include <AutoDeleter.h>
25f5e8e689SIngo Weinhold 
26f5e8e689SIngo Weinhold #include "multiuser_utils.h"
27f5e8e689SIngo Weinhold 
28f5e8e689SIngo Weinhold 
29f5e8e689SIngo Weinhold extern const char *__progname;
30f5e8e689SIngo Weinhold 
31f5e8e689SIngo Weinhold 
32f5e8e689SIngo Weinhold static const char* kUsage =
33c13744f4SIngo Weinhold 	"Usage: %s [ <options> ] <user name>\n"
34c13744f4SIngo Weinhold 	"Creates a new user <user name>.\n"
35c13744f4SIngo Weinhold 	"\n"
36c13744f4SIngo Weinhold 	"Options:\n"
37c13744f4SIngo Weinhold 	"  -d <home>\n"
38c13744f4SIngo Weinhold 	"    Specifies the home directory for the new user.\n"
39c13744f4SIngo Weinhold 	"  -e <expiration>\n"
40c13744f4SIngo Weinhold 	"    Specifies the expiration date for the new user's account.\n"
41c13744f4SIngo Weinhold 	"  -f <inactive>\n"
42c13744f4SIngo Weinhold 	"    Specifies the number of days after the expiration of the new user's "
43c13744f4SIngo Weinhold 			"password\n"
44c13744f4SIngo Weinhold 	"    until the account expires.\n"
45c13744f4SIngo Weinhold 	"  -g <gid>\n"
46c13744f4SIngo Weinhold 	"    Specifies the new user's primary group by ID or name.\n"
47c13744f4SIngo Weinhold 	"  -h, --help\n"
48c13744f4SIngo Weinhold 	"    Print usage info.\n"
49c13744f4SIngo Weinhold 	"  -s <shell>\n"
50c13744f4SIngo Weinhold 	"    Specifies the new user's login shell.\n"
51c13744f4SIngo Weinhold 	"  -n <real name>\n"
52c13744f4SIngo Weinhold 	"    Specifies the new user's real name.\n"
53f5e8e689SIngo Weinhold 	;
54f5e8e689SIngo Weinhold 
55f5e8e689SIngo Weinhold static void
print_usage_and_exit(bool error)56f5e8e689SIngo Weinhold print_usage_and_exit(bool error)
57f5e8e689SIngo Weinhold {
58f5e8e689SIngo Weinhold 	fprintf(error ? stderr : stdout, kUsage, __progname);
59f5e8e689SIngo Weinhold 	exit(error ? 1 : 0);
60f5e8e689SIngo Weinhold }
61f5e8e689SIngo Weinhold 
62f5e8e689SIngo Weinhold 
63f5e8e689SIngo Weinhold int
main(int argc,const char * const * argv)64f5e8e689SIngo Weinhold main(int argc, const char* const* argv)
65f5e8e689SIngo Weinhold {
66f5e8e689SIngo Weinhold 	const char* home = "/boot/home";
67f5e8e689SIngo Weinhold 	int expiration = 99999;
68f5e8e689SIngo Weinhold 	int inactive = -1;
690e9e586cSIngo Weinhold 	const char* group = NULL;
70f5e8e689SIngo Weinhold 	const char* shell = "/bin/sh";
71f5e8e689SIngo Weinhold 	const char* realName = "";
72f5e8e689SIngo Weinhold 
73f5e8e689SIngo Weinhold 	int min = -1;
74f5e8e689SIngo Weinhold 	int max = -1;
75f5e8e689SIngo Weinhold 	int warn = 7;
76f5e8e689SIngo Weinhold 
77f5e8e689SIngo Weinhold 	while (true) {
78f5e8e689SIngo Weinhold 		static struct option sLongOptions[] = {
79f5e8e689SIngo Weinhold 			{ "help", no_argument, 0, 'h' },
80f5e8e689SIngo Weinhold 			{ 0, 0, 0, 0 }
81f5e8e689SIngo Weinhold 		};
82f5e8e689SIngo Weinhold 
83f5e8e689SIngo Weinhold 		opterr = 0; // don't print errors
84f5e8e689SIngo Weinhold 		int c = getopt_long(argc, (char**)argv, "d:e:f:g:hn:s:", sLongOptions,
85f5e8e689SIngo Weinhold 			NULL);
86f5e8e689SIngo Weinhold 		if (c == -1)
87f5e8e689SIngo Weinhold 			break;
88f5e8e689SIngo Weinhold 
89f5e8e689SIngo Weinhold 
90f5e8e689SIngo Weinhold 		switch (c) {
91f5e8e689SIngo Weinhold 			case 'd':
92f5e8e689SIngo Weinhold 				home = optarg;
93f5e8e689SIngo Weinhold 				break;
94f5e8e689SIngo Weinhold 
95f5e8e689SIngo Weinhold 			case 'e':
96f5e8e689SIngo Weinhold 				expiration = parsedate(optarg, time(NULL)) / (3600 * 24);
97f5e8e689SIngo Weinhold 				break;
98f5e8e689SIngo Weinhold 
99f5e8e689SIngo Weinhold 			case 'f':
100f5e8e689SIngo Weinhold 				inactive = atoi(optarg);
101f5e8e689SIngo Weinhold 				break;
102f5e8e689SIngo Weinhold 
103f5e8e689SIngo Weinhold 			case 'g':
1040e9e586cSIngo Weinhold 			{
1050e9e586cSIngo Weinhold 				group = optarg;
106f5e8e689SIngo Weinhold 				break;
1070e9e586cSIngo Weinhold 			}
108f5e8e689SIngo Weinhold 
109f5e8e689SIngo Weinhold 			case 'h':
110f5e8e689SIngo Weinhold 				print_usage_and_exit(false);
111f5e8e689SIngo Weinhold 				break;
112f5e8e689SIngo Weinhold 
113f5e8e689SIngo Weinhold 			case 'n':
114f5e8e689SIngo Weinhold 				realName = optarg;
115f5e8e689SIngo Weinhold 				break;
116f5e8e689SIngo Weinhold 
117f5e8e689SIngo Weinhold 			case 's':
118f5e8e689SIngo Weinhold 				shell = optarg;
119f5e8e689SIngo Weinhold 				break;
120f5e8e689SIngo Weinhold 
121f5e8e689SIngo Weinhold 			default:
122f5e8e689SIngo Weinhold 				print_usage_and_exit(true);
123f5e8e689SIngo Weinhold 				break;
124f5e8e689SIngo Weinhold 		}
125f5e8e689SIngo Weinhold 	}
126f5e8e689SIngo Weinhold 
127f5e8e689SIngo Weinhold 	if (optind != argc - 1)
128f5e8e689SIngo Weinhold 		print_usage_and_exit(true);
129f5e8e689SIngo Weinhold 
130f5e8e689SIngo Weinhold 	const char* user = argv[optind];
131f5e8e689SIngo Weinhold 
132f5e8e689SIngo Weinhold 	if (geteuid() != 0) {
133*032ea9a4SIngo Weinhold 		fprintf(stderr, "Error: Only root may add users.\n");
134f5e8e689SIngo Weinhold 		exit(1);
135f5e8e689SIngo Weinhold 	}
136f5e8e689SIngo Weinhold 
137f5e8e689SIngo Weinhold 	// check, if user already exists
138f5e8e689SIngo Weinhold 	if (getpwnam(user) != NULL) {
139f5e8e689SIngo Weinhold 		fprintf(stderr, "Error: User \"%s\" already exists.\n", user);
140f5e8e689SIngo Weinhold 		exit(1);
141f5e8e689SIngo Weinhold 	}
142f5e8e689SIngo Weinhold 
1430e9e586cSIngo Weinhold 	// get group ID
1440e9e586cSIngo Weinhold 	gid_t gid = 100;
1450e9e586cSIngo Weinhold 	if (group != NULL) {
1460e9e586cSIngo Weinhold 		char* end;
1470e9e586cSIngo Weinhold 		gid = strtol(group, &end, 0);
1480e9e586cSIngo Weinhold 		if (*end == '\0') {
1490e9e586cSIngo Weinhold 			// seems to be a number
1500e9e586cSIngo Weinhold 			if (gid < 1) {
1510e9e586cSIngo Weinhold 				fprintf(stderr, "Error: Invalid group ID \"%s\".\n",
1520e9e586cSIngo Weinhold 					group);
1530e9e586cSIngo Weinhold 				exit(1);
1540e9e586cSIngo Weinhold 			}
1550e9e586cSIngo Weinhold 		} else {
1560e9e586cSIngo Weinhold 			// must be a group name -- get it
1570e9e586cSIngo Weinhold 			char* buffer = NULL;
1580e9e586cSIngo Weinhold 			ssize_t bufferSize = sysconf(_SC_GETGR_R_SIZE_MAX);
1590e9e586cSIngo Weinhold 			if (bufferSize <= 0)
1600e9e586cSIngo Weinhold 				bufferSize = 256;
1610e9e586cSIngo Weinhold 			for (;;) {
1620e9e586cSIngo Weinhold 				buffer = (char*)realloc(buffer, bufferSize);
1630e9e586cSIngo Weinhold 				if (buffer == NULL) {
1640e9e586cSIngo Weinhold 					fprintf(stderr, "Error: Out of memory!\n");
1650e9e586cSIngo Weinhold 					exit(1);
1660e9e586cSIngo Weinhold 				}
1670e9e586cSIngo Weinhold 
1680e9e586cSIngo Weinhold 				struct group groupBuffer;
1690e9e586cSIngo Weinhold 				struct group* groupFound;
1700e9e586cSIngo Weinhold 				int error = getgrnam_r(group, &groupBuffer, buffer, bufferSize,
1710e9e586cSIngo Weinhold 					&groupFound);
1720e9e586cSIngo Weinhold 				if (error == ERANGE) {
1730e9e586cSIngo Weinhold 					bufferSize *= 2;
1740e9e586cSIngo Weinhold 					continue;
1750e9e586cSIngo Weinhold 				}
1760e9e586cSIngo Weinhold 
1770e9e586cSIngo Weinhold 				if (error != 0) {
17882a064b5SIngo Weinhold 					fprintf(stderr, "Error: Failed to get info for group "
17982a064b5SIngo Weinhold 						"\"%s\".\n", group);
1800e9e586cSIngo Weinhold 					exit(1);
1810e9e586cSIngo Weinhold 				}
1820e9e586cSIngo Weinhold 				if (groupFound == NULL) {
1830e9e586cSIngo Weinhold 					fprintf(stderr, "Error: Specified group \"%s\" doesn't "
1840e9e586cSIngo Weinhold 						"exist.\n", group);
1850e9e586cSIngo Weinhold 					exit(1);
1860e9e586cSIngo Weinhold 				}
1870e9e586cSIngo Weinhold 
1880e9e586cSIngo Weinhold 				gid = groupFound->gr_gid;
1890e9e586cSIngo Weinhold 				break;
1900e9e586cSIngo Weinhold 			}
1910e9e586cSIngo Weinhold 		}
1920e9e586cSIngo Weinhold 	}
1930e9e586cSIngo Weinhold 
194f5e8e689SIngo Weinhold 	// find an unused UID
195f5e8e689SIngo Weinhold 	uid_t uid = 1000;
196f5e8e689SIngo Weinhold 	while (getpwuid(uid) != NULL)
197f5e8e689SIngo Weinhold 		uid++;
198f5e8e689SIngo Weinhold 
199f5e8e689SIngo Weinhold 	// prepare request for the registrar
200f5e8e689SIngo Weinhold 	KMessage message(BPrivate::B_REG_UPDATE_USER);
201f5e8e689SIngo Weinhold 	if (message.AddInt32("uid", uid) != B_OK
202f5e8e689SIngo Weinhold 		|| message.AddInt32("gid", gid) != B_OK
203f5e8e689SIngo Weinhold 		|| message.AddString("name", user) != B_OK
204f5e8e689SIngo Weinhold 		|| message.AddString("password", "x") != B_OK
205f5e8e689SIngo Weinhold 		|| message.AddString("home", home) != B_OK
206f5e8e689SIngo Weinhold 		|| message.AddString("shell", shell) != B_OK
207f5e8e689SIngo Weinhold 		|| message.AddString("real name", realName) != B_OK
208*032ea9a4SIngo Weinhold 		|| message.AddString("shadow password", "!") != B_OK
20989d327d6SIngo Weinhold 		|| message.AddInt32("last changed", time(NULL)) != B_OK
210f5e8e689SIngo Weinhold 		|| message.AddInt32("min", min) != B_OK
211f5e8e689SIngo Weinhold 		|| message.AddInt32("max", max) != B_OK
212f5e8e689SIngo Weinhold 		|| message.AddInt32("warn", warn) != B_OK
213f5e8e689SIngo Weinhold 		|| message.AddInt32("inactive", inactive) != B_OK
214f5e8e689SIngo Weinhold 		|| message.AddInt32("expiration", expiration) != B_OK
215f5e8e689SIngo Weinhold 		|| message.AddInt32("flags", 0) != B_OK
216f5e8e689SIngo Weinhold 		|| message.AddBool("add user", true) != B_OK) {
217f5e8e689SIngo Weinhold 		fprintf(stderr, "Error: Out of memory!\n");
218f5e8e689SIngo Weinhold 		exit(1);
219f5e8e689SIngo Weinhold 	}
220f5e8e689SIngo Weinhold 
221f5e8e689SIngo Weinhold 	// send the request
222f5e8e689SIngo Weinhold 	KMessage reply;
223f5e8e689SIngo Weinhold 	status_t error = send_authentication_request_to_registrar(message, reply);
224f5e8e689SIngo Weinhold 	if (error != B_OK) {
225f5e8e689SIngo Weinhold 		fprintf(stderr, "Error: Failed to create user: %s\n", strerror(error));
226f5e8e689SIngo Weinhold 		exit(1);
227f5e8e689SIngo Weinhold 	}
228f5e8e689SIngo Weinhold 
229f5e8e689SIngo Weinhold 	return 0;
230f5e8e689SIngo Weinhold }
231