xref: /haiku/src/bin/multiuser/useradd.cpp (revision 9f81ca838ce7b92b5689e57d3f86765db4705a7b)
1 /*
2  * Copyright 2008-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 #include <errno.h>
7 #include <getopt.h>
8 #include <pwd.h>
9 #include <shadow.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13 #include <termios.h>
14 #include <time.h>
15 #include <unistd.h>
16 
17 #include <OS.h>
18 #include <parsedate.h>
19 
20 #include <RegistrarDefs.h>
21 #include <user_group.h>
22 #include <util/KMessage.h>
23 
24 #include <AutoDeleter.h>
25 
26 #include "multiuser_utils.h"
27 
28 
29 extern const char *__progname;
30 
31 
32 static const char* kUsage =
33 	"Usage: %s [ <options> ] <user name>\n"
34 	"Creates a new user <user name>.\n"
35 	"\n"
36 	"Options:\n"
37 	"  -d <home>\n"
38 	"    Specifies the home directory for the new user.\n"
39 	"  -e <expiration>\n"
40 	"    Specifies the expiration date for the new user's account.\n"
41 	"  -f <inactive>\n"
42 	"    Specifies the number of days after the expiration of the new user's "
43 			"password\n"
44 	"    until the account expires.\n"
45 	"  -g <gid>\n"
46 	"    Specifies the new user's primary group by ID or name.\n"
47 	"  -h, --help\n"
48 	"    Print usage info.\n"
49 	"  -s <shell>\n"
50 	"    Specifies the new user's login shell.\n"
51 	"  -n <real name>\n"
52 	"    Specifies the new user's real name.\n"
53 	;
54 
55 static void
print_usage_and_exit(bool error)56 print_usage_and_exit(bool error)
57 {
58 	fprintf(error ? stderr : stdout, kUsage, __progname);
59 	exit(error ? 1 : 0);
60 }
61 
62 
63 int
main(int argc,const char * const * argv)64 main(int argc, const char* const* argv)
65 {
66 	const char* home = "/boot/home";
67 	int expiration = 99999;
68 	int inactive = -1;
69 	const char* group = NULL;
70 	const char* shell = "/bin/sh";
71 	const char* realName = "";
72 
73 	int min = -1;
74 	int max = -1;
75 	int warn = 7;
76 
77 	while (true) {
78 		static struct option sLongOptions[] = {
79 			{ "help", no_argument, 0, 'h' },
80 			{ 0, 0, 0, 0 }
81 		};
82 
83 		opterr = 0; // don't print errors
84 		int c = getopt_long(argc, (char**)argv, "d:e:f:g:hn:s:", sLongOptions,
85 			NULL);
86 		if (c == -1)
87 			break;
88 
89 
90 		switch (c) {
91 			case 'd':
92 				home = optarg;
93 				break;
94 
95 			case 'e':
96 				expiration = parsedate(optarg, time(NULL)) / (3600 * 24);
97 				break;
98 
99 			case 'f':
100 				inactive = atoi(optarg);
101 				break;
102 
103 			case 'g':
104 			{
105 				group = optarg;
106 				break;
107 			}
108 
109 			case 'h':
110 				print_usage_and_exit(false);
111 				break;
112 
113 			case 'n':
114 				realName = optarg;
115 				break;
116 
117 			case 's':
118 				shell = optarg;
119 				break;
120 
121 			default:
122 				print_usage_and_exit(true);
123 				break;
124 		}
125 	}
126 
127 	if (optind != argc - 1)
128 		print_usage_and_exit(true);
129 
130 	const char* user = argv[optind];
131 
132 	if (geteuid() != 0) {
133 		fprintf(stderr, "Error: Only root may add users.\n");
134 		exit(1);
135 	}
136 
137 	// check, if user already exists
138 	if (getpwnam(user) != NULL) {
139 		fprintf(stderr, "Error: User \"%s\" already exists.\n", user);
140 		exit(1);
141 	}
142 
143 	// get group ID
144 	gid_t gid = 100;
145 	if (group != NULL) {
146 		char* end;
147 		gid = strtol(group, &end, 0);
148 		if (*end == '\0') {
149 			// seems to be a number
150 			if (gid < 1) {
151 				fprintf(stderr, "Error: Invalid group ID \"%s\".\n",
152 					group);
153 				exit(1);
154 			}
155 		} else {
156 			// must be a group name -- get it
157 			char* buffer = NULL;
158 			ssize_t bufferSize = sysconf(_SC_GETGR_R_SIZE_MAX);
159 			if (bufferSize <= 0)
160 				bufferSize = 256;
161 			for (;;) {
162 				buffer = (char*)realloc(buffer, bufferSize);
163 				if (buffer == NULL) {
164 					fprintf(stderr, "Error: Out of memory!\n");
165 					exit(1);
166 				}
167 
168 				struct group groupBuffer;
169 				struct group* groupFound;
170 				int error = getgrnam_r(group, &groupBuffer, buffer, bufferSize,
171 					&groupFound);
172 				if (error == ERANGE) {
173 					bufferSize *= 2;
174 					continue;
175 				}
176 
177 				if (error != 0) {
178 					fprintf(stderr, "Error: Failed to get info for group "
179 						"\"%s\".\n", group);
180 					exit(1);
181 				}
182 				if (groupFound == NULL) {
183 					fprintf(stderr, "Error: Specified group \"%s\" doesn't "
184 						"exist.\n", group);
185 					exit(1);
186 				}
187 
188 				gid = groupFound->gr_gid;
189 				break;
190 			}
191 		}
192 	}
193 
194 	// find an unused UID
195 	uid_t uid = 1000;
196 	while (getpwuid(uid) != NULL)
197 		uid++;
198 
199 	// prepare request for the registrar
200 	KMessage message(BPrivate::B_REG_UPDATE_USER);
201 	if (message.AddInt32("uid", uid) != B_OK
202 		|| message.AddInt32("gid", gid) != B_OK
203 		|| message.AddString("name", user) != B_OK
204 		|| message.AddString("password", "x") != B_OK
205 		|| message.AddString("home", home) != B_OK
206 		|| message.AddString("shell", shell) != B_OK
207 		|| message.AddString("real name", realName) != B_OK
208 		|| message.AddString("shadow password", "!") != B_OK
209 		|| message.AddInt32("last changed", time(NULL)) != B_OK
210 		|| message.AddInt32("min", min) != B_OK
211 		|| message.AddInt32("max", max) != B_OK
212 		|| message.AddInt32("warn", warn) != B_OK
213 		|| message.AddInt32("inactive", inactive) != B_OK
214 		|| message.AddInt32("expiration", expiration) != B_OK
215 		|| message.AddInt32("flags", 0) != B_OK
216 		|| message.AddBool("add user", true) != B_OK) {
217 		fprintf(stderr, "Error: Out of memory!\n");
218 		exit(1);
219 	}
220 
221 	// send the request
222 	KMessage reply;
223 	status_t error = send_authentication_request_to_registrar(message, reply);
224 	if (error != B_OK) {
225 		fprintf(stderr, "Error: Failed to create user: %s\n", strerror(error));
226 		exit(1);
227 	}
228 
229 	return 0;
230 }
231