]>
Commit | Line | Data |
---|---|---|
00196ec7 AM |
1 | /* |
2 | * Copyright (c) 1997-2003 Red Hat, Inc. All rights reserved. | |
3 | * | |
4 | * This software may be freely redistributed under the terms of the GNU | |
5 | * public license. | |
6 | * | |
7 | * You should have received a copy of the GNU General Public License | |
8 | * along with this program; if not, write to the Free Software | |
9 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
10 | * | |
11 | */ | |
12 | ||
7742e157 AF |
13 | #include <alloca.h> |
14 | #include <ctype.h> | |
15 | #include <errno.h> | |
16 | #include <fcntl.h> | |
17 | #include <stdio.h> | |
18 | #include <stdlib.h> | |
19 | #include <string.h> | |
20 | #include <sys/stat.h> | |
21 | #include <unistd.h> | |
00196ec7 | 22 | #include <limits.h> |
7742e157 | 23 | |
de1fc6ce | 24 | /* This will be running setuid root, so be careful! */ |
52471a31 | 25 | static const char * safeEnviron[] = { |
7742e157 AF |
26 | "PATH=/bin:/sbin:/usr/bin:/usr/sbin", |
27 | "HOME=/root", | |
28 | NULL | |
29 | }; | |
30 | ||
31 | #define FOUND_FALSE -1 | |
32 | #define NOT_FOUND 0 | |
33 | #define FOUND_TRUE 1 | |
34 | ||
e9aec22c AM |
35 | #ifndef SBINDIR |
36 | #define SBINDIR "/sbin" | |
37 | #endif | |
38 | ||
de1fc6ce JR |
39 | static void |
40 | usage(void) { | |
41 | fprintf(stderr, "usage: usernetctl <interface-config> <up|down|report>\n"); | |
42 | exit(1); | |
43 | } | |
7742e157 | 44 | |
de1fc6ce | 45 | static size_t |
52471a31 | 46 | testSafe(char *ifaceConfig, int fd) { |
7742e157 AF |
47 | struct stat sb; |
48 | ||
de1fc6ce | 49 | /* These shouldn't be symbolic links -- anal, but that's fine w/ mkj. */ |
52471a31 | 50 | if (fstat(fd, &sb)) { |
7742e157 AF |
51 | fprintf(stderr, "failed to stat %s: %s\n", ifaceConfig, |
52 | strerror(errno)); | |
53 | exit(1); | |
54 | } | |
55 | ||
de1fc6ce | 56 | /* Safety/sanity checks. */ |
7742e157 AF |
57 | if (!S_ISREG(sb.st_mode)) { |
58 | fprintf(stderr, "%s is not a normal file\n", ifaceConfig); | |
59 | exit(1); | |
60 | } | |
61 | ||
62 | if (sb.st_uid) { | |
63 | fprintf(stderr, "%s should be owned by root\n", ifaceConfig); | |
64 | exit(1); | |
65 | } | |
66 | ||
67 | if (sb.st_mode & S_IWOTH) { | |
68 | fprintf(stderr, "%s should not be world writeable\n", ifaceConfig); | |
69 | exit(1); | |
70 | } | |
71 | ||
72 | return sb.st_size; | |
73 | } | |
74 | ||
75 | ||
de1fc6ce JR |
76 | static int |
77 | userCtl(char *file) { | |
00196ec7 | 78 | char *buf; |
de1fc6ce JR |
79 | char *contents = NULL; |
80 | char *chptr = NULL; | |
81 | char *next = NULL; | |
82 | int fd = -1, retval = NOT_FOUND; | |
83 | size_t size = 0; | |
7742e157 | 84 | |
52471a31 AM |
85 | /* Open the file and then test it to see if we like it. This way |
86 | we avoid switcheroo attacks. */ | |
de1fc6ce | 87 | if ((fd = open(file, O_RDONLY)) == -1) { |
7742e157 AF |
88 | fprintf(stderr, "failed to open %s: %s\n", file, strerror(errno)); |
89 | exit(1); | |
90 | } | |
91 | ||
52471a31 AM |
92 | size = testSafe(file, fd); |
93 | if (size > INT_MAX) { | |
94 | fprintf(stderr, "file %s is too big\n", file); | |
95 | exit(1); | |
96 | } | |
97 | ||
98 | buf = contents = malloc(size + 2); | |
99 | if (contents == NULL) { | |
100 | fprintf(stderr, "failed to allocate memory\n"); | |
101 | exit(1); | |
102 | } | |
103 | ||
7742e157 AF |
104 | if (read(fd, contents, size) != size) { |
105 | perror("error reading device configuration"); | |
106 | exit(1); | |
107 | } | |
108 | close(fd); | |
109 | ||
110 | contents[size] = '\n'; | |
111 | contents[size + 1] = '\0'; | |
112 | ||
de1fc6ce JR |
113 | /* Each pass parses a single line (until an answer is found), The contents |
114 | pointer itself points to the beginning of the current line. */ | |
7742e157 AF |
115 | while (*contents) { |
116 | chptr = contents; | |
117 | while (*chptr != '\n') chptr++; | |
de1fc6ce | 118 | next = chptr + 1; |
12de71be | 119 | while (chptr >= contents && isspace(*chptr)) chptr--; |
7742e157 AF |
120 | *(++chptr) = '\0'; |
121 | ||
52471a31 | 122 | if (!strncasecmp(contents, "USERCTL=", 8)) { |
12de71be | 123 | contents += 8; |
00196ec7 AM |
124 | if ((contents[0] == '"' && |
125 | contents[strlen(contents) - 1] == '"') || | |
126 | (contents[0] == '\'' && | |
127 | contents[strlen(contents) - 1] == '\'')) | |
128 | { | |
12de71be | 129 | contents++; |
130 | contents[strlen(contents) - 1] = '\0'; | |
131 | } | |
132 | ||
52471a31 | 133 | if (!strcasecmp(contents, "yes") || !strcasecmp(contents, "true")) |
de1fc6ce | 134 | retval = FOUND_TRUE; |
12de71be | 135 | else |
de1fc6ce JR |
136 | retval = FOUND_FALSE; |
137 | ||
138 | break; | |
7742e157 AF |
139 | } |
140 | ||
de1fc6ce | 141 | contents = next; |
7742e157 AF |
142 | } |
143 | ||
00196ec7 | 144 | free(buf); |
7742e157 | 145 | |
de1fc6ce JR |
146 | return retval; |
147 | } | |
7742e157 | 148 | |
de1fc6ce JR |
149 | int |
150 | main(int argc, char ** argv) { | |
7742e157 AF |
151 | char * ifaceConfig; |
152 | char * chptr; | |
00196ec7 | 153 | char * cmd = NULL; |
7742e157 AF |
154 | int report = 0; |
155 | char tmp; | |
156 | ||
157 | if (argc != 3) usage(); | |
158 | ||
159 | if (!strcmp(argv[2], "up")) { | |
e9aec22c | 160 | cmd = SBINDIR "/ifup"; |
7742e157 | 161 | } else if (!strcmp(argv[2], "down")) { |
e9aec22c | 162 | cmd = SBINDIR "/ifdown"; |
7742e157 AF |
163 | } else if (!strcmp(argv[2], "report")) { |
164 | report = 1; | |
165 | } else { | |
166 | usage(); | |
167 | } | |
168 | ||
d25dc461 AM |
169 | if (chdir("/etc/sysconfig/interfaces")) { |
170 | fprintf(stderr, "error switching to /etc/sysconfig/interfaces: " | |
7742e157 AF |
171 | "%s\n", strerror(errno)); |
172 | exit(1); | |
173 | } | |
174 | ||
175 | /* force the interface configuration to be in the current directory */ | |
176 | chptr = ifaceConfig = argv[1]; | |
177 | while (*chptr) { | |
178 | if (*chptr == '/') | |
179 | ifaceConfig = chptr + 1; | |
180 | chptr++; | |
181 | } | |
182 | ||
183 | /* automatically prepend "ifcfg-" if it is not specified */ | |
184 | if (strncmp(ifaceConfig, "ifcfg-", 6)) { | |
185 | char *temp; | |
00196ec7 AM |
186 | size_t len = strlen(ifaceConfig); |
187 | ||
188 | /* Make sure a wise guys hasn't tried an integer wrap-around or | |
189 | stack overflow attack. There's no way it could refer to anything | |
190 | bigger than the largest filename, so cut 'em off there. */ | |
191 | if (len > PATH_MAX) | |
192 | exit(1); | |
7742e157 | 193 | |
00196ec7 | 194 | temp = (char *) alloca(len + 7); |
7742e157 AF |
195 | strcpy(temp, "ifcfg-"); |
196 | /* strcat is safe because we got the length from strlen */ | |
197 | strcat(temp, ifaceConfig); | |
198 | ifaceConfig = temp; | |
199 | } | |
200 | ||
de1fc6ce | 201 | if(getuid() != 0) |
7742e157 AF |
202 | switch (userCtl(ifaceConfig)) { |
203 | char *dash; | |
204 | ||
205 | case NOT_FOUND: | |
206 | /* a `-' will be found at least in "ifcfg-" */ | |
207 | dash = strrchr(ifaceConfig, '-'); | |
208 | if (*(dash-1) != 'g') { | |
209 | /* This was a clone configuration; ask the parent config */ | |
210 | tmp = *dash; | |
211 | *dash = '\0'; | |
212 | if (userCtl(ifaceConfig) == FOUND_TRUE) { | |
213 | /* exit the switch; users are allowed to control */ | |
214 | *dash = tmp; | |
215 | break; | |
216 | } | |
217 | *dash = tmp; | |
218 | } | |
219 | /* else fall through */ | |
220 | case FOUND_FALSE: | |
221 | if (! report) | |
222 | fprintf(stderr, | |
223 | "Users are not allowed to control this interface.\n"); | |
224 | exit(1); | |
225 | break; | |
226 | } | |
227 | ||
228 | /* looks good to me -- let's go for it if we are changing the interface, | |
229 | * report good status to the user otherwise */ | |
230 | ||
231 | if (report) | |
232 | exit(0); | |
233 | ||
234 | /* pppd wants the real uid to be the same as the effective (god only | |
235 | knows why when it works fine setuid out of the box) */ | |
236 | setuid(geteuid()); | |
237 | ||
238 | execle(cmd, cmd, ifaceConfig, NULL, safeEnviron); | |
239 | fprintf(stderr, "exec of %s failed: %s\n", cmd, strerror(errno)); | |
240 | ||
241 | exit(1); | |
242 | } |