busybox/libbb/update_passwd.c

341 lines
8.8 KiB
C
Raw Normal View History

2007-07-21 02:59:49 +05:30
/* vi: set sw=4 ts=4: */
/*
* update_passwd
*
* update_passwd is a common function for passwd and chpasswd applets;
* it is responsible for updating password file (i.e. /etc/passwd or
* /etc/shadow) for a given user and password.
*
* Moved from loginutils/passwd.c by Alexander Shishkin <virtuoso@slind.org>
2008-12-07 06:22:58 +05:30
*
* Modified to be able to add or delete users, groups and users to/from groups
* by Tito Ragusa <farmatito@tiscali.it>
*
* Licensed under GPLv2, see file LICENSE in this source tree.
2007-07-21 02:59:49 +05:30
*/
#include "libbb.h"
#if ENABLE_SELINUX
static void check_selinux_update_passwd(const char *username)
{
security_context_t seuser;
char *p;
if (getuid() != (uid_t)0 || is_selinux_enabled() == 0)
return; /* No need to check */
if (getprevcon_raw(&seuser) < 0)
libbb: reduce the overhead of single parameter bb_error_msg() calls Back in 2007, commit 0c97c9d43707 ("'simple' error message functions by Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower overhead call to bb_perror_msg() when only a string was being printed with no parameters. This saves space for some CPU architectures because it avoids the overhead of a call to a variadic function. However there has never been a simple version of bb_error_msg(), and since 2007 many new calls to bb_perror_msg() have been added that only take a single parameter and so could have been using bb_simple_perror_message(). This changeset introduces 'simple' versions of bb_info_msg(), bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and bb_herror_msg_and_die(), and replaces all calls that only take a single parameter, or use something like ("%s", arg), with calls to the corresponding 'simple' version. Since it is likely that single parameter calls to the variadic functions may be accidentally reintroduced in the future a new debugging config option WARN_SIMPLE_MSG has been introduced. This uses some macro magic which will cause any such calls to generate a warning, but this is turned off by default to avoid use of the unpleasant macros in normal circumstances. This is a large changeset due to the number of calls that have been replaced. The only files that contain changes other than simple substitution of function calls are libbb.h, libbb/herror_msg.c, libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c, networking/udhcp/common.h and util-linux/mdev.c additonal macros have been added for logging so that single parameter and multiple parameter logging variants exist. The amount of space saved varies considerably by architecture, and was found to be as follows (for 'defconfig' using GCC 7.4): Arm: -92 bytes MIPS: -52 bytes PPC: -1836 bytes x86_64: -938 bytes Note that for the MIPS architecture only an exception had to be made disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h) because it made these files larger on MIPS. Signed-off-by: James Byrne <james.byrne@origamienergy.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 15:05:03 +05:30
bb_simple_perror_msg_and_die("getprevcon failed");
p = strchr(seuser, ':');
if (!p)
bb_error_msg_and_die("invalid context '%s'", seuser);
*p = '\0';
if (strcmp(seuser, username) != 0) {
security_class_t tclass;
access_vector_t av;
tclass = string_to_security_class("passwd");
if (tclass == 0)
goto die;
av = string_to_av_perm(tclass, "passwd");
if (av == 0)
goto die;
if (selinux_check_passwd_access(av) != 0)
die:
libbb: reduce the overhead of single parameter bb_error_msg() calls Back in 2007, commit 0c97c9d43707 ("'simple' error message functions by Loic Grenie") introduced bb_simple_perror_msg() to allow for a lower overhead call to bb_perror_msg() when only a string was being printed with no parameters. This saves space for some CPU architectures because it avoids the overhead of a call to a variadic function. However there has never been a simple version of bb_error_msg(), and since 2007 many new calls to bb_perror_msg() have been added that only take a single parameter and so could have been using bb_simple_perror_message(). This changeset introduces 'simple' versions of bb_info_msg(), bb_error_msg(), bb_error_msg_and_die(), bb_herror_msg() and bb_herror_msg_and_die(), and replaces all calls that only take a single parameter, or use something like ("%s", arg), with calls to the corresponding 'simple' version. Since it is likely that single parameter calls to the variadic functions may be accidentally reintroduced in the future a new debugging config option WARN_SIMPLE_MSG has been introduced. This uses some macro magic which will cause any such calls to generate a warning, but this is turned off by default to avoid use of the unpleasant macros in normal circumstances. This is a large changeset due to the number of calls that have been replaced. The only files that contain changes other than simple substitution of function calls are libbb.h, libbb/herror_msg.c, libbb/verror_msg.c and libbb/xfuncs_printf.c. In miscutils/devfsd.c, networking/udhcp/common.h and util-linux/mdev.c additonal macros have been added for logging so that single parameter and multiple parameter logging variants exist. The amount of space saved varies considerably by architecture, and was found to be as follows (for 'defconfig' using GCC 7.4): Arm: -92 bytes MIPS: -52 bytes PPC: -1836 bytes x86_64: -938 bytes Note that for the MIPS architecture only an exception had to be made disabling the 'simple' calls for 'udhcp' (in networking/udhcp/common.h) because it made these files larger on MIPS. Signed-off-by: James Byrne <james.byrne@origamienergy.com> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2019-07-02 15:05:03 +05:30
bb_simple_error_msg_and_die("SELinux: access denied");
}
if (ENABLE_FEATURE_CLEAN_UP)
freecon(context);
}
#else
# define check_selinux_update_passwd(username) ((void)0)
#endif
/*
1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL)
only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser
2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL)
only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup
3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER)
only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a'
like in addgroup and member != NULL
4) delete a user: update_passwd(FILE, USER, NULL, NULL)
5) delete a group: update_passwd(FILE, GROUP, NULL, NULL)
6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER)
only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL
7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL)
only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd
or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd
8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER)
This function does not validate the arguments fed to it
so the calling program should take care of that.
Returns number of lines changed, or -1 on error.
*/
int FAST_FUNC update_passwd(const char *filename,
const char *name,
const char *new_passwd,
const char *member)
2007-07-21 02:59:49 +05:30
{
#if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP)
#define member NULL
#endif
2007-07-21 02:59:49 +05:30
struct stat sb;
struct flock lock;
FILE *old_fp;
FILE *new_fp;
char *fnamesfx;
char *sfx_char;
char *name_colon;
2007-07-21 02:59:49 +05:30
int old_fd;
int new_fd;
int i;
int changed_lines;
int ret = -1; /* failure */
/* used as a bool: "are we modifying /etc/shadow?" */
#if ENABLE_FEATURE_SHADOWPASSWDS
const char *shadow = strstr(filename, "shadow");
#else
# define shadow NULL
#endif
2007-07-21 02:59:49 +05:30
filename = xmalloc_follow_symlinks(filename);
if (filename == NULL)
return ret;
if (name)
check_selinux_update_passwd(name);
2007-07-21 02:59:49 +05:30
/* New passwd file, "/etc/passwd+" for now */
fnamesfx = xasprintf("%s+", filename);
sfx_char = &fnamesfx[strlen(fnamesfx)-1];
name_colon = xasprintf("%s:", name ? name : "");
2007-07-21 02:59:49 +05:30
if (shadow)
old_fp = fopen(filename, "r+");
else
old_fp = fopen_or_warn(filename, "r+");
if (!old_fp) {
if (shadow)
ret = 0; /* missing shadow is not an error */
2007-07-21 02:59:49 +05:30
goto free_mem;
}
2007-07-21 02:59:49 +05:30
old_fd = fileno(old_fp);
selinux_preserve_fcontext(old_fd);
2007-07-21 02:59:49 +05:30
/* Try to create "/etc/passwd+". Wait if it exists. */
i = 30;
do {
// FIXME: on last iteration try w/o O_EXCL but with O_TRUNC?
new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600);
2007-07-21 02:59:49 +05:30
if (new_fd >= 0) goto created;
if (errno != EEXIST) break;
usleep(100000); /* 0.1 sec */
} while (--i);
bb_perror_msg("can't create '%s'", fnamesfx);
2007-07-21 02:59:49 +05:30
goto close_old_fp;
created:
if (fstat(old_fd, &sb) == 0) {
2007-07-21 02:59:49 +05:30
fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */
fchown(new_fd, sb.st_uid, sb.st_gid);
}
errno = 0;
new_fp = xfdopen_for_write(new_fd);
2007-07-21 02:59:49 +05:30
/* Backup file is "/etc/passwd-" */
*sfx_char = '-';
/* Delete old backup */
i = (unlink(fnamesfx) && errno != ENOENT);
/* Create backup as a hardlink to current */
if (i || link(filename, fnamesfx))
bb_perror_msg("warning: can't create backup copy '%s'",
fnamesfx);
*sfx_char = '+';
2007-07-21 02:59:49 +05:30
/* Lock the password file before updating */
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if (fcntl(old_fd, F_SETLK, &lock) < 0)
bb_perror_msg("warning: can't lock '%s'", filename);
2007-07-21 02:59:49 +05:30
lock.l_type = F_UNLCK;
/* Read current password file, write updated /etc/passwd+ */
changed_lines = 0;
2007-07-21 02:59:49 +05:30
while (1) {
char *cp, *line;
line = xmalloc_fgetline(old_fp);
if (!line) /* EOF/error */
break;
#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
if (!name && member) {
/* Delete member from all groups */
/* line is "GROUP:PASSWD:[member1[,member2]...]" */
unsigned member_len = strlen(member);
char *list = strrchr(line, ':');
while (list) {
list++;
next_list_element:
libbb: introduce and use is_prefixed_with() function old new delta is_prefixed_with - 18 +18 complete_username 78 77 -1 man_main 737 735 -2 fsck_device 429 427 -2 unpack_ar_archive 80 76 -4 strip_unsafe_prefix 105 101 -4 singlemount 1054 1050 -4 rtc_adjtime_is_utc 90 86 -4 resolve_mount_spec 88 84 -4 parse_one_line 1029 1025 -4 parse_conf 1460 1456 -4 may_wakeup 83 79 -4 loadkmap_main 219 215 -4 get_irqs_from_stat 103 99 -4 get_header_cpio 913 909 -4 findfs_main 79 75 -4 fbsplash_main 1230 1226 -4 load_crontab 776 771 -5 expand_vars_to_list 1151 1146 -5 date_main 881 876 -5 skip_dev_pfx 30 24 -6 make_device 2199 2193 -6 complete_cmd_dir_file 773 767 -6 run_applet_and_exit 715 708 -7 uudecode_main 321 313 -8 pwdx_main 197 189 -8 execute 568 560 -8 i2cdetect_main 1186 1176 -10 procps_scan 1242 1230 -12 procps_read_smaps 1017 1005 -12 process_module 746 734 -12 patch_main 1903 1891 -12 nfsmount 3572 3560 -12 stack_machine 126 112 -14 process_timer_stats 449 435 -14 match_fstype 111 97 -14 do_ipaddr 1344 1330 -14 open_list_and_close 359 343 -16 get_header_tar 1795 1779 -16 prepend_new_eth_table 340 323 -17 fsck_main 1811 1794 -17 find_iface_state 56 38 -18 dnsd_main 1321 1303 -18 base_device 179 158 -21 find_keyword 104 82 -22 handle_incoming_and_exit 2785 2762 -23 parse_and_put_prompt 774 746 -28 modinfo 347 317 -30 find_action 204 171 -33 update_passwd 1470 1436 -34 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/49 up/down: 18/-540) Total: -522 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2015-03-12 22:18:34 +05:30
if (is_prefixed_with(list, member)) {
char c;
changed_lines++;
c = list[member_len];
if (c == '\0') {
if (list[-1] == ',')
list--;
*list = '\0';
break;
}
if (c == ',') {
overlapping_strcpy(list, list + member_len + 1);
goto next_list_element;
}
changed_lines--;
}
list = strchr(list, ',');
}
fprintf(new_fp, "%s\n", line);
goto next;
}
#endif
libbb: introduce and use is_prefixed_with() function old new delta is_prefixed_with - 18 +18 complete_username 78 77 -1 man_main 737 735 -2 fsck_device 429 427 -2 unpack_ar_archive 80 76 -4 strip_unsafe_prefix 105 101 -4 singlemount 1054 1050 -4 rtc_adjtime_is_utc 90 86 -4 resolve_mount_spec 88 84 -4 parse_one_line 1029 1025 -4 parse_conf 1460 1456 -4 may_wakeup 83 79 -4 loadkmap_main 219 215 -4 get_irqs_from_stat 103 99 -4 get_header_cpio 913 909 -4 findfs_main 79 75 -4 fbsplash_main 1230 1226 -4 load_crontab 776 771 -5 expand_vars_to_list 1151 1146 -5 date_main 881 876 -5 skip_dev_pfx 30 24 -6 make_device 2199 2193 -6 complete_cmd_dir_file 773 767 -6 run_applet_and_exit 715 708 -7 uudecode_main 321 313 -8 pwdx_main 197 189 -8 execute 568 560 -8 i2cdetect_main 1186 1176 -10 procps_scan 1242 1230 -12 procps_read_smaps 1017 1005 -12 process_module 746 734 -12 patch_main 1903 1891 -12 nfsmount 3572 3560 -12 stack_machine 126 112 -14 process_timer_stats 449 435 -14 match_fstype 111 97 -14 do_ipaddr 1344 1330 -14 open_list_and_close 359 343 -16 get_header_tar 1795 1779 -16 prepend_new_eth_table 340 323 -17 fsck_main 1811 1794 -17 find_iface_state 56 38 -18 dnsd_main 1321 1303 -18 base_device 179 158 -21 find_keyword 104 82 -22 handle_incoming_and_exit 2785 2762 -23 parse_and_put_prompt 774 746 -28 modinfo 347 317 -30 find_action 204 171 -33 update_passwd 1470 1436 -34 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/49 up/down: 18/-540) Total: -522 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2015-03-12 22:18:34 +05:30
cp = is_prefixed_with(line, name_colon);
if (!cp) {
fprintf(new_fp, "%s\n", line);
goto next;
}
/* We have a match with "name:"... */
libbb: introduce and use is_prefixed_with() function old new delta is_prefixed_with - 18 +18 complete_username 78 77 -1 man_main 737 735 -2 fsck_device 429 427 -2 unpack_ar_archive 80 76 -4 strip_unsafe_prefix 105 101 -4 singlemount 1054 1050 -4 rtc_adjtime_is_utc 90 86 -4 resolve_mount_spec 88 84 -4 parse_one_line 1029 1025 -4 parse_conf 1460 1456 -4 may_wakeup 83 79 -4 loadkmap_main 219 215 -4 get_irqs_from_stat 103 99 -4 get_header_cpio 913 909 -4 findfs_main 79 75 -4 fbsplash_main 1230 1226 -4 load_crontab 776 771 -5 expand_vars_to_list 1151 1146 -5 date_main 881 876 -5 skip_dev_pfx 30 24 -6 make_device 2199 2193 -6 complete_cmd_dir_file 773 767 -6 run_applet_and_exit 715 708 -7 uudecode_main 321 313 -8 pwdx_main 197 189 -8 execute 568 560 -8 i2cdetect_main 1186 1176 -10 procps_scan 1242 1230 -12 procps_read_smaps 1017 1005 -12 process_module 746 734 -12 patch_main 1903 1891 -12 nfsmount 3572 3560 -12 stack_machine 126 112 -14 process_timer_stats 449 435 -14 match_fstype 111 97 -14 do_ipaddr 1344 1330 -14 open_list_and_close 359 343 -16 get_header_tar 1795 1779 -16 prepend_new_eth_table 340 323 -17 fsck_main 1811 1794 -17 find_iface_state 56 38 -18 dnsd_main 1321 1303 -18 base_device 179 158 -21 find_keyword 104 82 -22 handle_incoming_and_exit 2785 2762 -23 parse_and_put_prompt 774 746 -28 modinfo 347 317 -30 find_action 204 171 -33 update_passwd 1470 1436 -34 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/49 up/down: 18/-540) Total: -522 bytes Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
2015-03-12 22:18:34 +05:30
/* cp points past "name:" */
#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
if (member) {
/* It's actually /etc/group+, not /etc/passwd+ */
if (ENABLE_FEATURE_ADDUSER_TO_GROUP
&& applet_name[0] == 'a'
) {
/* Add user to group */
fprintf(new_fp, "%s%s%s\n", line,
last_char_is(line, ':') ? "" : ",",
member);
changed_lines++;
} else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP
/* && applet_name[0] == 'd' */
) {
/* Delete user from group */
char *tmp;
const char *fmt = "%s";
/* find the start of the member list: last ':' */
cp = strrchr(line, ':');
/* cut it */
*cp++ = '\0';
/* write the cut line name:passwd:gid:
* or name:!:: */
fprintf(new_fp, "%s:", line);
/* parse the tokens of the member list */
tmp = cp;
while ((cp = strsep(&tmp, ",")) != NULL) {
if (strcmp(member, cp) != 0) {
fprintf(new_fp, fmt, cp);
fmt = ",%s";
} else {
/* found member, skip it */
changed_lines++;
}
}
fprintf(new_fp, "\n");
}
2007-07-21 02:59:49 +05:30
} else
#endif
if ((ENABLE_PASSWD && applet_name[0] == 'p')
|| (ENABLE_CHPASSWD && applet_name[0] == 'c')
) {
/* Change passwd */
cp = strchrnul(cp, ':'); /* move past old passwd */
if (shadow && *cp == ':') {
/* /etc/shadow's field 3 (passwd change date) needs updating */
/* move past old change date */
unsigned time_days = (unsigned long)(time(NULL)) / (24*60*60);
if (time_days == 0) {
/* 0 as change date has special meaning, avoid it */
time_days = 1;
}
cp = strchrnul(cp + 1, ':');
/* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */
fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd,
time_days, cp);
} else {
/* "name:" + "new_passwd" + ":rest of line" */
fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp);
}
changed_lines++;
} /* else delete user or group: skip the line */
next:
2007-07-21 02:59:49 +05:30
free(line);
}
if (changed_lines == 0) {
#if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP
if (member) {
if (ENABLE_ADDGROUP && applet_name[0] == 'a')
bb_error_msg("can't find %s in %s", name, filename);
if (ENABLE_DELGROUP && applet_name[0] == 'd')
bb_error_msg("can't find %s in %s", member, filename);
}
2009-04-14 13:36:59 +05:30
#endif
if ((ENABLE_ADDUSER || ENABLE_ADDGROUP)
&& applet_name[0] == 'a' && !member
) {
/* add user or group */
fprintf(new_fp, "%s%s\n", name_colon, new_passwd);
changed_lines++;
}
}
2007-07-21 02:59:49 +05:30
fcntl(old_fd, F_SETLK, &lock);
/* We do want all of them to execute, thus | instead of || */
errno = 0;
2007-07-21 02:59:49 +05:30
if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp))
|| rename(fnamesfx, filename)
2007-07-21 02:59:49 +05:30
) {
/* At least one of those failed */
bb_perror_nomsg();
2007-07-21 02:59:49 +05:30
goto unlink_new;
}
/* Success: ret >= 0 */
ret = changed_lines;
2007-07-21 02:59:49 +05:30
unlink_new:
if (ret < 0)
unlink(fnamesfx);
2007-07-21 02:59:49 +05:30
close_old_fp:
fclose(old_fp);
free_mem:
free(fnamesfx);
free((char *)filename);
free(name_colon);
2007-07-21 02:59:49 +05:30
return ret;
}