7 |
* |
* |
8 |
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
9 |
*/ |
*/ |
|
|
|
10 |
#include "libbb.h" |
#include "libbb.h" |
11 |
|
|
12 |
|
#if CONFIG_LAST_SYSTEM_ID < CONFIG_FIRST_SYSTEM_ID |
13 |
|
#error Bad LAST_SYSTEM_ID or FIRST_SYSTEM_ID in .config |
14 |
|
#endif |
15 |
|
|
16 |
|
/* #define OPT_HOME (1 << 0) */ /* unused */ |
17 |
|
/* #define OPT_GECOS (1 << 1) */ /* unused */ |
18 |
|
#define OPT_SHELL (1 << 2) |
19 |
|
#define OPT_GID (1 << 3) |
20 |
#define OPT_DONT_SET_PASS (1 << 4) |
#define OPT_DONT_SET_PASS (1 << 4) |
21 |
#define OPT_SYSTEM_ACCOUNT (1 << 5) |
#define OPT_SYSTEM_ACCOUNT (1 << 5) |
22 |
#define OPT_DONT_MAKE_HOME (1 << 6) |
#define OPT_DONT_MAKE_HOME (1 << 6) |
23 |
|
#define OPT_UID (1 << 7) |
24 |
|
|
25 |
|
/* We assume UID_T_MAX == INT_MAX */ |
26 |
/* remix */ |
/* remix */ |
27 |
/* recoded such that the uid may be passed in *p */ |
/* recoded such that the uid may be passed in *p */ |
28 |
static void passwd_study(struct passwd *p) |
static void passwd_study(struct passwd *p) |
29 |
{ |
{ |
30 |
int max; |
int max = UINT_MAX; |
|
|
|
|
if (getpwnam(p->pw_name)) |
|
|
bb_error_msg_and_die("login '%s' is in use", p->pw_name); |
|
31 |
|
|
32 |
if (option_mask32 & OPT_SYSTEM_ACCOUNT) { |
if (getpwnam(p->pw_name)) { |
33 |
p->pw_uid = 0; |
bb_error_msg_and_die("%s '%s' in use", "user", p->pw_name); |
34 |
max = 999; |
/* this format string is reused in adduser and addgroup */ |
|
} else { |
|
|
p->pw_uid = 1000; |
|
|
max = 64999; |
|
35 |
} |
} |
36 |
|
|
37 |
|
if (!(option_mask32 & OPT_UID)) { |
38 |
|
if (option_mask32 & OPT_SYSTEM_ACCOUNT) { |
39 |
|
p->pw_uid = CONFIG_FIRST_SYSTEM_ID; |
40 |
|
max = CONFIG_LAST_SYSTEM_ID; |
41 |
|
} else { |
42 |
|
p->pw_uid = CONFIG_LAST_SYSTEM_ID + 1; |
43 |
|
max = 64999; |
44 |
|
} |
45 |
|
} |
46 |
/* check for a free uid (and maybe gid) */ |
/* check for a free uid (and maybe gid) */ |
47 |
while (getpwuid(p->pw_uid) || (!p->pw_gid && getgrgid(p->pw_uid))) |
while (getpwuid(p->pw_uid) || (p->pw_gid == (gid_t)-1 && getgrgid(p->pw_uid))) { |
48 |
|
if (option_mask32 & OPT_UID) { |
49 |
|
/* -u N, cannot pick uid other than N: error */ |
50 |
|
bb_error_msg_and_die("%s '%s' in use", "uid", itoa(p->pw_uid)); |
51 |
|
/* this format string is reused in adduser and addgroup */ |
52 |
|
} |
53 |
|
if (p->pw_uid == max) { |
54 |
|
bb_error_msg_and_die("no %cids left", 'u'); |
55 |
|
} |
56 |
p->pw_uid++; |
p->pw_uid++; |
|
|
|
|
if (!p->pw_gid) { |
|
|
/* new gid = uid */ |
|
|
p->pw_gid = p->pw_uid; |
|
|
if (getgrnam(p->pw_name)) |
|
|
bb_error_msg_and_die("group name '%s' is in use", p->pw_name); |
|
57 |
} |
} |
58 |
|
|
59 |
if (p->pw_uid > max) |
if (p->pw_gid == (gid_t)-1) { |
60 |
bb_error_msg_and_die("no free uids left"); |
p->pw_gid = p->pw_uid; /* new gid = uid */ |
61 |
|
if (getgrnam(p->pw_name)) { |
62 |
|
bb_error_msg_and_die("%s '%s' in use", "group", p->pw_name); |
63 |
|
/* this format string is reused in adduser and addgroup */ |
64 |
|
} |
65 |
|
} |
66 |
} |
} |
67 |
|
|
68 |
static void addgroup_wrapper(struct passwd *p) |
static void addgroup_wrapper(struct passwd *p, const char *group_name) |
69 |
{ |
{ |
70 |
char *cmd; |
char *cmd; |
71 |
|
|
72 |
cmd = xasprintf("addgroup -g %u '%s'", (unsigned)p->pw_gid, p->pw_name); |
if (group_name) /* Add user to existing group */ |
73 |
|
cmd = xasprintf("addgroup '%s' '%s'", p->pw_name, group_name); |
74 |
|
else /* Add user to his own group with the first free gid found in passwd_study */ |
75 |
|
cmd = xasprintf("addgroup -g %u '%s'", (unsigned)p->pw_gid, p->pw_name); |
76 |
|
/* Warning: to be compatible with external addgroup programs we should use --gid instead */ |
77 |
system(cmd); |
system(cmd); |
78 |
free(cmd); |
free(cmd); |
79 |
} |
} |
82 |
|
|
83 |
static void passwd_wrapper(const char *login) |
static void passwd_wrapper(const char *login) |
84 |
{ |
{ |
85 |
static const char prog[] ALIGN1 = "passwd"; |
BB_EXECLP("passwd", "passwd", login, NULL); |
86 |
|
bb_error_msg_and_die("can't execute passwd, you must set password manually"); |
|
BB_EXECLP(prog, prog, login, NULL); |
|
|
bb_error_msg_and_die("cannot execute %s, you must set password manually", prog); |
|
87 |
} |
} |
88 |
|
|
89 |
#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS |
#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS |
96 |
"empty-password\0" No_argument "D" |
"empty-password\0" No_argument "D" |
97 |
"system\0" No_argument "S" |
"system\0" No_argument "S" |
98 |
"no-create-home\0" No_argument "H" |
"no-create-home\0" No_argument "H" |
99 |
|
"uid\0" Required_argument "u" |
100 |
; |
; |
101 |
#endif |
#endif |
102 |
|
|
110 |
{ |
{ |
111 |
struct passwd pw; |
struct passwd pw; |
112 |
const char *usegroup = NULL; |
const char *usegroup = NULL; |
113 |
FILE *file; |
char *p; |
114 |
|
unsigned opts; |
115 |
|
|
116 |
#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS |
#if ENABLE_FEATURE_ADDUSER_LONG_OPTIONS |
117 |
applet_long_options = adduser_longopts; |
applet_long_options = adduser_longopts; |
127 |
pw.pw_dir = NULL; |
pw.pw_dir = NULL; |
128 |
|
|
129 |
/* exactly one non-option arg */ |
/* exactly one non-option arg */ |
130 |
opt_complementary = "=1"; |
/* disable interactive passwd for system accounts */ |
131 |
getopt32(argv, "h:g:s:G:DSH", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup); |
opt_complementary = "=1:SD:u+"; |
132 |
|
if (sizeof(pw.pw_uid) == sizeof(int)) { |
133 |
|
opts = getopt32(argv, "h:g:s:G:DSHu:", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup, &pw.pw_uid); |
134 |
|
} else { |
135 |
|
unsigned uid; |
136 |
|
opts = getopt32(argv, "h:g:s:G:DSHu:", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup, &uid); |
137 |
|
if (opts & OPT_UID) { |
138 |
|
pw.pw_uid = uid; |
139 |
|
} |
140 |
|
} |
141 |
argv += optind; |
argv += optind; |
142 |
|
|
143 |
/* fill in the passwd struct */ |
/* fill in the passwd struct */ |
148 |
pw.pw_dir = xasprintf("/home/%s", argv[0]); |
pw.pw_dir = xasprintf("/home/%s", argv[0]); |
149 |
} |
} |
150 |
pw.pw_passwd = (char *)"x"; |
pw.pw_passwd = (char *)"x"; |
151 |
pw.pw_gid = usegroup ? xgroup2gid(usegroup) : 0; /* exits on failure */ |
if (opts & OPT_SYSTEM_ACCOUNT) { |
152 |
|
if (!usegroup) { |
153 |
|
usegroup = "nogroup"; |
154 |
|
} |
155 |
|
if (!(opts & OPT_SHELL)) { |
156 |
|
pw.pw_shell = (char *) "/bin/false"; |
157 |
|
} |
158 |
|
} |
159 |
|
pw.pw_gid = usegroup ? xgroup2gid(usegroup) : -1; /* exits on failure */ |
160 |
|
|
161 |
/* make sure everything is kosher and setup uid && maybe gid */ |
/* make sure everything is kosher and setup uid && maybe gid */ |
162 |
passwd_study(&pw); |
passwd_study(&pw); |
163 |
|
|
164 |
/* add to passwd */ |
p = xasprintf("x:%u:%u:%s:%s:%s", |
165 |
file = xfopen(bb_path_passwd_file, "a"); |
(unsigned) pw.pw_uid, (unsigned) pw.pw_gid, |
166 |
//fseek(file, 0, SEEK_END); /* paranoia, "a" should ensure that anyway */ |
pw.pw_gecos, pw.pw_dir, pw.pw_shell); |
167 |
if (putpwent(&pw, file) != 0) { |
if (update_passwd(bb_path_passwd_file, pw.pw_name, p, NULL) < 0) { |
168 |
bb_perror_nomsg_and_die(); |
return EXIT_FAILURE; |
169 |
} |
} |
170 |
/* do fclose even if !ENABLE_FEATURE_CLEAN_UP. |
if (ENABLE_FEATURE_CLEAN_UP) |
171 |
* We will exec passwd, files must be flushed & closed before that! */ |
free(p); |
|
fclose(file); |
|
172 |
|
|
173 |
#if ENABLE_FEATURE_SHADOWPASSWDS |
#if ENABLE_FEATURE_SHADOWPASSWDS |
174 |
/* add to shadow if necessary */ |
/* /etc/shadow fields: |
175 |
file = fopen_or_warn(bb_path_shadow_file, "a"); |
* 1. username |
176 |
if (file) { |
* 2. encrypted password |
177 |
//fseek(file, 0, SEEK_END); |
* 3. last password change (unix date (unix time/24*60*60)) |
178 |
fprintf(file, "%s:!:%u:0:99999:7:::\n", |
* 4. minimum days required between password changes |
179 |
pw.pw_name, /* username */ |
* 5. maximum days password is valid |
180 |
(unsigned)(time(NULL) / 86400) /* sp->sp_lstchg */ |
* 6. days before password is to expire that user is warned |
181 |
/*0,*/ /* sp->sp_min */ |
* 7. days after password expires that account is disabled |
182 |
/*99999,*/ /* sp->sp_max */ |
* 8. unix date when login expires (i.e. when it may no longer be used) |
183 |
/*7*/ /* sp->sp_warn */ |
*/ |
184 |
); |
/* fields: 2 3 4 5 6 78 */ |
185 |
fclose(file); |
p = xasprintf("!:%u:0:99999:7:::", (unsigned)(time(NULL)) / (24*60*60)); |
186 |
} |
/* ignore errors: if file is missing we suppose admin doesn't want it */ |
187 |
|
update_passwd(bb_path_shadow_file, pw.pw_name, p, NULL); |
188 |
|
if (ENABLE_FEATURE_CLEAN_UP) |
189 |
|
free(p); |
190 |
#endif |
#endif |
191 |
|
|
192 |
/* add to group */ |
/* add to group */ |
193 |
/* addgroup should be responsible for dealing w/ gshadow */ |
addgroup_wrapper(&pw, usegroup); |
|
/* if using a pre-existing group, don't create one */ |
|
|
if (!usegroup) |
|
|
addgroup_wrapper(&pw); |
|
194 |
|
|
195 |
/* Clear the umask for this process so it doesn't |
/* clear the umask for this process so it doesn't |
196 |
* screw up the permissions on the mkdir and chown. */ |
* screw up the permissions on the mkdir and chown. */ |
197 |
umask(0); |
umask(0); |
198 |
if (!(option_mask32 & OPT_DONT_MAKE_HOME)) { |
if (!(opts & OPT_DONT_MAKE_HOME)) { |
199 |
/* Set the owner and group so it is owned by the new user, |
/* set the owner and group so it is owned by the new user, |
200 |
then fix up the permissions to 2755. Can't do it before |
* then fix up the permissions to 2755. Can't do it before |
201 |
since chown will clear the setgid bit */ |
* since chown will clear the setgid bit */ |
202 |
if (mkdir(pw.pw_dir, 0755) |
if ((mkdir(pw.pw_dir, 0755) != 0 && errno != EEXIST) |
203 |
|| chown(pw.pw_dir, pw.pw_uid, pw.pw_gid) |
|| chown(pw.pw_dir, pw.pw_uid, pw.pw_gid) != 0 |
204 |
|| chmod(pw.pw_dir, 02755) /* set setgid bit on homedir */ |
|| chmod(pw.pw_dir, 02755) != 0 /* set setgid bit on homedir */ |
205 |
) { |
) { |
206 |
bb_simple_perror_msg(pw.pw_dir); |
bb_simple_perror_msg(pw.pw_dir); |
207 |
} |
} |
208 |
} |
} |
209 |
|
|
210 |
if (!(option_mask32 & OPT_DONT_SET_PASS)) { |
if (!(opts & OPT_DONT_SET_PASS)) { |
211 |
/* interactively set passwd */ |
/* interactively set passwd */ |
212 |
passwd_wrapper(pw.pw_name); |
passwd_wrapper(pw.pw_name); |
213 |
} |
} |