]>
Commit | Line | Data |
---|---|---|
20ab58c7 SH |
1 | /* |
2 | * | |
3 | * Copyright © 2013 Serge Hallyn <serge.hallyn@ubuntu.com>. | |
4 | * Copyright © 2013 Canonical Ltd. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License version 2, as | |
8 | * published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; if not, write to the Free Software Foundation, Inc., | |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 | */ | |
19 | ||
d38dd64a CB |
20 | #ifndef _GNU_SOURCE |
21 | #define _GNU_SOURCE 1 | |
22 | #endif | |
1f109d47 | 23 | #include <alloca.h> |
d38dd64a | 24 | #include <arpa/inet.h> |
1f109d47 CB |
25 | #include <ctype.h> |
26 | #include <errno.h> | |
27 | #include <fcntl.h> | |
28 | #include <grp.h> | |
4119204e | 29 | #include <linux/netlink.h> |
20ab58c7 SH |
30 | #include <linux/rtnetlink.h> |
31 | #include <linux/sockios.h> | |
1f109d47 CB |
32 | #include <net/if.h> |
33 | #include <net/if_arp.h> | |
34 | #include <netinet/in.h> | |
d38dd64a CB |
35 | #include <pwd.h> |
36 | #include <sched.h> | |
37 | #include <stdbool.h> | |
38 | #include <stdio.h> | |
39 | #include <stdlib.h> | |
40 | #include <string.h> | |
1f109d47 CB |
41 | #include <sys/file.h> |
42 | #include <sys/ioctl.h> | |
43 | #include <sys/mman.h> | |
4119204e | 44 | #include <sys/param.h> |
1f109d47 CB |
45 | #include <sys/socket.h> |
46 | #include <sys/stat.h> | |
47 | #include <sys/types.h> | |
d38dd64a | 48 | #include <unistd.h> |
f2363e38 | 49 | |
37fc7b9e | 50 | #include "config.h" |
3725a69c | 51 | #include "log.h" |
8befa924 | 52 | #include "network.h" |
9994d140 | 53 | #include "parse.h" |
47d720d1 | 54 | #include "raw_syscalls.h" |
59524108 | 55 | #include "syscall_wrappers.h" |
1f109d47 | 56 | #include "utils.h" |
20ab58c7 | 57 | |
43f984ea DJ |
58 | #ifndef HAVE_STRLCPY |
59 | #include "include/strlcpy.h" | |
60 | #endif | |
61 | ||
16af2380 CB |
62 | #define usernic_debug_stream(stream, format, ...) \ |
63 | do { \ | |
64 | fprintf(stream, "%s: %d: %s: " format, __FILE__, __LINE__, \ | |
65 | __func__, __VA_ARGS__); \ | |
66 | } while (false) | |
67 | ||
68 | #define usernic_error(format, ...) usernic_debug_stream(stderr, format, __VA_ARGS__) | |
69 | ||
74a3920a | 70 | static void usage(char *me, bool fail) |
20ab58c7 | 71 | { |
900e5f94 CB |
72 | fprintf(stderr, "Usage: %s create {lxcpath} {name} {pid} {type} " |
73 | "{bridge} {nicname}\n", me); | |
cb5659e1 CB |
74 | fprintf(stderr, "Usage: %s delete {lxcpath} {name} " |
75 | "{/proc/<pid>/ns/net} {type} {bridge} {nicname}\n", me); | |
900e5f94 CB |
76 | fprintf(stderr, "{nicname} is the name to use inside the container\n"); |
77 | ||
78 | if (fail) | |
3725a69c | 79 | _exit(EXIT_FAILURE); |
20ab58c7 | 80 | |
3725a69c | 81 | _exit(EXIT_SUCCESS); |
900e5f94 | 82 | } |
c43cbc04 | 83 | |
552a7d05 | 84 | static int open_and_lock(char *path) |
20ab58c7 | 85 | { |
af256970 | 86 | int fd, ret; |
20ab58c7 SH |
87 | struct flock lk; |
88 | ||
af256970 | 89 | fd = open(path, O_RDWR | O_CREAT, S_IWUSR | S_IRUSR); |
20ab58c7 | 90 | if (fd < 0) { |
3725a69c | 91 | CMD_SYSERROR("Failed to open \"%s\"\n", path); |
2b333aee | 92 | return -1; |
20ab58c7 SH |
93 | } |
94 | ||
95 | lk.l_type = F_WRLCK; | |
96 | lk.l_whence = SEEK_SET; | |
97 | lk.l_start = 0; | |
98 | lk.l_len = 0; | |
af256970 CB |
99 | |
100 | ret = fcntl(fd, F_SETLKW, &lk); | |
101 | if (ret < 0) { | |
3725a69c | 102 | CMD_SYSERROR("Failed to lock \"%s\"\n", path); |
49aba4d3 | 103 | close(fd); |
78f97d4c | 104 | return -1; |
20ab58c7 SH |
105 | } |
106 | ||
107 | return fd; | |
108 | } | |
109 | ||
552a7d05 | 110 | static char *get_username(void) |
20ab58c7 | 111 | { |
cb7aa5e8 DJ |
112 | struct passwd pwent; |
113 | struct passwd *pwentp = NULL; | |
114 | char *buf; | |
115 | char *username; | |
116 | size_t bufsize; | |
117 | int ret; | |
118 | ||
119 | bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); | |
120 | if (bufsize == -1) | |
121 | bufsize = 1024; | |
122 | ||
123 | buf = malloc(bufsize); | |
124 | if (!buf) | |
125 | return NULL; | |
126 | ||
127 | ret = getpwuid_r(getuid(), &pwent, buf, bufsize, &pwentp); | |
128 | if (!pwentp) { | |
129 | if (ret == 0) | |
130 | usernic_error("%s", "Could not find matched password record\n"); | |
20ab58c7 | 131 | |
3725a69c | 132 | CMD_SYSERROR("Failed to get username: %u\n", getuid()); |
cb7aa5e8 | 133 | free(buf); |
20ab58c7 SH |
134 | return NULL; |
135 | } | |
136 | ||
cb7aa5e8 DJ |
137 | username = strdup(pwent.pw_name); |
138 | free(buf); | |
139 | ||
140 | return username; | |
20ab58c7 SH |
141 | } |
142 | ||
21002b39 HK |
143 | static void free_groupnames(char **groupnames) |
144 | { | |
d791668b | 145 | int i; |
2b333aee | 146 | |
d791668b SH |
147 | if (!groupnames) |
148 | return; | |
2b333aee | 149 | |
d791668b SH |
150 | for (i = 0; groupnames[i]; i++) |
151 | free(groupnames[i]); | |
2b333aee | 152 | |
21002b39 HK |
153 | free(groupnames); |
154 | } | |
155 | ||
3043883c HK |
156 | static char **get_groupnames(void) |
157 | { | |
158 | int ngroups; | |
159 | gid_t *group_ids; | |
21002b39 | 160 | int ret, i; |
af59ff2e | 161 | char **groupnames; |
3de9fb4c DJ |
162 | struct group grent; |
163 | struct group *grentp = NULL; | |
164 | char *buf; | |
165 | size_t bufsize; | |
3043883c HK |
166 | |
167 | ngroups = getgroups(0, NULL); | |
2b333aee | 168 | if (ngroups < 0) { |
3725a69c | 169 | CMD_SYSERROR("Failed to get number of groups the user belongs to\n"); |
af59ff2e | 170 | return NULL; |
3725a69c | 171 | } else if (ngroups == 0) { |
21002b39 | 172 | return NULL; |
3725a69c | 173 | } |
1b7eaf07 | 174 | |
2b333aee CB |
175 | group_ids = malloc(sizeof(gid_t) * ngroups); |
176 | if (!group_ids) { | |
3725a69c | 177 | CMD_SYSERROR("Failed to allocate memory while getting groups the user belongs to\n"); |
21002b39 HK |
178 | return NULL; |
179 | } | |
180 | ||
3043883c | 181 | ret = getgroups(ngroups, group_ids); |
3043883c HK |
182 | if (ret < 0) { |
183 | free(group_ids); | |
3725a69c | 184 | CMD_SYSERROR("Failed to get process groups\n"); |
3043883c HK |
185 | return NULL; |
186 | } | |
187 | ||
2b333aee CB |
188 | groupnames = malloc(sizeof(char *) * (ngroups + 1)); |
189 | if (!groupnames) { | |
21002b39 | 190 | free(group_ids); |
3725a69c | 191 | CMD_SYSERROR("Failed to allocate memory while getting group names\n"); |
21002b39 HK |
192 | return NULL; |
193 | } | |
194 | ||
a5452e50 | 195 | memset(groupnames, 0, sizeof(char *) * (ngroups + 1)); |
196 | ||
3de9fb4c DJ |
197 | bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); |
198 | if (bufsize == -1) | |
199 | bufsize = 1024; | |
200 | ||
201 | buf = malloc(bufsize); | |
202 | if (!buf) { | |
203 | free(group_ids); | |
204 | free_groupnames(groupnames); | |
3725a69c | 205 | CMD_SYSERROR("Failed to allocate memory while getting group names\n"); |
3de9fb4c DJ |
206 | return NULL; |
207 | } | |
208 | ||
2b333aee | 209 | for (i = 0; i < ngroups; i++) { |
3de9fb4c DJ |
210 | ret = getgrgid_r(group_ids[i], &grent, buf, bufsize, &grentp); |
211 | if (!grentp) { | |
212 | if (ret == 0) | |
213 | usernic_error("%s", "Could not find matched group record\n"); | |
214 | ||
3725a69c | 215 | CMD_SYSERROR("Failed to get group name: %u\n", group_ids[i]); |
3de9fb4c | 216 | free(buf); |
3043883c | 217 | free(group_ids); |
21002b39 | 218 | free_groupnames(groupnames); |
3043883c HK |
219 | return NULL; |
220 | } | |
221 | ||
3de9fb4c | 222 | groupnames[i] = strdup(grent.gr_name); |
2b333aee | 223 | if (!groupnames[i]) { |
3725a69c | 224 | usernic_error("Failed to copy group name \"%s\"", grent.gr_name); |
3de9fb4c | 225 | free(buf); |
3043883c | 226 | free(group_ids); |
21002b39 | 227 | free_groupnames(groupnames); |
1b7eaf07 | 228 | return NULL; |
3043883c HK |
229 | } |
230 | } | |
231 | ||
3de9fb4c | 232 | free(buf); |
3043883c HK |
233 | free(group_ids); |
234 | ||
af59ff2e HK |
235 | return groupnames; |
236 | } | |
237 | ||
af59ff2e HK |
238 | static bool name_is_in_groupnames(char *name, char **groupnames) |
239 | { | |
2b333aee CB |
240 | while (groupnames) { |
241 | if (!strcmp(name, *groupnames)) | |
af59ff2e HK |
242 | return true; |
243 | groupnames++; | |
244 | } | |
3725a69c | 245 | |
af59ff2e | 246 | return false; |
3043883c HK |
247 | } |
248 | ||
af59ff2e HK |
249 | struct alloted_s { |
250 | char *name; | |
251 | int allowed; | |
252 | struct alloted_s *next; | |
253 | }; | |
3043883c | 254 | |
af256970 CB |
255 | static struct alloted_s *append_alloted(struct alloted_s **head, char *name, |
256 | int n) | |
3043883c HK |
257 | { |
258 | struct alloted_s *cur, *al; | |
259 | ||
2b333aee | 260 | if (!head || !name) { |
af256970 | 261 | /* Sanity check. Parameters should not be null. */ |
e771c51d | 262 | usernic_error("%s\n", "Unexpected NULL argument"); |
3043883c HK |
263 | return NULL; |
264 | } | |
265 | ||
2b333aee CB |
266 | al = malloc(sizeof(struct alloted_s)); |
267 | if (!al) { | |
3725a69c | 268 | CMD_SYSERROR("Failed to allocate memory\n"); |
3043883c HK |
269 | return NULL; |
270 | } | |
271 | ||
272 | al->name = strdup(name); | |
2b333aee | 273 | if (!al->name) { |
21002b39 HK |
274 | free(al); |
275 | return NULL; | |
276 | } | |
277 | ||
3043883c HK |
278 | al->allowed = n; |
279 | al->next = NULL; | |
280 | ||
2b333aee | 281 | if (!*head) { |
3043883c HK |
282 | *head = al; |
283 | return al; | |
284 | } | |
285 | ||
286 | cur = *head; | |
2b333aee | 287 | while (cur->next) |
3043883c | 288 | cur = cur->next; |
3043883c | 289 | cur->next = al; |
2b333aee | 290 | |
3043883c HK |
291 | return al; |
292 | } | |
293 | ||
294 | static void free_alloted(struct alloted_s **head) | |
295 | { | |
296 | struct alloted_s *cur; | |
297 | ||
2b333aee | 298 | if (!head) |
3043883c | 299 | return; |
3043883c HK |
300 | |
301 | cur = *head; | |
2b333aee | 302 | while (cur) { |
3043883c HK |
303 | cur = cur->next; |
304 | free((*head)->name); | |
305 | free(*head); | |
306 | *head = cur; | |
307 | } | |
308 | } | |
309 | ||
20ab58c7 SH |
310 | /* The configuration file consists of lines of the form: |
311 | * | |
8c81de19 | 312 | * user type bridge count |
af59ff2e HK |
313 | * or |
314 | * @group type bridge count | |
20ab58c7 | 315 | * |
8c81de19 SH |
316 | * Return the count entry for the calling user if there is one. Else |
317 | * return -1. | |
20ab58c7 | 318 | */ |
af256970 CB |
319 | static int get_alloted(char *me, char *intype, char *link, |
320 | struct alloted_s **alloted) | |
20ab58c7 | 321 | { |
2b333aee | 322 | int n, ret; |
af59ff2e | 323 | char name[100], type[100], br[100]; |
3043883c | 324 | char **groups; |
2b333aee | 325 | FILE *fin; |
2b333aee CB |
326 | int count = 0; |
327 | size_t len = 0; | |
328 | char *line = NULL; | |
329 | ||
330 | fin = fopen(LXC_USERNIC_CONF, "r"); | |
8c81de19 | 331 | if (!fin) { |
3725a69c | 332 | CMD_SYSERROR("Failed to open \"%s\"\n", LXC_USERNIC_CONF); |
20ab58c7 | 333 | return -1; |
8c81de19 | 334 | } |
20ab58c7 | 335 | |
af59ff2e | 336 | groups = get_groupnames(); |
20ab58c7 | 337 | while ((getline(&line, &len, fin)) != -1) { |
af256970 CB |
338 | ret = sscanf(line, "%99[^ \t] %99[^ \t] %99[^ \t] %d", name, |
339 | type, br, &n); | |
20ab58c7 SH |
340 | if (ret != 4) |
341 | continue; | |
af59ff2e HK |
342 | |
343 | if (strlen(name) == 0) | |
20ab58c7 | 344 | continue; |
af59ff2e | 345 | |
2b333aee | 346 | if (strcmp(name, me)) { |
af59ff2e HK |
347 | if (name[0] != '@') |
348 | continue; | |
2b333aee CB |
349 | |
350 | if (!name_is_in_groupnames(name + 1, groups)) | |
af59ff2e HK |
351 | continue; |
352 | } | |
2b333aee CB |
353 | |
354 | if (strcmp(type, intype)) | |
20ab58c7 | 355 | continue; |
2b333aee CB |
356 | |
357 | if (strcmp(link, br)) | |
20ab58c7 | 358 | continue; |
3043883c | 359 | |
2b333aee CB |
360 | /* Found the user or group with the appropriate settings, |
361 | * therefore finish the search. What to do if there are more | |
362 | * than one applicable lines? not specified in the docs. Since | |
363 | * getline is implemented with realloc, we don't need to free | |
364 | * line until exiting func. | |
680836fa | 365 | * |
2b333aee CB |
366 | * If append_alloted returns NULL, e.g. due to a malloc error, |
367 | * we set count to 0 and break the loop, allowing cleanup and | |
368 | * then exiting from main(). | |
af59ff2e | 369 | */ |
2b333aee | 370 | if (!append_alloted(alloted, name, n)) { |
680836fa HK |
371 | count = 0; |
372 | break; | |
373 | } | |
3725a69c | 374 | |
3043883c | 375 | count += n; |
3043883c HK |
376 | } |
377 | ||
af59ff2e | 378 | free_groupnames(groups); |
20ab58c7 | 379 | fclose(fin); |
f10fad2f | 380 | free(line); |
21002b39 | 381 | |
2b333aee | 382 | /* Now return the total number of nics that this user can create. */ |
3043883c | 383 | return count; |
20ab58c7 SH |
384 | } |
385 | ||
8f7b58d6 | 386 | static char *get_eol(char *s, char *e) |
20ab58c7 | 387 | { |
2b333aee | 388 | while ((s < e) && *s && (*s != '\n')) |
20ab58c7 | 389 | s++; |
3725a69c | 390 | |
20ab58c7 SH |
391 | return s; |
392 | } | |
393 | ||
8f7b58d6 | 394 | static char *get_eow(char *s, char *e) |
20ab58c7 | 395 | { |
2b333aee | 396 | while ((s < e) && *s && !isblank(*s) && (*s != '\n')) |
20ab58c7 | 397 | s++; |
3725a69c | 398 | |
20ab58c7 SH |
399 | return s; |
400 | } | |
401 | ||
6d8c2779 CB |
402 | static char *find_line(char *buf_start, char *buf_end, char *name, |
403 | char *net_type, char *net_link, char *net_dev, | |
404 | bool *owner, bool *found, bool *keep) | |
20ab58c7 | 405 | { |
6d8c2779 | 406 | char *end_of_line, *end_of_word, *line; |
1b7eaf07 | 407 | |
6d8c2779 CB |
408 | while (buf_start < buf_end) { |
409 | size_t len; | |
410 | char netdev_name[IFNAMSIZ]; | |
411 | ||
412 | *found = false; | |
413 | *keep = true; | |
414 | *owner = false; | |
415 | ||
416 | end_of_line = get_eol(buf_start, buf_end); | |
417 | if (end_of_line >= buf_end) | |
418 | return NULL; | |
419 | ||
420 | line = buf_start; | |
421 | if (*buf_start == '#') | |
20ab58c7 | 422 | goto next; |
2b333aee | 423 | |
6d8c2779 CB |
424 | while ((buf_start < buf_end) && isblank(*buf_start)) |
425 | buf_start++; | |
426 | ||
427 | /* Check whether the line contains the caller's name. */ | |
428 | end_of_word = get_eow(buf_start, buf_end); | |
429 | /* corrupt db */ | |
430 | if (!end_of_word) | |
431 | return NULL; | |
432 | ||
433 | if (strncmp(buf_start, name, strlen(name))) | |
434 | *found = false; | |
435 | ||
436 | *owner = true; | |
2b333aee | 437 | |
6d8c2779 CB |
438 | buf_start = end_of_word + 1; |
439 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
440 | buf_start++; | |
441 | ||
442 | /* Check whether line is of the right network type. */ | |
443 | end_of_word = get_eow(buf_start, buf_end); | |
444 | /* corrupt db */ | |
445 | if (!end_of_word) | |
446 | return NULL; | |
2b333aee | 447 | |
6d8c2779 CB |
448 | if (strncmp(buf_start, net_type, strlen(net_type))) |
449 | *found = false; | |
450 | ||
451 | buf_start = end_of_word + 1; | |
452 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
453 | buf_start++; | |
454 | ||
455 | /* Check whether line is contains the right link. */ | |
456 | end_of_word = get_eow(buf_start, buf_end); | |
457 | /* corrupt db */ | |
458 | if (!end_of_word) | |
459 | return NULL; | |
460 | ||
461 | if (strncmp(buf_start, net_link, strlen(net_link))) | |
462 | *found = false; | |
463 | ||
464 | buf_start = end_of_word + 1; | |
465 | while ((buf_start < buf_end) && isblank(*buf_start)) | |
466 | buf_start++; | |
467 | ||
468 | /* Check whether line contains the right network device. */ | |
469 | end_of_word = get_eow(buf_start, buf_end); | |
470 | /* corrupt db */ | |
471 | if (!end_of_word) | |
472 | return NULL; | |
473 | ||
474 | len = end_of_word - buf_start; | |
475 | /* corrupt db */ | |
476 | if (len >= IFNAMSIZ) | |
477 | return NULL; | |
2b333aee | 478 | |
6d8c2779 CB |
479 | memcpy(netdev_name, buf_start, len); |
480 | netdev_name[len] = '\0'; | |
481 | *keep = lxc_nic_exists(netdev_name); | |
2b333aee | 482 | |
6d8c2779 CB |
483 | if (net_dev && !strcmp(netdev_name, net_dev)) |
484 | *found = true; | |
2b333aee | 485 | |
6d8c2779 | 486 | return line; |
2b333aee | 487 | |
af256970 | 488 | next: |
6d8c2779 | 489 | buf_start = end_of_line + 1; |
20ab58c7 SH |
490 | } |
491 | ||
492 | return NULL; | |
493 | } | |
494 | ||
cb5659e1 | 495 | static int instantiate_veth(char *veth1, char *veth2) |
20ab58c7 | 496 | { |
cb5659e1 | 497 | int ret; |
20ab58c7 | 498 | |
cb5659e1 CB |
499 | ret = lxc_veth_create(veth1, veth2); |
500 | if (ret < 0) { | |
3725a69c | 501 | errno = -ret; |
502 | CMD_SYSERROR("Failed to create %s-%s\n", veth1, veth2); | |
78f97d4c | 503 | return -1; |
20ab58c7 SH |
504 | } |
505 | ||
2b333aee CB |
506 | /* Changing the high byte of the mac address to 0xfe, the bridge |
507 | * interface will always keep the host's mac address and not take the | |
75ca3dc6 TH |
508 | * mac address of a container. |
509 | */ | |
cb5659e1 | 510 | ret = setup_private_host_hw_addr(veth1); |
3725a69c | 511 | if (ret < 0) { |
512 | errno = -ret; | |
513 | CMD_SYSERROR("Failed to change mac address of host interface %s\n", veth1); | |
514 | } | |
20ab58c7 | 515 | |
cb5659e1 | 516 | return netdev_set_flag(veth1, IFF_UP); |
20ab58c7 SH |
517 | } |
518 | ||
0130df54 SH |
519 | static int get_mtu(char *name) |
520 | { | |
2b333aee CB |
521 | int idx; |
522 | ||
523 | idx = if_nametoindex(name); | |
8424b4e1 CB |
524 | if (idx < 0) |
525 | return -1; | |
3725a69c | 526 | |
0130df54 SH |
527 | return netdev_get_mtu(idx); |
528 | } | |
529 | ||
cb5659e1 | 530 | static int create_nic(char *nic, char *br, int pid, char **cnic) |
20ab58c7 | 531 | { |
cb5659e1 | 532 | char veth1buf[IFNAMSIZ], veth2buf[IFNAMSIZ]; |
2b333aee CB |
533 | int mtu, ret; |
534 | ||
20ab58c7 SH |
535 | ret = snprintf(veth1buf, IFNAMSIZ, "%s", nic); |
536 | if (ret < 0 || ret >= IFNAMSIZ) { | |
e771c51d | 537 | usernic_error("%s", "Could not create nic name\n"); |
cb5659e1 | 538 | return -1; |
20ab58c7 SH |
539 | } |
540 | ||
cb5659e1 CB |
541 | ret = snprintf(veth2buf, IFNAMSIZ, "%sp", veth1buf); |
542 | if (ret < 0 || ret >= IFNAMSIZ) { | |
543 | usernic_error("%s\n", "Could not create nic name"); | |
544 | return -1; | |
545 | } | |
3725a69c | 546 | |
20ab58c7 | 547 | /* create the nics */ |
cb5659e1 CB |
548 | ret = instantiate_veth(veth1buf, veth2buf); |
549 | if (ret < 0) { | |
e771c51d | 550 | usernic_error("%s", "Error creating veth tunnel\n"); |
cb5659e1 | 551 | return -1; |
20ab58c7 SH |
552 | } |
553 | ||
2b333aee | 554 | if (strcmp(br, "none")) { |
cff7b5eb FN |
555 | /* copy the bridge's mtu to both ends */ |
556 | mtu = get_mtu(br); | |
2b333aee CB |
557 | if (mtu > 0) { |
558 | ret = lxc_netdev_set_mtu(veth1buf, mtu); | |
559 | if (ret < 0) { | |
af256970 | 560 | usernic_error("Failed to set mtu to %d on %s\n", |
2ec1c484 | 561 | mtu, veth1buf); |
2b333aee CB |
562 | goto out_del; |
563 | } | |
564 | ||
565 | ret = lxc_netdev_set_mtu(veth2buf, mtu); | |
566 | if (ret < 0) { | |
af256970 | 567 | usernic_error("Failed to set mtu to %d on %s\n", |
2ec1c484 | 568 | mtu, veth2buf); |
cff7b5eb FN |
569 | goto out_del; |
570 | } | |
0130df54 | 571 | } |
0130df54 | 572 | |
cff7b5eb | 573 | /* attach veth1 to bridge */ |
581c75e7 | 574 | ret = lxc_bridge_attach(br, veth1buf); |
2b333aee | 575 | if (ret < 0) { |
e771c51d | 576 | usernic_error("Error attaching %s to %s\n", veth1buf, br); |
cff7b5eb FN |
577 | goto out_del; |
578 | } | |
20ab58c7 SH |
579 | } |
580 | ||
581 | /* pass veth2 to target netns */ | |
8d357196 | 582 | ret = lxc_netdev_move_by_name(veth2buf, pid, NULL); |
20ab58c7 | 583 | if (ret < 0) { |
af256970 | 584 | usernic_error("Error moving %s to network namespace of %d\n", |
2ec1c484 | 585 | veth2buf, pid); |
20ab58c7 SH |
586 | goto out_del; |
587 | } | |
2b333aee | 588 | |
4119204e | 589 | *cnic = strdup(veth2buf); |
2b333aee | 590 | if (!*cnic) { |
e771c51d | 591 | usernic_error("Failed to copy string \"%s\"\n", veth2buf); |
cb5659e1 | 592 | return -1; |
2b333aee CB |
593 | } |
594 | ||
cb5659e1 | 595 | return 0; |
20ab58c7 SH |
596 | |
597 | out_del: | |
598 | lxc_netdev_delete_by_name(veth1buf); | |
cb5659e1 | 599 | return -1; |
20ab58c7 SH |
600 | } |
601 | ||
03c2eb15 SH |
602 | struct entry_line { |
603 | char *start; | |
604 | int len; | |
605 | bool keep; | |
606 | }; | |
607 | ||
6d8c2779 CB |
608 | static bool cull_entries(int fd, char *name, char *net_type, char *net_link, |
609 | char *net_dev, bool *found_nicname) | |
20ab58c7 | 610 | { |
8b8e00a2 | 611 | int i, ret; |
6d8c2779 | 612 | char *buf, *buf_end, *buf_start; |
2b333aee | 613 | struct stat sb; |
8b8e00a2 | 614 | int n = 0; |
6d8c2779 | 615 | bool found, keep; |
03c2eb15 | 616 | struct entry_line *entry_lines = NULL; |
20ab58c7 | 617 | |
8b8e00a2 CB |
618 | ret = fstat(fd, &sb); |
619 | if (ret < 0) { | |
3725a69c | 620 | CMD_SYSERROR("Failed to fstat\n"); |
1e985428 SG |
621 | return false; |
622 | } | |
2b333aee | 623 | |
6d8c2779 CB |
624 | if (!sb.st_size) |
625 | return false; | |
2b333aee | 626 | |
8b8e00a2 | 627 | buf = lxc_strmmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
20ab58c7 | 628 | if (buf == MAP_FAILED) { |
3725a69c | 629 | CMD_SYSERROR("Failed to establish shared memory mapping\n"); |
20ab58c7 SH |
630 | return false; |
631 | } | |
632 | ||
6d8c2779 CB |
633 | buf_start = buf; |
634 | buf_end = buf + sb.st_size; | |
635 | while ((buf_start = find_line(buf_start, buf_end, name, net_type, | |
636 | net_link, net_dev, &(bool){true}, &found, | |
637 | &keep))) { | |
2b333aee CB |
638 | struct entry_line *newe; |
639 | ||
640 | newe = realloc(entry_lines, sizeof(*entry_lines) * (n + 1)); | |
03c2eb15 SH |
641 | if (!newe) { |
642 | free(entry_lines); | |
6d8c2779 | 643 | lxc_strmunmap(buf, sb.st_size); |
03c2eb15 SH |
644 | return false; |
645 | } | |
2b333aee | 646 | |
6d8c2779 CB |
647 | if (found) |
648 | *found_nicname = true; | |
649 | ||
03c2eb15 | 650 | entry_lines = newe; |
6d8c2779 CB |
651 | entry_lines[n].start = buf_start; |
652 | entry_lines[n].len = get_eol(buf_start, buf_end) - entry_lines[n].start; | |
653 | entry_lines[n].keep = keep; | |
03c2eb15 | 654 | n++; |
2b333aee | 655 | |
6d8c2779 CB |
656 | buf_start += entry_lines[n - 1].len + 1; |
657 | if (buf_start >= buf_end) | |
20ab58c7 | 658 | break; |
2b333aee CB |
659 | } |
660 | ||
6d8c2779 | 661 | buf_start = buf; |
3725a69c | 662 | |
2b333aee | 663 | for (i = 0; i < n; i++) { |
03c2eb15 SH |
664 | if (!entry_lines[i].keep) |
665 | continue; | |
2b333aee | 666 | |
6d8c2779 CB |
667 | memcpy(buf_start, entry_lines[i].start, entry_lines[i].len); |
668 | buf_start += entry_lines[i].len; | |
669 | *buf_start = '\n'; | |
670 | buf_start++; | |
03c2eb15 | 671 | } |
3725a69c | 672 | |
03c2eb15 | 673 | free(entry_lines); |
2b333aee | 674 | |
6d8c2779 | 675 | ret = ftruncate(fd, buf_start - buf); |
3ca10814 | 676 | lxc_strmunmap(buf, sb.st_size); |
8b8e00a2 | 677 | if (ret < 0) |
3725a69c | 678 | CMD_SYSERROR("Failed to set new file size\n"); |
2b333aee | 679 | |
20ab58c7 SH |
680 | return true; |
681 | } | |
682 | ||
6d8c2779 | 683 | static int count_entries(char *buf, off_t len, char *name, char *net_type, char *net_link) |
20ab58c7 | 684 | { |
20ab58c7 | 685 | int count = 0; |
2115d566 | 686 | bool owner = false; |
cef588fc | 687 | char *buf_end; |
6d8c2779 CB |
688 | |
689 | buf_end = &buf[len]; | |
690 | while ((buf = find_line(buf, buf_end, name, net_type, net_link, NULL, | |
691 | &owner, &(bool){true}, &(bool){true}))) { | |
692 | if (owner) | |
693 | count++; | |
3725a69c | 694 | |
6d8c2779 CB |
695 | buf = get_eol(buf, buf_end) + 1; |
696 | if (buf >= buf_end) | |
20ab58c7 SH |
697 | break; |
698 | } | |
699 | ||
700 | return count; | |
701 | } | |
702 | ||
af256970 | 703 | /* The dbfile has lines of the format: user type bridge nicname. */ |
8285dcfb CB |
704 | static char *get_nic_if_avail(int fd, struct alloted_s *names, int pid, |
705 | char *intype, char *br, int allowed, char **cnic) | |
20ab58c7 | 706 | { |
2b333aee | 707 | int ret; |
a92028b2 | 708 | size_t slen; |
cb5659e1 CB |
709 | char *newline, *owner; |
710 | char nicname[IFNAMSIZ]; | |
20ab58c7 | 711 | struct stat sb; |
3043883c | 712 | struct alloted_s *n; |
2b333aee | 713 | char *buf = NULL; |
20ab58c7 | 714 | |
2b333aee | 715 | for (n = names; n != NULL; n = n->next) |
8b8e00a2 | 716 | cull_entries(fd, n->name, intype, br, NULL, NULL); |
20ab58c7 | 717 | |
c073c147 | 718 | if (allowed == 0) |
8285dcfb | 719 | return NULL; |
c073c147 | 720 | |
3043883c HK |
721 | owner = names->name; |
722 | ||
a92028b2 CB |
723 | ret = fstat(fd, &sb); |
724 | if (ret < 0) { | |
3725a69c | 725 | CMD_SYSERROR("Failed to fstat\n"); |
8285dcfb | 726 | return NULL; |
1e985428 | 727 | } |
2b333aee | 728 | |
a92028b2 CB |
729 | if (sb.st_size > 0) { |
730 | buf = lxc_strmmap(NULL, sb.st_size, PROT_READ | PROT_WRITE, | |
731 | MAP_SHARED, fd, 0); | |
20ab58c7 | 732 | if (buf == MAP_FAILED) { |
3725a69c | 733 | CMD_SYSERROR("Failed to establish shared memory mapping\n"); |
8285dcfb | 734 | return NULL; |
20ab58c7 SH |
735 | } |
736 | ||
3043883c | 737 | owner = NULL; |
3725a69c | 738 | |
2b333aee | 739 | for (n = names; n != NULL; n = n->next) { |
aac44dc4 TH |
740 | int count; |
741 | ||
a92028b2 | 742 | count = count_entries(buf, sb.st_size, n->name, intype, br); |
3043883c HK |
743 | if (count >= n->allowed) |
744 | continue; | |
745 | ||
746 | owner = n->name; | |
747 | break; | |
748 | } | |
a92028b2 CB |
749 | |
750 | lxc_strmunmap(buf, sb.st_size); | |
20ab58c7 SH |
751 | } |
752 | ||
55186593 | 753 | if (!owner) |
8285dcfb | 754 | return NULL; |
3043883c | 755 | |
cb5659e1 CB |
756 | ret = snprintf(nicname, sizeof(nicname), "vethXXXXXX"); |
757 | if (ret < 0 || (size_t)ret >= sizeof(nicname)) | |
758 | return NULL; | |
759 | ||
760 | if (!lxc_mkifname(nicname)) | |
761 | return NULL; | |
762 | ||
763 | ret = create_nic(nicname, br, pid, cnic); | |
764 | if (ret < 0) { | |
765 | usernic_error("%s", "Failed to create new nic\n"); | |
8285dcfb CB |
766 | return NULL; |
767 | } | |
2b333aee | 768 | |
a92028b2 CB |
769 | /* strlen(owner) |
770 | * + | |
771 | * " " | |
772 | * + | |
773 | * strlen(intype) | |
774 | * + | |
775 | * " " | |
776 | * + | |
777 | * strlen(br) | |
778 | * + | |
779 | * " " | |
780 | * + | |
781 | * strlen(nicname) | |
782 | * + | |
783 | * \n | |
784 | * + | |
785 | * \0 | |
786 | */ | |
787 | slen = strlen(owner) + strlen(intype) + strlen(br) + strlen(nicname) + 4; | |
788 | newline = malloc(slen + 1); | |
2b333aee | 789 | if (!newline) { |
a92028b2 | 790 | free(newline); |
3725a69c | 791 | CMD_SYSERROR("Failed allocate memory\n"); |
8285dcfb | 792 | return NULL; |
2b333aee CB |
793 | } |
794 | ||
a92028b2 CB |
795 | ret = snprintf(newline, slen + 1, "%s %s %s %s\n", owner, intype, br, nicname); |
796 | if (ret < 0 || (size_t)ret >= (slen + 1)) { | |
8285dcfb CB |
797 | if (lxc_netdev_delete_by_name(nicname) != 0) |
798 | usernic_error("Error unlinking %s\n", nicname); | |
3725a69c | 799 | |
a92028b2 | 800 | free(newline); |
8285dcfb | 801 | return NULL; |
20ab58c7 | 802 | } |
2b333aee | 803 | |
a92028b2 CB |
804 | /* Note that the file needs to be truncated to the size **without** the |
805 | * \0 byte! Files are not \0-terminated! | |
806 | */ | |
807 | ret = ftruncate(fd, sb.st_size + slen); | |
808 | if (ret < 0) | |
3725a69c | 809 | CMD_SYSERROR("Failed to truncate file\n"); |
2b333aee | 810 | |
a92028b2 CB |
811 | buf = lxc_strmmap(NULL, sb.st_size + slen, PROT_READ | PROT_WRITE, |
812 | MAP_SHARED, fd, 0); | |
20ab58c7 | 813 | if (buf == MAP_FAILED) { |
3725a69c | 814 | CMD_SYSERROR("Failed to establish shared memory mapping\n"); |
815 | ||
8285dcfb CB |
816 | if (lxc_netdev_delete_by_name(nicname) != 0) |
817 | usernic_error("Error unlinking %s\n", nicname); | |
3725a69c | 818 | |
a92028b2 | 819 | free(newline); |
8285dcfb | 820 | return NULL; |
20ab58c7 | 821 | } |
2b333aee | 822 | |
a92028b2 CB |
823 | /* Note that the memory needs to be moved in the buffer **without** the |
824 | * \0 byte! Files are not \0-terminated! | |
825 | */ | |
826 | memmove(buf + sb.st_size, newline, slen); | |
827 | free(newline); | |
828 | lxc_strmunmap(buf, sb.st_size + slen); | |
2b333aee | 829 | |
cb5659e1 | 830 | return strdup(nicname); |
20ab58c7 SH |
831 | } |
832 | ||
552a7d05 | 833 | static bool create_db_dir(char *fnam) |
070a4b8e | 834 | { |
af256970 | 835 | int ret; |
2b333aee | 836 | char *p; |
43f984ea | 837 | size_t len; |
070a4b8e | 838 | |
43f984ea DJ |
839 | len = strlen(fnam); |
840 | p = alloca(len + 1); | |
841 | (void)strlcpy(p, fnam, len + 1); | |
070a4b8e SH |
842 | fnam = p; |
843 | p = p + 1; | |
2b333aee | 844 | |
070a4b8e | 845 | again: |
2b333aee CB |
846 | while (*p && *p != '/') |
847 | p++; | |
3725a69c | 848 | |
070a4b8e SH |
849 | if (!*p) |
850 | return true; | |
2b333aee | 851 | |
070a4b8e | 852 | *p = '\0'; |
af256970 CB |
853 | |
854 | ret = mkdir(fnam, 0755); | |
855 | if (ret < 0 && errno != EEXIST) { | |
3725a69c | 856 | CMD_SYSERROR("Failed to create %s\n", fnam); |
070a4b8e SH |
857 | *p = '/'; |
858 | return false; | |
859 | } | |
3725a69c | 860 | |
070a4b8e | 861 | *(p++) = '/'; |
2b333aee | 862 | |
070a4b8e SH |
863 | goto again; |
864 | } | |
865 | ||
0cffb676 | 866 | static char *lxc_secure_rename_in_ns(int pid, char *oldname, char *newname, |
74c6e2b0 | 867 | int *container_veth_ifidx) |
4119204e | 868 | { |
ba464e9b | 869 | int ofd, ret; |
0059379f | 870 | pid_t pid_self; |
16af2380 | 871 | uid_t ruid, suid, euid; |
c92dfebd CB |
872 | char ifname[IFNAMSIZ]; |
873 | char *string_ret = NULL, *name = NULL; | |
ba464e9b | 874 | int fd = -1, ifindex = -1; |
4119204e | 875 | |
0059379f | 876 | pid_self = lxc_raw_getpid(); |
3725a69c | 877 | |
0059379f | 878 | ofd = lxc_preserve_ns(pid_self, "net"); |
5d04811e | 879 | if (ofd < 0) { |
0059379f | 880 | usernic_error("Failed opening network namespace path for %d", pid_self); |
c92dfebd | 881 | return NULL; |
4119204e | 882 | } |
4119204e | 883 | |
5d04811e CB |
884 | fd = lxc_preserve_ns(pid, "net"); |
885 | if (fd < 0) { | |
e771c51d | 886 | usernic_error("Failed opening network namespace path for %d", pid); |
16af2380 | 887 | goto do_partial_cleanup; |
4119204e | 888 | } |
5d04811e | 889 | |
16af2380 CB |
890 | ret = getresuid(&ruid, &euid, &suid); |
891 | if (ret < 0) { | |
3725a69c | 892 | CMD_SYSERROR("Failed to retrieve real, effective, and saved user IDs\n"); |
16af2380 | 893 | goto do_partial_cleanup; |
4119204e | 894 | } |
16af2380 CB |
895 | |
896 | ret = setns(fd, CLONE_NEWNET); | |
897 | close(fd); | |
898 | fd = -1; | |
899 | if (ret < 0) { | |
3725a69c | 900 | CMD_SYSERROR("Failed to setns() to the network namespace of " |
901 | "the container with PID %d\n", pid); | |
16af2380 CB |
902 | goto do_partial_cleanup; |
903 | } | |
904 | ||
905 | ret = setresuid(ruid, ruid, 0); | |
906 | if (ret < 0) { | |
3725a69c | 907 | CMD_SYSERROR("Failed to drop privilege by setting effective " |
908 | "user id and real user id to %d, and saved user " | |
909 | "ID to 0\n", ruid); | |
1a0e70ac CB |
910 | /* It's ok to jump to do_full_cleanup here since setresuid() |
911 | * will succeed when trying to set real, effective, and saved to | |
912 | * values they currently have. | |
913 | */ | |
16af2380 CB |
914 | goto do_full_cleanup; |
915 | } | |
916 | ||
c92dfebd CB |
917 | /* Check if old interface exists. */ |
918 | ifindex = if_nametoindex(oldname); | |
919 | if (!ifindex) { | |
3725a69c | 920 | CMD_SYSERROR("Failed to get netdev index\n"); |
c92dfebd | 921 | goto do_full_cleanup; |
8aecd66b | 922 | } |
16af2380 | 923 | |
c92dfebd CB |
924 | /* When the IFLA_IFNAME attribute is passed something like "<prefix>%d" |
925 | * netlink will replace the format specifier with an appropriate index. | |
926 | * So we pass "eth%d". | |
927 | */ | |
928 | if (newname) | |
929 | name = newname; | |
930 | else | |
931 | name = "eth%d"; | |
932 | ||
933 | ret = lxc_netdev_rename_by_name(oldname, name); | |
a055595c | 934 | name = NULL; |
16af2380 | 935 | if (ret < 0) { |
c92dfebd | 936 | usernic_error("Error %d renaming netdev %s to %s in container\n", |
3725a69c | 937 | ret, oldname, newname ? newname : "eth%d"); |
16af2380 | 938 | goto do_full_cleanup; |
4119204e | 939 | } |
16af2380 | 940 | |
c92dfebd CB |
941 | /* Retrieve new name for interface. */ |
942 | if (!if_indextoname(ifindex, ifname)) { | |
3725a69c | 943 | CMD_SYSERROR("Failed to get new netdev name\n"); |
c92dfebd | 944 | goto do_full_cleanup; |
8aecd66b | 945 | } |
16af2380 | 946 | |
c92dfebd CB |
947 | /* Allocation failure for strdup() is checked below. */ |
948 | name = strdup(ifname); | |
949 | string_ret = name; | |
74c6e2b0 | 950 | *container_veth_ifidx = ifindex; |
16af2380 CB |
951 | |
952 | do_full_cleanup: | |
953 | ret = setresuid(ruid, euid, suid); | |
954 | if (ret < 0) { | |
3725a69c | 955 | CMD_SYSERROR("Failed to restore privilege by setting " |
956 | "effective user id to %d, real user id to %d, " | |
957 | "and saved user ID to %d\n", ruid, euid, suid); | |
c92dfebd CB |
958 | |
959 | string_ret = NULL; | |
4119204e | 960 | } |
4119204e | 961 | |
16af2380 CB |
962 | ret = setns(ofd, CLONE_NEWNET); |
963 | if (ret < 0) { | |
3725a69c | 964 | CMD_SYSERROR("Failed to setns() to original network namespace " |
965 | "of PID %d\n", ofd); | |
c92dfebd CB |
966 | |
967 | string_ret = NULL; | |
16af2380 | 968 | } |
4119204e | 969 | |
16af2380 | 970 | do_partial_cleanup: |
4119204e SH |
971 | if (fd >= 0) |
972 | close(fd); | |
c92dfebd CB |
973 | |
974 | if (!string_ret && name) | |
975 | free(name); | |
976 | ||
16af2380 CB |
977 | close(ofd); |
978 | ||
c92dfebd | 979 | return string_ret; |
4119204e SH |
980 | } |
981 | ||
1a0e70ac CB |
982 | /* If the caller (real uid, not effective uid) may read the /proc/[pid]/ns/net, |
983 | * then it is either the caller's netns or one which it created. | |
8d9f636d SH |
984 | */ |
985 | static bool may_access_netns(int pid) | |
986 | { | |
987 | int ret; | |
988 | char s[200]; | |
989 | uid_t ruid, suid, euid; | |
990 | bool may_access = false; | |
991 | ||
992 | ret = getresuid(&ruid, &euid, &suid); | |
2b333aee | 993 | if (ret < 0) { |
3725a69c | 994 | CMD_SYSERROR("Failed to retrieve real, effective, and saved user IDs\n"); |
8d9f636d SH |
995 | return false; |
996 | } | |
2b333aee | 997 | |
8d9f636d | 998 | ret = setresuid(ruid, ruid, euid); |
2b333aee | 999 | if (ret < 0) { |
3725a69c | 1000 | CMD_SYSERROR("Failed to drop privilege by setting effective " |
1001 | "user id and real user id to %d, and saved user " | |
1002 | "ID to %d\n", ruid, euid); | |
8d9f636d SH |
1003 | return false; |
1004 | } | |
2b333aee | 1005 | |
8d9f636d | 1006 | ret = snprintf(s, 200, "/proc/%d/ns/net", pid); |
2b333aee | 1007 | if (ret < 0 || ret >= 200) |
8d9f636d | 1008 | return false; |
2b333aee | 1009 | |
8d9f636d | 1010 | ret = access(s, R_OK); |
2b333aee | 1011 | may_access = true; |
2ec1c484 | 1012 | if (ret < 0) { |
2b333aee | 1013 | may_access = false; |
3725a69c | 1014 | CMD_SYSERROR("Uid %d may not access %s\n", (int)ruid, s); |
8d9f636d | 1015 | } |
2b333aee | 1016 | |
8d9f636d | 1017 | ret = setresuid(ruid, euid, suid); |
2b333aee | 1018 | if (ret < 0) { |
3725a69c | 1019 | CMD_SYSERROR("Failed to restore user id to %d, real user id " |
1020 | "to %d, and saved user ID to %d\n", ruid, euid, suid); | |
8d9f636d SH |
1021 | may_access = false; |
1022 | } | |
2b333aee | 1023 | |
8d9f636d SH |
1024 | return may_access; |
1025 | } | |
1026 | ||
900e5f94 CB |
1027 | struct user_nic_args { |
1028 | char *cmd; | |
1029 | char *lxc_path; | |
1030 | char *lxc_name; | |
1031 | char *pid; | |
1032 | char *type; | |
1033 | char *link; | |
1034 | char *veth_name; | |
1035 | }; | |
1036 | ||
8b8e00a2 CB |
1037 | #define LXC_USERNIC_CREATE 0 |
1038 | #define LXC_USERNIC_DELETE 1 | |
1039 | ||
1bd8d726 CB |
1040 | static bool is_privileged_over_netns(int netns_fd) |
1041 | { | |
ba464e9b | 1042 | int ofd, ret; |
0059379f | 1043 | pid_t pid_self; |
1bd8d726 CB |
1044 | uid_t euid, ruid, suid; |
1045 | bool bret = false; | |
1bd8d726 | 1046 | |
0059379f | 1047 | pid_self = lxc_raw_getpid(); |
3725a69c | 1048 | |
0059379f | 1049 | ofd = lxc_preserve_ns(pid_self, "net"); |
1bd8d726 | 1050 | if (ofd < 0) { |
0059379f | 1051 | usernic_error("Failed opening network namespace path for %d", pid_self); |
1bd8d726 CB |
1052 | return false; |
1053 | } | |
1054 | ||
1055 | ret = getresuid(&ruid, &euid, &suid); | |
1056 | if (ret < 0) { | |
3725a69c | 1057 | CMD_SYSERROR("Failed to retrieve real, effective, and saved user IDs\n"); |
1bd8d726 CB |
1058 | goto do_partial_cleanup; |
1059 | } | |
1060 | ||
1061 | ret = setns(netns_fd, CLONE_NEWNET); | |
1062 | if (ret < 0) { | |
3725a69c | 1063 | CMD_SYSERROR("Failed to setns() to network namespace\n"); |
1bd8d726 CB |
1064 | goto do_partial_cleanup; |
1065 | } | |
1066 | ||
1067 | ret = setresuid(ruid, ruid, 0); | |
1068 | if (ret < 0) { | |
3725a69c | 1069 | CMD_SYSERROR("Failed to drop privilege by setting effective " |
1070 | "user id and real user id to %d, and saved user " | |
1071 | "ID to 0\n", ruid); | |
1bd8d726 CB |
1072 | /* It's ok to jump to do_full_cleanup here since setresuid() |
1073 | * will succeed when trying to set real, effective, and saved to | |
1074 | * values they currently have. | |
1075 | */ | |
1076 | goto do_full_cleanup; | |
1077 | } | |
1078 | ||
1079 | /* Test whether we are privileged over the network namespace. To do this | |
1080 | * we try to delete the loopback interface which is not possible. If we | |
1081 | * are privileged over the network namespace we will get ENOTSUP. If we | |
1082 | * are not privileged over the network namespace we will get EPERM. | |
1083 | */ | |
1084 | ret = lxc_netdev_delete_by_name("lo"); | |
1085 | if (ret == -ENOTSUP) | |
1086 | bret = true; | |
1087 | ||
1088 | do_full_cleanup: | |
1089 | ret = setresuid(ruid, euid, suid); | |
1090 | if (ret < 0) { | |
3725a69c | 1091 | CMD_SYSERROR("Failed to restore privilege by setting " |
1092 | "effective user id to %d, real user id to %d, " | |
1093 | "and saved user ID to %d\n", ruid, euid, suid); | |
1bd8d726 CB |
1094 | bret = false; |
1095 | } | |
1096 | ||
1097 | ret = setns(ofd, CLONE_NEWNET); | |
1098 | if (ret < 0) { | |
3725a69c | 1099 | CMD_SYSERROR("Failed to setns() to original network namespace " |
1100 | "of PID %d\n", ofd); | |
1bd8d726 CB |
1101 | bret = false; |
1102 | } | |
1103 | ||
1104 | do_partial_cleanup: | |
1bd8d726 CB |
1105 | close(ofd); |
1106 | return bret; | |
1107 | } | |
1108 | ||
20ab58c7 SH |
1109 | int main(int argc, char *argv[]) |
1110 | { | |
99573f4a | 1111 | int fd, n, pid, request, ret; |
c92dfebd | 1112 | char *me, *newname; |
1bd8d726 | 1113 | struct user_nic_args args; |
99573f4a | 1114 | int container_veth_ifidx = -1, host_veth_ifidx = -1, netns_fd = -1; |
25aead3f | 1115 | char *cnic = NULL, *nicname = NULL; |
3043883c | 1116 | struct alloted_s *alloted = NULL; |
900e5f94 CB |
1117 | |
1118 | if (argc < 7 || argc > 8) { | |
1119 | usage(argv[0], true); | |
3725a69c | 1120 | _exit(EXIT_FAILURE); |
900e5f94 CB |
1121 | } |
1122 | ||
1123 | memset(&args, 0, sizeof(struct user_nic_args)); | |
3725a69c | 1124 | |
900e5f94 CB |
1125 | args.cmd = argv[1]; |
1126 | args.lxc_path = argv[2]; | |
1127 | args.lxc_name = argv[3]; | |
1128 | args.pid = argv[4]; | |
1129 | args.type = argv[5]; | |
1130 | args.link = argv[6]; | |
1131 | if (argc >= 8) | |
1132 | args.veth_name = argv[7]; | |
20ab58c7 | 1133 | |
8b8e00a2 CB |
1134 | if (!strcmp(args.cmd, "create")) { |
1135 | request = LXC_USERNIC_CREATE; | |
1136 | } else if (!strcmp(args.cmd, "delete")) { | |
1137 | request = LXC_USERNIC_DELETE; | |
1138 | } else { | |
1139 | usage(argv[0], true); | |
3725a69c | 1140 | _exit(EXIT_FAILURE); |
8b8e00a2 CB |
1141 | } |
1142 | ||
d04e77c3 CB |
1143 | /* Set a sane env, because we are setuid-root. */ |
1144 | ret = clearenv(); | |
1145 | if (ret) { | |
e771c51d | 1146 | usernic_error("%s", "Failed to clear environment\n"); |
3725a69c | 1147 | _exit(EXIT_FAILURE); |
361b4fe7 | 1148 | } |
d04e77c3 CB |
1149 | |
1150 | ret = setenv("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); | |
1151 | if (ret < 0) { | |
e771c51d | 1152 | usernic_error("%s", "Failed to set PATH, exiting\n"); |
3725a69c | 1153 | _exit(EXIT_FAILURE); |
6ad22d06 | 1154 | } |
d04e77c3 CB |
1155 | |
1156 | me = get_username(); | |
1157 | if (!me) { | |
e771c51d | 1158 | usernic_error("%s", "Failed to get username\n"); |
3725a69c | 1159 | _exit(EXIT_FAILURE); |
20ab58c7 SH |
1160 | } |
1161 | ||
1bd8d726 CB |
1162 | if (request == LXC_USERNIC_CREATE) { |
1163 | ret = lxc_safe_int(args.pid, &pid); | |
1164 | if (ret < 0) { | |
1165 | usernic_error("Could not read pid: %s\n", args.pid); | |
3725a69c | 1166 | _exit(EXIT_FAILURE); |
1bd8d726 CB |
1167 | } |
1168 | } else if (request == LXC_USERNIC_DELETE) { | |
f26dc127 CB |
1169 | char opath[LXC_PROC_PID_FD_LEN]; |
1170 | ||
1171 | /* Open the path with O_PATH which will not trigger an actual | |
1172 | * open(). Don't report an errno to the caller to not leak | |
1173 | * information whether the path exists or not. | |
1174 | * When stracing setuid is stripped so this is not a concern | |
1175 | * either. | |
1176 | */ | |
1177 | netns_fd = open(args.pid, O_PATH | O_CLOEXEC); | |
1bd8d726 | 1178 | if (netns_fd < 0) { |
f26dc127 | 1179 | usernic_error("Failed to open \"%s\"\n", args.pid); |
3725a69c | 1180 | _exit(EXIT_FAILURE); |
f26dc127 CB |
1181 | } |
1182 | ||
1183 | if (!fhas_fs_type(netns_fd, NSFS_MAGIC)) { | |
1184 | usernic_error("Path \"%s\" does not refer to a network namespace path\n", args.pid); | |
1185 | close(netns_fd); | |
3725a69c | 1186 | _exit(EXIT_FAILURE); |
f26dc127 CB |
1187 | } |
1188 | ||
1189 | ret = snprintf(opath, sizeof(opath), "/proc/self/fd/%d", netns_fd); | |
1190 | if (ret < 0 || (size_t)ret >= sizeof(opath)) { | |
1191 | close(netns_fd); | |
3725a69c | 1192 | _exit(EXIT_FAILURE); |
f26dc127 CB |
1193 | } |
1194 | ||
1195 | /* Now get an fd that we can use in setns() calls. */ | |
1196 | ret = open(opath, O_RDONLY | O_CLOEXEC); | |
1197 | if (ret < 0) { | |
3725a69c | 1198 | CMD_SYSERROR("Failed to open \"%s\"\n", args.pid); |
f26dc127 | 1199 | close(netns_fd); |
3725a69c | 1200 | _exit(EXIT_FAILURE); |
1bd8d726 | 1201 | } |
3725a69c | 1202 | |
f26dc127 CB |
1203 | close(netns_fd); |
1204 | netns_fd = ret; | |
8d9f636d SH |
1205 | } |
1206 | ||
8befa924 | 1207 | if (!create_db_dir(LXC_USERNIC_DB)) { |
e771c51d | 1208 | usernic_error("%s", "Failed to create directory for db file\n"); |
3725a69c | 1209 | |
1bd8d726 CB |
1210 | if (netns_fd >= 0) |
1211 | close(netns_fd); | |
3725a69c | 1212 | |
1213 | _exit(EXIT_FAILURE); | |
070a4b8e SH |
1214 | } |
1215 | ||
d04e77c3 CB |
1216 | fd = open_and_lock(LXC_USERNIC_DB); |
1217 | if (fd < 0) { | |
e771c51d | 1218 | usernic_error("Failed to lock %s\n", LXC_USERNIC_DB); |
3725a69c | 1219 | |
1bd8d726 CB |
1220 | if (netns_fd >= 0) |
1221 | close(netns_fd); | |
3725a69c | 1222 | |
1223 | _exit(EXIT_FAILURE); | |
20ab58c7 SH |
1224 | } |
1225 | ||
1bd8d726 CB |
1226 | if (request == LXC_USERNIC_CREATE) { |
1227 | if (!may_access_netns(pid)) { | |
1228 | usernic_error("User %s may not modify netns for pid %d\n", me, pid); | |
3725a69c | 1229 | _exit(EXIT_FAILURE); |
1bd8d726 CB |
1230 | } |
1231 | } else if (request == LXC_USERNIC_DELETE) { | |
1232 | bool has_priv; | |
3725a69c | 1233 | |
1bd8d726 CB |
1234 | has_priv = is_privileged_over_netns(netns_fd); |
1235 | close(netns_fd); | |
1236 | if (!has_priv) { | |
1237 | usernic_error("%s", "Process is not privileged over " | |
2ec1c484 | 1238 | "network namespace\n"); |
3725a69c | 1239 | _exit(EXIT_FAILURE); |
1bd8d726 | 1240 | } |
8d9f636d SH |
1241 | } |
1242 | ||
8b8e00a2 | 1243 | n = get_alloted(me, args.type, args.link, &alloted); |
cb7aa5e8 | 1244 | free(me); |
a055595c | 1245 | |
8b8e00a2 CB |
1246 | if (request == LXC_USERNIC_DELETE) { |
1247 | int ret; | |
1248 | struct alloted_s *it; | |
1249 | bool found_nicname = false; | |
1250 | ||
1251 | if (!is_ovs_bridge(args.link)) { | |
a055595c | 1252 | usernic_error("%s", "Deletion of non ovs type network " |
2ec1c484 | 1253 | "devices not implemented\n"); |
8b8e00a2 CB |
1254 | close(fd); |
1255 | free_alloted(&alloted); | |
3725a69c | 1256 | _exit(EXIT_FAILURE); |
8b8e00a2 CB |
1257 | } |
1258 | ||
1259 | /* Check whether the network device we are supposed to delete | |
1260 | * exists in the db. If it doesn't we will not delete it as we | |
1261 | * need to assume the network device is not under our control. | |
1262 | * As a side effect we also clear any invalid entries from the | |
1263 | * database. | |
1264 | */ | |
1265 | for (it = alloted; it; it = it->next) | |
1266 | cull_entries(fd, it->name, args.type, args.link, | |
1267 | args.veth_name, &found_nicname); | |
1268 | close(fd); | |
1269 | free_alloted(&alloted); | |
1270 | ||
1271 | if (!found_nicname) { | |
32311345 | 1272 | usernic_error("Caller is not allowed to delete network " |
2ec1c484 | 1273 | "device \"%s\"\n", args.veth_name); |
3725a69c | 1274 | _exit(EXIT_FAILURE); |
a055595c CB |
1275 | } |
1276 | ||
1277 | ret = lxc_ovs_delete_port(args.link, args.veth_name); | |
1278 | if (ret < 0) { | |
1279 | usernic_error("Failed to remove port \"%s\" from " | |
2ec1c484 TH |
1280 | "openvswitch bridge \"%s\"", |
1281 | args.veth_name, args.link); | |
3725a69c | 1282 | _exit(EXIT_FAILURE); |
a055595c | 1283 | } |
8b8e00a2 | 1284 | |
3725a69c | 1285 | _exit(EXIT_SUCCESS); |
a055595c | 1286 | } |
3725a69c | 1287 | |
20ab58c7 | 1288 | if (n > 0) |
900e5f94 CB |
1289 | nicname = get_nic_if_avail(fd, alloted, pid, args.type, |
1290 | args.link, n, &cnic); | |
3043883c | 1291 | |
20ab58c7 | 1292 | close(fd); |
3043883c | 1293 | free_alloted(&alloted); |
3725a69c | 1294 | |
8285dcfb | 1295 | if (!nicname) { |
e771c51d | 1296 | usernic_error("%s", "Quota reached\n"); |
3725a69c | 1297 | _exit(EXIT_FAILURE); |
20ab58c7 | 1298 | } |
20ab58c7 | 1299 | |
2b333aee | 1300 | /* Now rename the link. */ |
74c6e2b0 CB |
1301 | newname = lxc_secure_rename_in_ns(pid, cnic, args.veth_name, |
1302 | &container_veth_ifidx); | |
c92dfebd | 1303 | if (!newname) { |
e771c51d | 1304 | usernic_error("%s", "Failed to rename the link\n"); |
3725a69c | 1305 | |
d04e77c3 CB |
1306 | ret = lxc_netdev_delete_by_name(cnic); |
1307 | if (ret < 0) | |
1308 | usernic_error("Failed to delete \"%s\"\n", cnic); | |
3725a69c | 1309 | |
8285dcfb | 1310 | free(nicname); |
3725a69c | 1311 | _exit(EXIT_FAILURE); |
4119204e | 1312 | } |
cb5659e1 | 1313 | |
74c6e2b0 | 1314 | host_veth_ifidx = if_nametoindex(nicname); |
8424b4e1 CB |
1315 | if (!host_veth_ifidx) { |
1316 | free(newname); | |
1317 | free(nicname); | |
3725a69c | 1318 | CMD_SYSERROR("Failed to get netdev index\n"); |
1319 | _exit(EXIT_FAILURE); | |
8424b4e1 | 1320 | } |
20ab58c7 | 1321 | |
74c6e2b0 CB |
1322 | /* Write names of veth pairs and their ifindeces to stout: |
1323 | * (e.g. eth0:731:veth9MT2L4:730) | |
1324 | */ | |
1325 | fprintf(stdout, "%s:%d:%s:%d\n", newname, container_veth_ifidx, nicname, | |
1326 | host_veth_ifidx); | |
c92dfebd | 1327 | free(newname); |
8285dcfb | 1328 | free(nicname); |
3725a69c | 1329 | |
1330 | fflush(stdout); | |
1331 | _exit(EXIT_SUCCESS); | |
20ab58c7 | 1332 | } |