1 /** 2 * Copyright (C) 2012-2014 Steven Barth <steven@midlink.org> 3 * Copyright (C) 2017-2018 Hans Dedecker <dedeckeh@gmail.com> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License v2 as published by 7 * the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 */ 15 16 #include <arpa/inet.h> 17 #include <errno.h> 18 #include <inttypes.h> 19 #include <netdb.h> 20 #include <netinet/in.h> 21 #include <resolv.h> 22 #include <signal.h> 23 #include <stdio.h> 24 #include <stdlib.h> 25 #include <string.h> 26 #include <sys/wait.h> 27 #include <unistd.h> 28 29 #include "odhcp6c.h" 30 31 static const char hexdigits[] = "0123456789abcdef"; 32 static const int8_t hexvals[] = { 33 -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -1, -1, -2, -1, -1, 34 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 35 -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 36 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 37 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 38 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 39 -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 40 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 41 }; 42 43 static char action[16] = ""; 44 static char *argv[4] = {NULL, NULL, action, NULL}; 45 static volatile pid_t running = 0; 46 static time_t started; 47 48 static void script_sighandle(int signal) 49 { 50 if (signal == SIGCHLD) { 51 pid_t child; 52 53 while ((child = waitpid(-1, NULL, WNOHANG)) > 0) 54 if (running == child) 55 running = 0; 56 } 57 } 58 59 int script_init(const char *path, const char *ifname) 60 { 61 argv[0] = (char*)path; 62 argv[1] = (char*)ifname; 63 signal(SIGCHLD, script_sighandle); 64 65 return 0; 66 } 67 68 ssize_t script_unhexlify(uint8_t *dst, size_t len, const char *src) 69 { 70 size_t c; 71 72 for (c = 0; c < len && src[0] && src[1]; ++c) { 73 int8_t x = (int8_t)*src++; 74 int8_t y = (int8_t)*src++; 75 if (x < 0 || (x = hexvals[x]) < 0 76 || y < 0 || (y = hexvals[y]) < 0) 77 return -1; 78 dst[c] = x << 4 | y; 79 while (((int8_t)*src) < 0 || 80 (*src && hexvals[(uint8_t)*src] < 0)) 81 src++; 82 } 83 84 return c; 85 } 86 87 void script_hexlify(char *dst, const uint8_t *src, size_t len) 88 { 89 for (size_t i = 0; i < len; ++i) { 90 *dst++ = hexdigits[src[i] >> 4]; 91 *dst++ = hexdigits[src[i] & 0x0f]; 92 } 93 94 *dst = 0; 95 } 96 97 static void ipv6_to_env(const char *name, 98 const struct in6_addr *addr, size_t cnt) 99 { 100 size_t buf_len = strlen(name); 101 char *buf = malloc(cnt * INET6_ADDRSTRLEN + buf_len + 2); 102 103 if (!buf) 104 return; 105 106 memcpy(buf, name, buf_len); 107 buf[buf_len++] = '='; 108 109 for (size_t i = 0; i < cnt; ++i) { 110 inet_ntop(AF_INET6, &addr[i], &buf[buf_len], INET6_ADDRSTRLEN); 111 buf_len += strlen(&buf[buf_len]); 112 buf[buf_len++] = ' '; 113 } 114 115 if (buf[buf_len - 1] == ' ') 116 buf_len--; 117 118 buf[buf_len] = '\0'; 119 putenv(buf); 120 } 121 122 static void fqdn_to_env(const char *name, const uint8_t *fqdn, size_t len) 123 { 124 size_t buf_len = strlen(name); 125 size_t buf_size = len + buf_len + 2; 126 const uint8_t *fqdn_end = fqdn + len; 127 char *buf = malloc(buf_size); 128 129 if (!buf) 130 return; 131 132 memcpy(buf, name, buf_len); 133 buf[buf_len++] = '='; 134 135 while (fqdn < fqdn_end) { 136 int l = dn_expand(fqdn, fqdn_end, fqdn, &buf[buf_len], buf_size - buf_len); 137 if (l <= 0) 138 break; 139 fqdn += l; 140 buf_len += strlen(&buf[buf_len]); 141 buf[buf_len++] = ' '; 142 } 143 144 if (buf[buf_len - 1] == ' ') 145 buf_len--; 146 147 buf[buf_len] = '\0'; 148 putenv(buf); 149 } 150 151 static void string_to_env(const char *name, const uint8_t *string, size_t len) 152 { 153 size_t name_len = strlen(name); 154 char *buf = malloc(name_len + 1 + len + 1); 155 156 if (!buf) 157 return; 158 159 memcpy(buf, name, name_len); 160 buf[name_len] = '='; 161 memcpy(&buf[name_len + 1], string, len); 162 buf[name_len + 1 + len] = '\0'; 163 putenv(buf); 164 } 165 166 static void bin_to_env(uint8_t *opts, size_t len) 167 { 168 uint8_t *oend = opts + len, *odata; 169 uint16_t otype, olen; 170 171 dhcpv6_for_each_option(opts, oend, otype, olen, odata) { 172 char *buf = malloc(14 + (olen * 2)); 173 size_t buf_len = 0; 174 175 if (!buf) 176 continue; 177 178 snprintf(buf, 14, "OPTION_%hu=", otype); 179 buf_len += strlen(buf); 180 181 script_hexlify(&buf[buf_len], odata, olen); 182 putenv(buf); 183 } 184 } 185 186 enum entry_type { 187 ENTRY_ADDRESS, 188 ENTRY_HOST, 189 ENTRY_ROUTE, 190 ENTRY_PREFIX 191 }; 192 193 static void entry_to_env(const char *name, const void *data, size_t len, enum entry_type type) 194 { 195 size_t buf_len = strlen(name); 196 const uint8_t *start = data; 197 // Worst case: ENTRY_PREFIX with iaid != 1 and exclusion 198 const size_t max_entry_len = (INET6_ADDRSTRLEN-1 + 5 + 44 + 15 + 10 + 199 INET6_ADDRSTRLEN-1 + 11 + 1); 200 /* An upper bound on the entry count: every entry occupies at least 201 * sizeof(struct odhcp6c_entry) bytes (auxlen rounds up to 4-byte 202 * stride, never below 0). */ 203 char *buf = malloc(buf_len + 2 + (len / sizeof(struct odhcp6c_entry)) * max_entry_len); 204 205 if (!buf) 206 return; 207 208 memcpy(buf, name, buf_len); 209 buf[buf_len++] = '='; 210 211 for (const struct odhcp6c_entry *e = (const struct odhcp6c_entry *)start; 212 (const uint8_t *)e < start + len && 213 (const uint8_t *)odhcp6c_next_entry(e) <= start + len; 214 e = odhcp6c_next_entry(e)) { 215 /* 216 * The only invalid entries allowed to be passed to the script are prefix and RA 217 * entries. This will allow immediate removal of the old ipv6-prefix-assignment 218 * that might otherwise be kept for up to 2 hours (see L-13 requirement of RFC 7084). 219 * Similarly, a RA with router lifetime set to 0 indicates that the advertising 220 * router "is not a default router and SHOULD NOT appear on the default router list" 221 * (see RFC 4861, section 4.2). 222 */ 223 if (!e->valid && type != ENTRY_PREFIX && type != ENTRY_ROUTE) 224 continue; 225 226 inet_ntop(AF_INET6, &e->target, &buf[buf_len], INET6_ADDRSTRLEN); 227 buf_len += strlen(&buf[buf_len]); 228 229 if (type != ENTRY_HOST) { 230 snprintf(&buf[buf_len], 6, "/%"PRIu16, e->length); 231 buf_len += strlen(&buf[buf_len]); 232 233 if (type == ENTRY_ROUTE) { 234 buf[buf_len++] = ','; 235 236 if (!IN6_IS_ADDR_UNSPECIFIED(&e->router)) { 237 inet_ntop(AF_INET6, &e->router, &buf[buf_len], INET6_ADDRSTRLEN); 238 buf_len += strlen(&buf[buf_len]); 239 } 240 241 snprintf(&buf[buf_len], 23, ",%u,%u", e->valid, e->priority); 242 buf_len += strlen(&buf[buf_len]); 243 } else { 244 snprintf(&buf[buf_len], 45, ",%u,%u,%u,%u", e->preferred, e->valid, e->t1, e->t2); 245 buf_len += strlen(&buf[buf_len]); 246 } 247 248 if (type == ENTRY_PREFIX && ntohl(e->iaid) != 1) { 249 snprintf(&buf[buf_len], 16, ",class=%08x", ntohl(e->iaid)); 250 buf_len += strlen(&buf[buf_len]); 251 } 252 253 if (type == ENTRY_PREFIX && e->exclusion_length) { 254 snprintf(&buf[buf_len], 11, ",excluded="); 255 buf_len += strlen(&buf[buf_len]); 256 // '.router' is dual-used: for prefixes it contains the prefix 257 inet_ntop(AF_INET6, &e->router, &buf[buf_len], INET6_ADDRSTRLEN); 258 buf_len += strlen(&buf[buf_len]); 259 snprintf(&buf[buf_len], 12, "/%u", e->exclusion_length); 260 buf_len += strlen(&buf[buf_len]); 261 } 262 } 263 264 buf[buf_len++] = ' '; 265 } 266 267 if (buf[buf_len - 1] == ' ') 268 buf_len--; 269 270 buf[buf_len] = '\0'; 271 putenv(buf); 272 } 273 274 static void search_to_env(const char *name, const uint8_t *start, size_t len) 275 { 276 size_t buf_len = strlen(name); 277 char *buf = malloc(buf_len + 2 + len); 278 char *c; 279 280 if (!buf) 281 return; 282 283 c = mempcpy(buf, name, buf_len); 284 *c++ = '='; 285 286 for (struct odhcp6c_entry *e = (struct odhcp6c_entry*)start; 287 (uint8_t*)e < &start[len] && 288 (uint8_t*)odhcp6c_next_entry(e) <= &start[len]; 289 e = odhcp6c_next_entry(e)) { 290 if (!e->valid) 291 continue; 292 c = mempcpy(c, e->auxtarget, e->auxlen); 293 *c++ = ' '; 294 } 295 296 if (c[-1] == ' ') 297 c--; 298 299 *c = '\0'; 300 putenv(buf); 301 } 302 303 static void int_to_env(const char *name, int value) 304 { 305 size_t len = 13 + strlen(name); 306 char *buf = malloc(len); 307 308 if (!buf) 309 return; 310 311 snprintf(buf, len, "%s=%d", name, value); 312 putenv(buf); 313 } 314 315 static void s46_to_env_portparams(const uint8_t *data, size_t len, FILE *fp) 316 { 317 uint8_t *odata; 318 uint16_t otype, olen; 319 320 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 321 if (otype == DHCPV6_OPT_S46_PORTPARAMS && 322 olen == sizeof(struct dhcpv6_s46_portparams)) { 323 struct dhcpv6_s46_portparams *params = (void*)odata; 324 fprintf(fp, "offset=%d,psidlen=%d,psid=%d,", 325 params->offset, params->psid_len, ntohs(params->psid)); 326 } 327 } 328 } 329 330 static void s46_to_env(enum odhcp6c_state state, const uint8_t *data, size_t len) 331 { 332 const char *name = (state == STATE_S46_MAPE) ? "MAPE" : 333 (state == STATE_S46_MAPT) ? "MAPT" : "LW4O6"; 334 335 if (len == 0) 336 return; 337 338 char *str; 339 size_t strsize; 340 341 FILE *fp = open_memstream(&str, &strsize); 342 fputs(name, fp); 343 fputc('=', fp); 344 345 const char *type = (state == STATE_S46_MAPE) ? "map-e" : 346 (state == STATE_S46_MAPT) ? "map-t" : "lw4o6"; 347 348 uint8_t *odata; 349 uint16_t otype, olen; 350 351 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 352 struct dhcpv6_s46_rule *rule = (struct dhcpv6_s46_rule*)odata; 353 struct dhcpv6_s46_v4v6bind *bind = (struct dhcpv6_s46_v4v6bind*)odata; 354 355 if (state != STATE_S46_LW && otype == DHCPV6_OPT_S46_RULE && 356 olen >= sizeof(struct dhcpv6_s46_rule)) { 357 char buf4[INET_ADDRSTRLEN]; 358 char buf6[INET6_ADDRSTRLEN]; 359 struct in6_addr in6 = IN6ADDR_ANY_INIT; 360 361 size_t prefix6len = rule->prefix6_len; 362 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 363 364 if (prefix6len > sizeof(in6) || 365 olen < sizeof(struct dhcpv6_s46_rule) + prefix6len) 366 continue; 367 368 memcpy(&in6, rule->ipv6_prefix, prefix6len); 369 370 inet_ntop(AF_INET, &rule->ipv4_prefix, buf4, sizeof(buf4)); 371 inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 372 373 if (rule->flags & 1) 374 fputs("fmr,", fp); 375 376 fprintf(fp, "type=%s,ealen=%d,prefix4len=%d,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,", 377 type, rule->ea_len, rule->prefix4_len, rule->prefix6_len, buf4, buf6); 378 379 s46_to_env_portparams(&rule->ipv6_prefix[prefix6len], 380 olen - sizeof(*rule) - prefix6len, fp); 381 382 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 383 if (state != STATE_S46_MAPT && otype == DHCPV6_OPT_S46_BR && 384 olen == sizeof(struct in6_addr)) { 385 inet_ntop(AF_INET6, odata, buf6, sizeof(buf6)); 386 fprintf(fp, "br=%s,", buf6); 387 } else if (state == STATE_S46_MAPT && otype == DHCPV6_OPT_S46_DMR && 388 olen >= sizeof(struct dhcpv6_s46_dmr)) { 389 struct dhcpv6_s46_dmr *dmr = (struct dhcpv6_s46_dmr*)odata; 390 memset(&in6, 0, sizeof(in6)); 391 size_t prefix6len = dmr->dmr_prefix6_len; 392 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 393 394 if (prefix6len > sizeof(in6) || 395 olen < sizeof(struct dhcpv6_s46_dmr) + prefix6len) 396 continue; 397 398 memcpy(&in6, dmr->dmr_ipv6_prefix, prefix6len); 399 inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 400 fprintf(fp, "dmr=%s/%d,", buf6, dmr->dmr_prefix6_len); 401 } 402 } 403 404 fputc(' ', fp); 405 } else if (state == STATE_S46_LW && otype == DHCPV6_OPT_S46_V4V6BIND && 406 olen >= sizeof(struct dhcpv6_s46_v4v6bind)) { 407 char buf4[INET_ADDRSTRLEN]; 408 char buf6[INET6_ADDRSTRLEN]; 409 struct in6_addr in6 = IN6ADDR_ANY_INIT; 410 411 size_t prefix6len = bind->bindprefix6_len; 412 prefix6len = (prefix6len % 8 == 0) ? prefix6len / 8 : prefix6len / 8 + 1; 413 414 if (prefix6len > sizeof(in6) || 415 olen < sizeof(struct dhcpv6_s46_v4v6bind) + prefix6len) 416 continue; 417 418 memcpy(&in6, bind->bind_ipv6_prefix, prefix6len); 419 420 inet_ntop(AF_INET, &bind->ipv4_address, buf4, sizeof(buf4)); 421 inet_ntop(AF_INET6, &in6, buf6, sizeof(buf6)); 422 423 fprintf(fp, "type=%s,prefix4len=32,prefix6len=%d,ipv4prefix=%s,ipv6prefix=%s,", 424 type, bind->bindprefix6_len, buf4, buf6); 425 426 s46_to_env_portparams(&bind->bind_ipv6_prefix[prefix6len], 427 olen - sizeof(*bind) - prefix6len, fp); 428 429 dhcpv6_for_each_option(data, &data[len], otype, olen, odata) { 430 if (otype == DHCPV6_OPT_S46_BR && olen == sizeof(struct in6_addr)) { 431 inet_ntop(AF_INET6, odata, buf6, sizeof(buf6)); 432 fprintf(fp, "br=%s,", buf6); 433 } 434 } 435 436 fputc(' ', fp); 437 } 438 } 439 440 fclose(fp); 441 putenv(str); 442 } 443 444 void script_call(const char *status, int delay, bool resume) 445 { 446 time_t now = odhcp6c_get_milli_time() / 1000; 447 bool running_script = false; 448 449 if (!argv[0]) 450 return; 451 452 pid_t prev = running; 453 if (prev > 0) { 454 time_t diff = now - started; 455 456 kill(prev, SIGTERM); 457 458 if (diff > delay) 459 delay -= diff; 460 else 461 delay = 0; 462 463 running_script = true; 464 } 465 466 if (resume || !running_script || !action[0]) 467 strncpy(action, status, sizeof(action) - 1); 468 469 pid_t pid = fork(); 470 471 if (pid < 0) { 472 error("Failed to fork script handler: %s", strerror(errno)); 473 running = 0; 474 return; 475 } 476 477 if (pid > 0) { 478 running = pid; 479 started = now; 480 481 if (!resume) 482 action[0] = 0; 483 484 } else if (pid == 0) { 485 size_t dns_len, search_len, custom_len, sntp_ip_len, ntp_ip_len, ntp_dns_len; 486 size_t sip_ip_len, sip_fqdn_len, aftr_name_len, addr_len; 487 size_t s46_mapt_len, s46_mape_len, s46_lw_len, passthru_len; 488 size_t capt_port_ra_len, capt_port_dhcpv6_len; 489 490 signal(SIGTERM, SIG_DFL); 491 if (delay > 0) { 492 sleep(delay); 493 odhcp6c_expire(false); 494 } 495 496 struct in6_addr *addr = odhcp6c_get_state(STATE_SERVER_ADDR, &addr_len); 497 struct in6_addr *dns = odhcp6c_get_state(STATE_DNS, &dns_len); 498 uint8_t *search = odhcp6c_get_state(STATE_SEARCH, &search_len); 499 uint8_t *custom = odhcp6c_get_state(STATE_CUSTOM_OPTS, &custom_len); 500 struct in6_addr *sntp = odhcp6c_get_state(STATE_SNTP_IP, &sntp_ip_len); 501 struct in6_addr *ntp = odhcp6c_get_state(STATE_NTP_IP, &ntp_ip_len); 502 uint8_t *ntp_dns = odhcp6c_get_state(STATE_NTP_FQDN, &ntp_dns_len); 503 struct in6_addr *sip = odhcp6c_get_state(STATE_SIP_IP, &sip_ip_len); 504 uint8_t *sip_fqdn = odhcp6c_get_state(STATE_SIP_FQDN, &sip_fqdn_len); 505 uint8_t *aftr_name = odhcp6c_get_state(STATE_AFTR_NAME, &aftr_name_len); 506 uint8_t *s46_mapt = odhcp6c_get_state(STATE_S46_MAPT, &s46_mapt_len); 507 uint8_t *s46_mape = odhcp6c_get_state(STATE_S46_MAPE, &s46_mape_len); 508 uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len); 509 uint8_t *capt_port_ra = odhcp6c_get_state(STATE_CAPT_PORT_RA, &capt_port_ra_len); 510 uint8_t *capt_port_dhcpv6 = odhcp6c_get_state(STATE_CAPT_PORT_DHCPV6, &capt_port_dhcpv6_len); 511 uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len); 512 513 size_t prefix_len, address_len, ra_pref_len, 514 ra_route_len, ra_dns_len, ra_search_len; 515 uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len); 516 uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len); 517 uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len); 518 uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len); 519 uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len); 520 uint8_t *ra_search = odhcp6c_get_state(STATE_RA_SEARCH, &ra_search_len); 521 522 /* RFC8910 ยง3 */ 523 if (capt_port_ra_len > 0 && capt_port_dhcpv6_len > 0) { 524 if (capt_port_ra_len != capt_port_dhcpv6_len || 525 memcmp(capt_port_dhcpv6, capt_port_ra, capt_port_dhcpv6_len)) 526 error( 527 "%s received via different vectors differ: preferring URI from DHCPv6", 528 CAPT_PORT_URI_STR); 529 } 530 531 ipv6_to_env("SERVER", addr, addr_len / sizeof(*addr)); 532 ipv6_to_env("RDNSS", dns, dns_len / sizeof(*dns)); 533 ipv6_to_env("SNTP_IP", sntp, sntp_ip_len / sizeof(*sntp)); 534 ipv6_to_env("NTP_IP", ntp, ntp_ip_len / sizeof(*ntp)); 535 fqdn_to_env("NTP_FQDN", ntp_dns, ntp_dns_len); 536 ipv6_to_env("SIP_IP", sip, sip_ip_len / sizeof(*sip)); 537 fqdn_to_env("DOMAINS", search, search_len); 538 fqdn_to_env("SIP_DOMAIN", sip_fqdn, sip_fqdn_len); 539 fqdn_to_env("AFTR", aftr_name, aftr_name_len); 540 s46_to_env(STATE_S46_MAPE, s46_mape, s46_mape_len); 541 s46_to_env(STATE_S46_MAPT, s46_mapt, s46_mapt_len); 542 s46_to_env(STATE_S46_LW, s46_lw, s46_lw_len); 543 if (capt_port_dhcpv6_len > 0) 544 string_to_env(CAPT_PORT_URI_STR, capt_port_dhcpv6, capt_port_dhcpv6_len); 545 else if (capt_port_ra_len > 0) 546 string_to_env(CAPT_PORT_URI_STR, capt_port_ra, capt_port_ra_len); 547 bin_to_env(custom, custom_len); 548 549 if (odhcp6c_is_bound()) { 550 entry_to_env("PREFIXES", prefix, prefix_len, ENTRY_PREFIX); 551 entry_to_env("ADDRESSES", address, address_len, ENTRY_ADDRESS); 552 } 553 554 entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS); 555 entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE); 556 entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST); 557 search_to_env("RA_DOMAINS", ra_search, ra_search_len); 558 559 int_to_env("RA_HOPLIMIT", ra_get_hoplimit()); 560 int_to_env("RA_MTU", ra_get_mtu()); 561 int_to_env("RA_REACHABLE", ra_get_reachable()); 562 int_to_env("RA_RETRANSMIT", ra_get_retransmit()); 563 564 char *buf = malloc(10 + passthru_len * 2); 565 if (buf) { 566 strncpy(buf, "PASSTHRU=", 10); 567 script_hexlify(&buf[9], passthru, passthru_len); 568 putenv(buf); 569 } 570 571 execv(argv[0], argv); 572 _exit(128); 573 } 574 } 575
This page was automatically generated by LXR 0.3.1. • OpenWrt