summaryrefslogtreecommitdiff
path: root/loginutils/add-remove-shell.c
blob: 757e5050366dd84ec34d99f4a9676b75583fd341 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
 * add-shell and remove-shell implementation for busybox
 *
 * Copyright (C) 2010 Nokia Corporation. All rights reserved.
 * Written by Alexander Shishkin <virtuoso@slind.org>
 *
 * Licensed under GPLv2 or later, see the LICENSE file in this source tree
 * for details.
 */

//applet:IF_ADD_SHELL(   APPLET_ODDNAME(add-shell   , add_remove_shell, _BB_DIR_USR_BIN, _BB_SUID_DROP, add_shell   ))
//applet:IF_REMOVE_SHELL(APPLET_ODDNAME(remove-shell, add_remove_shell, _BB_DIR_USR_BIN, _BB_SUID_DROP, remove_shell))

//kbuild:lib-$(CONFIG_ADD_SHELL)    += add-remove-shell.o
//kbuild:lib-$(CONFIG_REMOVE_SHELL) += add-remove-shell.o

//config:config ADD_SHELL
//config:       bool "add-shell"
//config:       default y if DESKTOP
//config:       help
//config:         Add shells to /etc/shells.
//config:
//config:config REMOVE_SHELL
//config:       bool "remove-shell"
//config:       default y if DESKTOP
//config:       help
//config:         Remove shells from /etc/shells.

//usage:#define add_shell_trivial_usage
//usage:       "SHELL..."
//usage:#define add_shell_full_usage "\n\n"
//usage:       "Add SHELLs to /etc/shells"

//usage:#define remove_shell_trivial_usage
//usage:       "SHELL..."
//usage:#define remove_shell_full_usage "\n\n"
//usage:       "Remove SHELLs from /etc/shells"

#include "libbb.h"

#define SHELLS_FILE "/etc/shells"

#define REMOVE_SHELL (ENABLE_REMOVE_SHELL && (!ENABLE_ADD_SHELL || applet_name[0] == 'r'))
#define ADD_SHELL    (ENABLE_ADD_SHELL && (!ENABLE_REMOVE_SHELL || applet_name[0] == 'a'))

/* NB: we use the _address_, not the value, of this string
 * as a "special value of pointer" in the code.
 */
static const char dont_add[] ALIGN1 = "\n";

int add_remove_shell_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int add_remove_shell_main(int argc UNUSED_PARAM, char **argv)
{
	FILE *orig_fp;
	char *orig_fn;
	char *new_fn;

	argv++;

	orig_fn = xmalloc_follow_symlinks(SHELLS_FILE);
	if (!orig_fn)
		return EXIT_FAILURE;
	orig_fp = fopen_for_read(orig_fn);

	new_fn = xasprintf("%s.tmp", orig_fn);
	/*
	 * O_TRUNC or O_EXCL? At the first glance, O_EXCL looks better,
	 * since it prevents races. But: (1) it requires a retry loop,
	 * (2) if /etc/shells.tmp is *stale*, then retry loop
	 * with O_EXCL will never succeed - it should have a timeout,
	 * after which it should revert to O_TRUNC.
	 * For now, I settle for O_TRUNC instead.
	 */
	xmove_fd(xopen(new_fn, O_WRONLY | O_CREAT | O_TRUNC), STDOUT_FILENO);

	/* TODO:
	struct stat sb;
	xfstat(fileno(orig_fp), &sb);
	xfchown(STDOUT_FILENO, sb.st_uid, sb.st_gid);
	xfchmod(STDOUT_FILENO, sb.st_mode);
	*/

	if (orig_fp) {
		/* Copy old file, possibly skipping removed shell names */
		char *line;
		while ((line = xmalloc_fgetline(orig_fp)) != NULL) {
			char **cpp = argv;
			while (*cpp) {
				if (strcmp(*cpp, line) == 0) {
					/* Old file has this shell name */
					if (REMOVE_SHELL) {
						/* we are remove-shell */
						/* delete this name by not copying it */
						goto next_line;
					}
					/* we are add-shell */
					/* mark this name as "do not add" */
					*cpp = (char*)dont_add;
				}
				cpp++;
			}
			/* copy shell name from old to new file */
			printf("%s\n", line);
 next_line:
			free(line);
		}
		if (ENABLE_FEATURE_CLEAN_UP)
			fclose(orig_fp);
	}

	if (ADD_SHELL) {
		char **cpp = argv;
		while (*cpp) {
			if (*cpp != dont_add)
				printf("%s\n", *cpp);
			cpp++;
		}
	}

	/* Ensure we wrote out everything */
	if (fclose(stdout) != 0) {
		xunlink(new_fn);
		bb_perror_msg_and_die("%s: write error", new_fn);
	}

	/* Small hole: if rename fails, /etc/shells.tmp is not removed */
	xrename(new_fn, orig_fn);

	if (ENABLE_FEATURE_CLEAN_UP) {
		free(orig_fn);
		free(new_fn);
	}

	return EXIT_SUCCESS;
}