1 /* $OpenBSD: ldapclient.c,v 1.13 2009/01/27 23:29:42 pyr Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org> 5 * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/types.h> 21 #include <sys/param.h> 22 #include <sys/queue.h> 23 #include <sys/socket.h> 24 #include <sys/tree.h> 25 26 #include <netinet/in.h> 27 #include <arpa/inet.h> 28 29 #include <netdb.h> 30 #include <errno.h> 31 #include <err.h> 32 #include <event.h> 33 #include <fcntl.h> 34 #include <unistd.h> 35 #include <pwd.h> 36 #include <stdio.h> 37 #include <stdlib.h> 38 #include <string.h> 39 40 #include "aldap.h" 41 #include "ypldap.h" 42 43 void client_sig_handler(int, short, void *); 44 void client_dispatch_dns(int, short, void *); 45 void client_dispatch_parent(int, short, void *); 46 void client_shutdown(void); 47 void client_connect(int, short, void *); 48 void client_configure(struct env *); 49 void client_periodic_update(int, short, void *); 50 int client_try_idm(struct env *, struct idm *); 51 void client_try_idm_wrapper(int, short, void *); 52 void client_try_server_wrapper(int, short, void *); 53 int client_addr_init(struct idm *); 54 int client_addr_free(struct idm *); 55 56 struct aldap *client_aldap_open(struct ypldap_addr *); 57 58 /* 59 * dummy wrapper to provide aldap_init with its fd's. 60 */ 61 struct aldap * 62 client_aldap_open(struct ypldap_addr *addr) 63 { 64 int fd = -1; 65 struct ypldap_addr *p; 66 67 for (p = addr; p != NULL; p = p->next) { 68 char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; 69 struct sockaddr *sa = (struct sockaddr *)&p->ss; 70 71 if (getnameinfo(sa, SA_LEN(sa), hbuf, sizeof(hbuf), sbuf, 72 sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) 73 errx(1, "could not get numeric hostname"); 74 75 if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 76 return NULL; 77 78 if (connect(fd, sa, SA_LEN(sa)) == 0) 79 break; 80 81 warn("connect to %s port %s (%s) failed", hbuf, sbuf, "tcp"); 82 close(fd); 83 } 84 85 if (fd == -1) 86 return NULL; 87 88 return aldap_init(fd); 89 } 90 91 int 92 client_addr_init(struct idm *idm) 93 { 94 struct sockaddr_in *sa_in; 95 struct sockaddr_in6 *sa_in6; 96 struct ypldap_addr *h; 97 98 for (h = idm->idm_addr; h != NULL; h = h->next) { 99 switch (h->ss.ss_family) { 100 case AF_INET: 101 sa_in = (struct sockaddr_in *)&h->ss; 102 if (ntohs(sa_in->sin_port) == 0) 103 sa_in->sin_port = htons(389); 104 idm->idm_state = STATE_DNS_DONE; 105 break; 106 case AF_INET6: 107 sa_in6 = (struct sockaddr_in6 *)&h->ss; 108 if (ntohs(sa_in6->sin6_port) == 0) 109 sa_in6->sin6_port = htons(389); 110 idm->idm_state = STATE_DNS_DONE; 111 break; 112 default: 113 fatalx("king bula sez: wrong AF in client_addr_init"); 114 /* not reached */ 115 } 116 } 117 118 return (0); 119 } 120 121 int 122 client_addr_free(struct idm *idm) 123 { 124 struct ypldap_addr *h; 125 126 if (idm->idm_addr == NULL) 127 return (-1); 128 129 for (h = idm->idm_addr; h != NULL; h = h->next) 130 free(h); 131 132 idm->idm_addr = NULL; 133 134 return (0); 135 } 136 137 void 138 client_sig_handler(int sig, short event, void *p) 139 { 140 switch (sig) { 141 case SIGINT: 142 case SIGTERM: 143 client_shutdown(); 144 break; 145 default: 146 fatalx("unexpected signal"); 147 } 148 } 149 150 void 151 client_dispatch_dns(int fd, short event, void *p) 152 { 153 struct imsg imsg; 154 u_int16_t dlen; 155 u_char *data; 156 struct ypldap_addr *h; 157 int n, wait_cnt = 0; 158 struct idm *idm; 159 int shut = 0; 160 161 struct env *env = p; 162 struct imsgbuf *ibuf = env->sc_ibuf_dns; 163 164 switch (event) { 165 case EV_READ: 166 if ((n = imsg_read(ibuf)) == -1) 167 fatal("imsg_read error"); 168 if (n == 0) 169 shut = 1; 170 break; 171 case EV_WRITE: 172 if (msgbuf_write(&ibuf->w) == -1) 173 fatal("msgbuf_write"); 174 imsg_event_add(ibuf); 175 return; 176 default: 177 fatalx("unknown event"); 178 } 179 180 for (;;) { 181 if ((n = imsg_get(ibuf, &imsg)) == -1) 182 fatal("client_dispatch_parent: imsg_read_error"); 183 if (n == 0) 184 break; 185 186 switch (imsg.hdr.type) { 187 case IMSG_HOST_DNS: 188 TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) 189 if (idm->idm_id == imsg.hdr.peerid) 190 break; 191 if (idm == NULL) { 192 log_warnx("IMSG_HOST_DNS with invalid peerID"); 193 break; 194 } 195 if (idm->idm_addr != NULL) { 196 log_warnx("IMSG_HOST_DNS but addr != NULL!"); 197 break; 198 } 199 200 dlen = imsg.hdr.len - IMSG_HEADER_SIZE; 201 if (dlen == 0) { /* no data -> temp error */ 202 idm->idm_state = STATE_DNS_TEMPFAIL; 203 break; 204 } 205 206 data = (u_char *)imsg.data; 207 while (dlen >= sizeof(struct sockaddr_storage)) { 208 if ((h = calloc(1, sizeof(struct ypldap_addr))) == 209 NULL) 210 fatal(NULL); 211 memcpy(&h->ss, data, sizeof(h->ss)); 212 213 if (idm->idm_addr == NULL) 214 h->next = NULL; 215 else 216 h->next = idm->idm_addr; 217 218 idm->idm_addr = h; 219 220 data += sizeof(h->ss); 221 dlen -= sizeof(h->ss); 222 } 223 if (dlen != 0) 224 fatalx("IMSG_HOST_DNS: dlen != 0"); 225 226 client_addr_init(idm); 227 228 break; 229 default: 230 break; 231 } 232 imsg_free(&imsg); 233 } 234 235 TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { 236 if (client_try_idm(env, idm) == -1) 237 idm->idm_state = STATE_LDAP_FAIL; 238 239 if (idm->idm_state < STATE_LDAP_DONE) 240 wait_cnt++; 241 } 242 if (wait_cnt == 0) 243 imsg_compose(env->sc_ibuf, IMSG_END_UPDATE, 0, 0, NULL, 0); 244 245 if (!shut) 246 imsg_event_add(ibuf); 247 else { 248 /* this pipe is dead, so remove the event handler */ 249 event_del(&ibuf->ev); 250 event_loopexit(NULL); 251 } 252 } 253 254 void 255 client_dispatch_parent(int fd, short event, void *p) 256 { 257 int n; 258 int shut = 0; 259 struct imsg imsg; 260 struct env *env = p; 261 struct imsgbuf *ibuf = env->sc_ibuf; 262 263 264 switch (event) { 265 case EV_READ: 266 if ((n = imsg_read(ibuf)) == -1) 267 fatal("imsg_read error"); 268 if (n == 0) 269 shut = 1; 270 break; 271 case EV_WRITE: 272 if (msgbuf_write(&ibuf->w) == -1) 273 fatal("msgbuf_write"); 274 imsg_event_add(ibuf); 275 return; 276 default: 277 fatalx("unknown event"); 278 } 279 280 for (;;) { 281 if ((n = imsg_get(ibuf, &imsg)) == -1) 282 fatal("client_dispatch_parent: imsg_read_error"); 283 if (n == 0) 284 break; 285 286 switch (imsg.hdr.type) { 287 case IMSG_CONF_START: { 288 struct env params; 289 290 if (env->sc_flags & F_CONFIGURING) { 291 log_warnx("configuration already in progress"); 292 break; 293 } 294 memcpy(¶ms, imsg.data, sizeof(params)); 295 log_debug("configuration starting"); 296 env->sc_flags |= F_CONFIGURING; 297 purge_config(env); 298 memcpy(&env->sc_conf_tv, ¶ms.sc_conf_tv, 299 sizeof(env->sc_conf_tv)); 300 env->sc_flags |= params.sc_flags; 301 break; 302 } 303 case IMSG_CONF_IDM: { 304 struct idm *idm; 305 306 if (!(env->sc_flags & F_CONFIGURING)) 307 break; 308 if ((idm = calloc(1, sizeof(*idm))) == NULL) 309 fatal(NULL); 310 memcpy(idm, imsg.data, sizeof(*idm)); 311 idm->idm_env = env; 312 TAILQ_INSERT_TAIL(&env->sc_idms, idm, idm_entry); 313 break; 314 } 315 case IMSG_CONF_END: 316 env->sc_flags &= ~F_CONFIGURING; 317 log_debug("applying configuration"); 318 client_configure(env); 319 break; 320 default: 321 log_debug("client_dispatch_parent: unexpect imsg %d", 322 imsg.hdr.type); 323 324 break; 325 } 326 imsg_free(&imsg); 327 } 328 if (!shut) 329 imsg_event_add(ibuf); 330 else { 331 /* this pipe is dead, so remove the event handler */ 332 event_del(&ibuf->ev); 333 event_loopexit(NULL); 334 } 335 } 336 337 void 338 client_shutdown(void) 339 { 340 log_info("ldap client exiting"); 341 _exit(0); 342 } 343 344 pid_t 345 ldapclient(int pipe_main2client[2]) 346 { 347 pid_t pid, dns_pid; 348 int pipe_dns[2]; 349 struct passwd *pw; 350 struct event ev_sigint; 351 struct event ev_sigterm; 352 struct env env; 353 354 switch (pid = fork()) { 355 case -1: 356 fatal("cannot fork"); 357 break; 358 case 0: 359 break; 360 default: 361 return (pid); 362 } 363 364 bzero(&env, sizeof(env)); 365 TAILQ_INIT(&env.sc_idms); 366 367 if ((pw = getpwnam(YPLDAP_USER)) == NULL) 368 fatal("getpwnam"); 369 370 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_dns) == -1) 371 fatal("socketpair"); 372 dns_pid = ypldap_dns(pipe_dns, pw); 373 close(pipe_dns[1]); 374 375 #ifndef DEBUG 376 if (chroot(pw->pw_dir) == -1) 377 fatal("chroot"); 378 if (chdir("/") == -1) 379 fatal("chdir"); 380 #else 381 #warning disabling chrooting in DEBUG mode 382 #endif 383 setproctitle("ldap client"); 384 ypldap_process = PROC_CLIENT; 385 386 #ifndef DEBUG 387 if (setgroups(1, &pw->pw_gid) || 388 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 389 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 390 fatal("cannot drop privileges"); 391 #else 392 #warning disabling privilege revocation in DEBUG mode 393 #endif 394 395 event_init(); 396 signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL); 397 signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL); 398 signal_add(&ev_sigint, NULL); 399 signal_add(&ev_sigterm, NULL); 400 401 close(pipe_main2client[0]); 402 if ((env.sc_ibuf = calloc(1, sizeof(*env.sc_ibuf))) == NULL) 403 fatal(NULL); 404 if ((env.sc_ibuf_dns = calloc(1, sizeof(*env.sc_ibuf_dns))) == NULL) 405 fatal(NULL); 406 407 env.sc_ibuf->events = EV_READ; 408 env.sc_ibuf->data = &env; 409 imsg_init(env.sc_ibuf, pipe_main2client[1], client_dispatch_parent); 410 event_set(&env.sc_ibuf->ev, env.sc_ibuf->fd, env.sc_ibuf->events, 411 env.sc_ibuf->handler, &env); 412 event_add(&env.sc_ibuf->ev, NULL); 413 414 env.sc_ibuf_dns->events = EV_READ; 415 env.sc_ibuf_dns->data = &env; 416 imsg_init(env.sc_ibuf_dns, pipe_dns[0], client_dispatch_dns); 417 event_set(&env.sc_ibuf_dns->ev, env.sc_ibuf_dns->fd, env.sc_ibuf_dns->events, 418 env.sc_ibuf_dns->handler, &env); 419 event_add(&env.sc_ibuf_dns->ev, NULL); 420 421 event_dispatch(); 422 client_shutdown(); 423 424 return (0); 425 426 } 427 428 int 429 client_try_idm(struct env *env, struct idm *idm) 430 { 431 const char *where, *errstr; 432 char *attrs[ATTR_MAX+1]; 433 char **ldap_attrs; 434 int i, j, k; 435 struct idm_req ir; 436 struct aldap_message *m; 437 struct aldap *al; 438 439 where = "connect"; 440 if ((al = client_aldap_open(idm->idm_addr)) == NULL) 441 return (-1); 442 443 if (idm->idm_flags & F_NEEDAUTH) { 444 where = "binding"; 445 if (aldap_bind(al, idm->idm_binddn, idm->idm_bindcred) == -1) 446 goto bad; 447 448 where = "parsing"; 449 if ((m = aldap_parse(al)) == NULL) 450 goto bad; 451 where = "verifying msgid"; 452 if (al->msgid != m->msgid) { 453 aldap_freemsg(m); 454 goto bad; 455 } 456 aldap_freemsg(m); 457 } 458 459 bzero(attrs, sizeof(attrs)); 460 for (i = 0, j = 0; i < ATTR_MAX; i++) { 461 if (idm->idm_flags & F_FIXED_ATTR(i)) 462 continue; 463 attrs[j++] = idm->idm_attrs[i]; 464 } 465 attrs[j] = NULL; 466 467 where = "search"; 468 if (aldap_search(al, idm->idm_basedn, LDAP_SCOPE_SUBTREE, 469 idm->idm_filters[FILTER_USER], attrs, 0, 0, 0) == -1) { 470 aldap_get_errno(al, &errstr); 471 log_debug("%s\n", errstr); 472 goto bad; 473 } 474 475 /* 476 * build password line. 477 */ 478 while ((m = aldap_parse(al)) != NULL) { 479 where = "verifying msgid"; 480 if (al->msgid != m->msgid) { 481 aldap_freemsg(m); 482 goto bad; 483 } 484 /* end of the search result chain */ 485 if (m->message_type == LDAP_RES_SEARCH_RESULT) { 486 aldap_freemsg(m); 487 break; 488 } 489 /* search entry; the rest we won't handle */ 490 where = "verifying message_type"; 491 if (m->message_type != LDAP_RES_SEARCH_ENTRY) { 492 aldap_freemsg(m); 493 goto bad; 494 } 495 /* search entry */ 496 bzero(&ir, sizeof(ir)); 497 for (i = 0, j = 0; i < ATTR_MAX; i++) { 498 if (idm->idm_flags & F_FIXED_ATTR(i)) { 499 if (strlcat(ir.ir_line, idm->idm_attrs[i], 500 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 501 /* 502 * entry yields a line > 1024, trash it. 503 */ 504 goto next_pwdentry; 505 if (i == ATTR_UID) { 506 ir.ir_key.ik_uid = strtonum( 507 idm->idm_attrs[i], 0, 508 UID_MAX, NULL); 509 } 510 } else if (idm->idm_list & F_LIST(i)) { 511 if (aldap_match_entry(m, attrs[j++], &ldap_attrs) == -1) 512 goto next_pwdentry; 513 if (ldap_attrs[0] == NULL) 514 goto next_pwdentry; 515 for (k = 0; k >= 0 && ldap_attrs[k] != NULL; k++) { 516 if (strlcat(ir.ir_line, ldap_attrs[k], 517 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 518 continue; 519 if (ldap_attrs[k+1] != NULL) 520 if (strlcat(ir.ir_line, ",", 521 sizeof(ir.ir_line)) 522 >= sizeof(ir.ir_line)) { 523 aldap_free_entry(ldap_attrs); 524 goto next_pwdentry; 525 } 526 } 527 aldap_free_entry(ldap_attrs); 528 } else { 529 if (aldap_match_entry(m, attrs[j++], &ldap_attrs) == -1) 530 goto next_pwdentry; 531 if (ldap_attrs[0] == NULL) 532 goto next_pwdentry; 533 if (strlcat(ir.ir_line, ldap_attrs[0], 534 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) { 535 aldap_free_entry(ldap_attrs); 536 goto next_pwdentry; 537 } 538 if (i == ATTR_UID) { 539 ir.ir_key.ik_uid = strtonum( 540 ldap_attrs[0], 0, UID_MAX, NULL); 541 } 542 aldap_free_entry(ldap_attrs); 543 } 544 if (i != ATTR_SHELL) 545 if (strlcat(ir.ir_line, ":", 546 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 547 goto next_pwdentry; 548 } 549 imsg_compose(env->sc_ibuf, IMSG_PW_ENTRY, 0, 0, 550 &ir, sizeof(ir)); 551 next_pwdentry: 552 aldap_freemsg(m); 553 } 554 555 bzero(attrs, sizeof(attrs)); 556 for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) { 557 if (idm->idm_flags & F_FIXED_ATTR(i)) 558 continue; 559 attrs[j++] = idm->idm_attrs[i]; 560 } 561 attrs[j] = NULL; 562 563 where = "search"; 564 if (aldap_search(al, idm->idm_basedn, LDAP_SCOPE_SUBTREE, 565 idm->idm_filters[FILTER_GROUP], attrs, 0, 0, 0) == -1) { 566 aldap_get_errno(al, &errstr); 567 log_debug("%s\n", errstr); 568 569 goto bad; 570 } 571 572 /* 573 * build group line. 574 */ 575 while ((m = aldap_parse(al)) != NULL) { 576 where = "verifying msgid"; 577 if (al->msgid != m->msgid) { 578 aldap_freemsg(m); 579 goto bad; 580 } 581 /* end of the search result chain */ 582 if (m->message_type == LDAP_RES_SEARCH_RESULT) { 583 aldap_freemsg(m); 584 break; 585 } 586 /* search entry; the rest we won't handle */ 587 where = "verifying message_type"; 588 if (m->message_type != LDAP_RES_SEARCH_ENTRY) { 589 aldap_freemsg(m); 590 goto bad; 591 } 592 /* search entry */ 593 bzero(&ir, sizeof(ir)); 594 for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) { 595 if (idm->idm_flags & F_FIXED_ATTR(i)) { 596 if (strlcat(ir.ir_line, idm->idm_attrs[i], 597 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 598 /* 599 * entry yields a line > 1024, trash it. 600 */ 601 goto next_grpentry; 602 if (i == ATTR_GR_GID) { 603 ir.ir_key.ik_gid = strtonum( 604 idm->idm_attrs[i], 0, 605 GID_MAX, NULL); 606 } 607 } else if (idm->idm_list & F_LIST(i)) { 608 if (aldap_match_entry(m, attrs[j++], &ldap_attrs) == -1) 609 goto next_grpentry; 610 if (ldap_attrs[0] == NULL) 611 goto next_grpentry; 612 for (k = 0; k >= 0 && ldap_attrs[k] != NULL; k++) { 613 if (strlcat(ir.ir_line, ldap_attrs[k], 614 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 615 continue; 616 if (ldap_attrs[k+1] != NULL) 617 if (strlcat(ir.ir_line, ",", 618 sizeof(ir.ir_line)) 619 >= sizeof(ir.ir_line)) { 620 aldap_free_entry(ldap_attrs); 621 goto next_grpentry; 622 } 623 } 624 aldap_free_entry(ldap_attrs); 625 } else { 626 if (aldap_match_entry(m, attrs[j++], &ldap_attrs) == -1) 627 goto next_grpentry; 628 if (ldap_attrs[0] == NULL) 629 goto next_grpentry; 630 if (strlcat(ir.ir_line, ldap_attrs[0], 631 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) { 632 aldap_free_entry(ldap_attrs); 633 goto next_grpentry; 634 } 635 if (i == ATTR_GR_GID) { 636 ir.ir_key.ik_uid = strtonum( 637 ldap_attrs[0], 0, GID_MAX, NULL); 638 } 639 aldap_free_entry(ldap_attrs); 640 } 641 if (i != ATTR_GR_MEMBERS) 642 if (strlcat(ir.ir_line, ":", 643 sizeof(ir.ir_line)) >= sizeof(ir.ir_line)) 644 goto next_grpentry; 645 } 646 imsg_compose(env->sc_ibuf, IMSG_GRP_ENTRY, 0, 0, 647 &ir, sizeof(ir)); 648 next_grpentry: 649 aldap_freemsg(m); 650 } 651 652 aldap_close(al); 653 654 idm->idm_state = STATE_LDAP_DONE; 655 656 return (0); 657 bad: 658 log_debug("directory %s errored out in %s", idm->idm_name, where); 659 return (-1); 660 } 661 662 void 663 client_periodic_update(int fd, short event, void *p) 664 { 665 struct env *env = p; 666 667 struct idm *idm; 668 int fail_cnt = 0; 669 670 /* If LDAP isn't finished, notify the master process to trash the 671 * update. */ 672 TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { 673 if (idm->idm_state < STATE_LDAP_DONE) 674 fail_cnt++; 675 676 idm->idm_state = STATE_NONE; 677 678 client_addr_free(idm); 679 } 680 if (fail_cnt > 0) { 681 log_debug("trash the update"); 682 imsg_compose(env->sc_ibuf, IMSG_TRASH_UPDATE, 0, 0, NULL, 0); 683 } 684 685 client_configure(env); 686 } 687 688 void 689 client_configure(struct env *env) 690 { 691 struct timeval tv; 692 struct idm *idm; 693 u_int16_t dlen; 694 695 log_debug("connecting to directories"); 696 697 imsg_compose(env->sc_ibuf, IMSG_START_UPDATE, 0, 0, NULL, 0); 698 699 /* Start the DNS lookups */ 700 TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { 701 dlen = strlen(idm->idm_name) + 1; 702 imsg_compose(env->sc_ibuf_dns, IMSG_HOST_DNS, idm->idm_id, 0, 703 idm->idm_name, dlen); 704 } 705 706 tv.tv_sec = env->sc_conf_tv.tv_sec; 707 tv.tv_usec = env->sc_conf_tv.tv_usec; 708 evtimer_set(&env->sc_conf_ev, client_periodic_update, env); 709 evtimer_add(&env->sc_conf_ev, &tv); 710 } 711