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
29extern const char *__progname;
30
31
32static 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
55static void
56print_usage_and_exit(bool error)
57{
58	fprintf(error ? stderr : stdout, kUsage, __progname);
59	exit(error ? 1 : 0);
60}
61
62
63int
64main(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