serv.c (18736B)
1 /* 2 * src/serv.c from hirc 3 * 4 * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 */ 19 20 #include <stdlib.h> 21 #include <string.h> 22 #include <unistd.h> 23 #include <errno.h> 24 #include <netdb.h> 25 #include <sys/types.h> 26 #include <sys/socket.h> 27 #include <poll.h> 28 #ifdef TLS 29 #include <tls.h> 30 #endif /* TLS */ 31 #include "hirc.h" 32 33 /* 1024 is enough to fit two max-length messages in the buffer at once. 34 * To stress-test this, it is possible to set it to 2 as the lowest value. 35 * (The last byte is reserved for '\0', so a value of 1 will act weird). */ 36 #define INPUT_BUF_MIN 1024 37 38 void 39 serv_free(struct Server *server) { 40 struct Support *sp, *sprev; 41 struct Schedule *ep, *eprev; 42 43 if (!server) 44 return; 45 46 pfree(&server->name); 47 pfree(&server->username); 48 pfree(&server->realname); 49 pfree(&server->password); 50 pfree(&server->host); 51 pfree(&server->port); 52 pfree(&server->rpollfd); 53 nick_free(server->self); 54 hist_free_list(server->history); 55 chan_free_list(&server->channels); 56 chan_free_list(&server->queries); 57 sprev = server->supports; 58 if (sprev) 59 sp = sprev->next; 60 while (sprev) { 61 pfree(&sprev->key); 62 pfree(&sprev->value); 63 pfree(&sprev); 64 sprev = sp; 65 if (sp) 66 sp = sp->next; 67 } 68 eprev = server->schedule; 69 if (eprev) 70 ep = eprev->next; 71 while (eprev) { 72 pfree(&eprev->msg); 73 pfree(&eprev); 74 eprev = ep; 75 if (ep) 76 ep = ep->next; 77 } 78 #ifdef TLS 79 if (server->tls) 80 if (server->tls_ctx) 81 tls_free(server->tls_ctx); 82 #endif /* TLS */ 83 pfree(&server); 84 } 85 86 struct Server * 87 serv_create(char *name, char *host, char *port, char *nick, char *username, 88 char *realname, char *password, int tls, int tls_verify) { 89 struct Server *server; 90 int i; 91 92 assert_warn(name && host && port && nick, NULL); 93 94 server = emalloc(sizeof(struct Server)); 95 server->prev = server->next = NULL; 96 server->wfd = server->rfd = -1; 97 server->input.size = INPUT_BUF_MIN; 98 server->input.pos = 0; 99 server->input.buf = emalloc(server->input.size); 100 server->rpollfd = emalloc(sizeof(struct pollfd)); 101 server->rpollfd->fd = -1; 102 server->rpollfd->events = POLLIN; 103 server->rpollfd->revents = 0; 104 server->status = ConnStatus_notconnected; 105 server->name = estrdup(name); 106 server->username = username ? estrdup(username) : NULL; 107 server->realname = realname ? estrdup(realname) : NULL; 108 server->password = password ? estrdup(password) : NULL; 109 server->host = estrdup(host); 110 server->port = estrdup(port); 111 server->supports = NULL; 112 server->self = nick_create(nick, ' ', NULL); 113 server->self->self = 1; 114 server->history = emalloc(sizeof(struct HistInfo)); 115 server->history->activity = Activity_none; 116 server->history->unread = server->history->ignored = 0; 117 server->history->server = server; 118 server->history->channel = NULL; 119 server->history->history = NULL; 120 server->channels = NULL; 121 server->queries = NULL; 122 server->schedule = NULL; 123 server->reconnect = 0; 124 for (i=0; i < Expect_last; i++) 125 server->expect[i] = NULL; 126 server->autocmds = NULL; 127 server->connectfail = 0; 128 server->lastconnected = server->lastrecv = server->pingsent = 0; 129 130 #ifdef TLS 131 server->tls_verify = tls_verify; 132 server->tls = tls; 133 server->tls_ctx = NULL; 134 #else 135 if (tls) 136 hist_format(server->history, Activity_error, HIST_SHOW, 137 "SELF_TLSNOTCOMPILED %s", server->name); 138 #endif /* TLS */ 139 140 return server; 141 } 142 143 void 144 serv_update(struct Server *sp, char *nick, char *username, 145 char *realname, char *password, int tls, int tls_verify) { 146 assert_warn(sp,); 147 if (nick) { 148 pfree(&sp->self->nick); 149 sp->self->nick = estrdup(nick); 150 } 151 if (username) { 152 pfree(&sp->username); 153 sp->username = estrdup(nick); 154 } 155 if (realname) { 156 pfree(&sp->realname); 157 sp->username = estrdup(nick); 158 } 159 if (password) { 160 pfree(&sp->password); 161 sp->password = estrdup(password); 162 } 163 #ifdef TLS 164 if (tls >= 0 && !sp->tls) { 165 sp->tls = tls; 166 if (strcmp(sp->port, "6667") == 0) { 167 pfree(&sp->port); 168 sp->port = estrdup("6697"); 169 } 170 } 171 if (tls_verify >= 0) 172 sp->tls_verify = tls_verify; 173 #endif /* TLS */ 174 } 175 176 struct Server * 177 serv_add(struct Server **head, char *name, char *host, char *port, char *nick, 178 char *username, char *realname, char *password, int tls, int tls_verify) { 179 struct Server *new, *p; 180 181 new = serv_create(name, host, port, nick, username, realname, password, tls, tls_verify); 182 assert_warn(new, NULL); 183 184 if (!*head) { 185 *head = new; 186 return new; 187 } 188 189 p = *head; 190 for (; p && p->next; p = p->next); 191 p->next = new; 192 new->prev = p; 193 194 return new; 195 } 196 197 struct Server * 198 serv_get(struct Server **head, char *name) { 199 struct Server *p; 200 201 assert_warn(head && name, NULL); 202 if (!*head) 203 return NULL; 204 205 for (p = *head; p; p = p->next) { 206 if (strcmp(p->name, name) == 0) 207 return p; 208 } 209 210 return NULL; 211 } 212 213 int 214 serv_remove(struct Server **head, char *name) { 215 struct Server *p; 216 217 assert_warn(head && name, -1); 218 219 if ((p = serv_get(head, name)) == NULL) 220 return 0; 221 222 if (*head == p) 223 *head = p->next; 224 if (p->next) 225 p->next->prev = p->prev; 226 if (p->prev) 227 p->prev->next = p->next; 228 serv_free(p); 229 return 1; 230 } 231 232 void 233 serv_connect(struct Server *server) { 234 struct tls_config *tls_conf; 235 struct Support *s, *prev; 236 struct addrinfo hints; 237 struct addrinfo *ai = NULL; 238 int fd, ret; 239 240 assert_warn(server,); 241 242 if (server->status != ConnStatus_notconnected) { 243 ui_error("server '%s' is already connected", server->name); 244 return; 245 } 246 247 for (s = server->supports, prev = NULL; s; s = s->next) { 248 if (prev) { 249 pfree(&prev->key); 250 pfree(&prev->value); 251 pfree(&prev); 252 } 253 prev = s; 254 } 255 server->supports = NULL; 256 support_set(server, "CHANTYPES", config_gets("def.chantypes")); 257 support_set(server, "PREFIX", config_gets("def.prefixes")); 258 259 server->status = ConnStatus_connecting; 260 hist_format(server->history, Activity_status, HIST_SHOW|HIST_MAIN, 261 "SELF_CONNECTING %s %s", server->host, server->port); 262 263 memset(&hints, 0, sizeof(hints)); 264 hints.ai_family = AF_UNSPEC; 265 hints.ai_socktype = SOCK_STREAM; 266 267 if ((ret = getaddrinfo(server->host, server->port, &hints, &ai)) != 0 || ai == NULL) { 268 hist_format(server->history, Activity_error, HIST_SHOW, 269 "SELF_LOOKUPFAIL %s %s %s :%s", 270 server->name, server->host, server->port, gai_strerror(ret)); 271 goto fail; 272 } 273 if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1 || connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) { 274 hist_format(server->history, Activity_error, HIST_SHOW, 275 "SELF_CONNECTFAIL %s %s %s :%s", 276 server->name, server->host, server->port, strerror(errno)); 277 goto fail; 278 } 279 280 server->rfd = server->wfd = fd; 281 hist_format(server->history, Activity_status, HIST_SHOW|HIST_MAIN, 282 "SELF_CONNECTED %s %s %s", server->name, server->host, server->port); 283 284 #ifdef TLS 285 if (server->tls) { 286 if (server->tls_ctx) 287 tls_free(server->tls_ctx); 288 server->tls_ctx = NULL; 289 290 if ((tls_conf = tls_config_new()) == NULL) { 291 ui_tls_config_error(tls_conf, "tls_config_new()"); 292 goto fail; 293 } 294 295 if (!server->tls_verify) { 296 tls_config_insecure_noverifycert(tls_conf); 297 tls_config_insecure_noverifyname(tls_conf); 298 } 299 300 if ((server->tls_ctx = tls_client()) == NULL) { 301 ui_perror("tls_client()"); 302 goto fail; 303 } 304 305 if (tls_configure(server->tls_ctx, tls_conf) == -1) { 306 ui_tls_error(server->tls_ctx, "tls_configure()"); 307 goto fail; 308 } 309 310 if (tls_connect_socket(server->tls_ctx, fd, server->host) == -1) { 311 hist_format(server->history, Activity_error, HIST_SHOW, 312 "SELF_CONNECTLOST %s %s %s :%s", 313 server->name, server->host, server->port, tls_error(server->tls_ctx)); 314 goto fail; 315 } 316 317 if (tls_handshake(server->tls_ctx) == -1) { 318 hist_format(server->history, Activity_error, HIST_SHOW, 319 "SELF_CONNECTLOST %s %s %s :%s", 320 server->name, server->host, server->port, tls_error(server->tls_ctx)); 321 goto fail; 322 } 323 324 tls_config_free(tls_conf); 325 326 if (tls_peer_cert_provided(server->tls_ctx)) { 327 hist_format(server->history, Activity_status, HIST_SHOW, 328 "SELF_TLS_VERSION %s %s %d %s", 329 server->name, tls_conn_version(server->tls_ctx), 330 tls_conn_cipher_strength(server->tls_ctx), 331 tls_conn_cipher(server->tls_ctx)); 332 hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_SNI %s :%s", 333 server->name, tls_conn_servername(server->tls_ctx)); 334 hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_ISSUER %s :%s", 335 server->name, tls_peer_cert_issuer(server->tls_ctx)); 336 hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_SUBJECT %s :%s", 337 server->name, tls_peer_cert_subject(server->tls_ctx)); 338 } 339 } 340 #endif /* TLS */ 341 342 freeaddrinfo(ai); 343 server->connectfail = 0; 344 345 if (server->password) 346 serv_write(server, Sched_now, "PASS %s\r\n", server->password); 347 serv_write(server, Sched_now, "NICK %s\r\n", server->self->nick); 348 serv_write(server, Sched_now, "USER %s * * :%s\r\n", 349 server->username ? server->username : server->self->nick, 350 server->realname ? server->realname : server->self->nick); 351 352 return; 353 354 fail: 355 serv_disconnect(server, 1, NULL); 356 if (server->connectfail * config_getl("reconnect.interval") < config_getl("reconnect.maxinterval")) 357 server->connectfail += 1; 358 if (ai) 359 freeaddrinfo(ai); 360 } 361 362 void 363 serv_read(struct Server *sp) { 364 char *line, *end; 365 char *err; 366 char *reason = NULL; 367 size_t len; 368 int ret; 369 370 assert_warn(sp,); 371 372 #ifdef TLS 373 if (sp->tls) { 374 switch (ret = tls_read(sp->tls_ctx, &sp->input.buf[sp->input.pos], sp->input.size - sp->input.pos - 1)) { 375 case -1: 376 err = (char *)tls_error(sp->tls_ctx); 377 len = CONSTLEN("tls_read(): ") + strlen(err) + 1; 378 reason = smprintf(len, "tls_read(): %s", err); 379 /* fallthrough */ 380 case 0: 381 serv_disconnect(sp, 1, "EOF"); 382 hist_format(sp->history, Activity_error, HIST_SHOW, 383 "SELF_CONNECTLOST %s %s %s :%s", 384 sp->name, sp->host, sp->port, reason ? reason : "connection closed"); 385 pfree(&reason); 386 return; 387 case TLS_WANT_POLLIN: 388 case TLS_WANT_POLLOUT: 389 return; 390 default: 391 sp->input.pos += ret; 392 break; 393 } 394 } else { 395 #endif /* TLS */ 396 switch (ret = read(sp->rfd, &sp->input.buf[sp->input.pos], sp->input.size - sp->input.pos - 1)) { 397 case -1: 398 err = estrdup(strerror(errno)); 399 len = CONSTLEN("read(): ") + strlen(err) + 1; 400 reason = smprintf(len, "read(): %s", err); 401 pfree(&err); 402 /* fallthrough */ 403 case 0: 404 serv_disconnect(sp, 1, "EOF"); 405 hist_format(sp->history, Activity_error, HIST_SHOW, 406 "SELF_CONNECTLOST %s %s %s :%s", 407 sp->name, sp->host, sp->port, reason ? reason : "connection closed"); 408 pfree(&reason); 409 return; 410 default: 411 sp->input.pos += ret; 412 break; 413 } 414 #ifdef TLS 415 } 416 #endif /* TLS */ 417 418 sp->input.buf[sp->input.size - 1] = '\0'; 419 line = sp->input.buf; 420 while ((end = strstr(line, "\r\n"))) { 421 *end = '\0'; 422 handle(sp, line); 423 line = end + 2; 424 } 425 426 sp->input.pos -= line - sp->input.buf; 427 memmove(sp->input.buf, line, sp->input.pos); 428 429 /* Shrink and grow buffer as needed 430 * If we didn't read everything, serv_read will be called 431 * again in the main loop as poll gives another POLLIN. */ 432 if (sp->input.pos + ret > sp->input.size / 2) { 433 sp->input.size *= 2; 434 sp->input.buf = erealloc(sp->input.buf, sp->input.size); 435 } else if (sp->input.pos + ret < sp->input.size / 2 && sp->input.size != INPUT_BUF_MIN) { 436 sp->input.size /= 2; 437 sp->input.buf = erealloc(sp->input.buf, sp->input.size); 438 } 439 } 440 441 int 442 serv_write(struct Server *server, enum Sched when, char *format, ...) { 443 char msg[512]; 444 va_list ap; 445 int ret; 446 447 assert_warn(server && server->status != ConnStatus_notconnected, -1); 448 449 va_start(ap, format); 450 ret = vsnprintf(msg, sizeof(msg), format, ap); 451 va_end(ap); 452 453 assert_warn(ret >= 0, -1); 454 455 if (when != Sched_now) { 456 switch (when) { 457 case Sched_connected: 458 if (server->status == ConnStatus_connected) 459 goto write; 460 break; 461 } 462 schedule(server, when, msg); 463 return 0; 464 } 465 466 write: 467 468 #ifdef TLS 469 if (server->tls) 470 do { 471 ret = tls_write(server->tls_ctx, msg, strlen(msg)); 472 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 473 else 474 #endif /* TLS */ 475 ret = write(server->wfd, msg, strlen(msg)); 476 477 if (ret == -1 && server->status == ConnStatus_connected) { 478 serv_disconnect(server, 1, NULL); 479 hist_format(server->history, Activity_error, HIST_SHOW, 480 "SELF_CONNECTLOST %s %s %s :%s", 481 server->name, server->host, server->port, strerror(errno)); 482 } else if (ret == -1 && server->status != ConnStatus_connecting) { 483 ui_error("Not connected to server '%s'", server->name); 484 } 485 486 return ret; 487 } 488 489 490 int 491 serv_len(struct Server **head) { 492 struct Server *p; 493 int i; 494 495 for (p = *head, i = 0; p; p = p->next) 496 i++; 497 return i; 498 } 499 500 int 501 serv_poll(struct Server **head, int timeout) { 502 struct pollfd fds[64]; 503 struct Server *sp; 504 int i, ret; 505 506 for (i=0, sp = *head; sp; sp = sp->next, i++) { 507 sp->rpollfd->fd = sp->rfd; 508 fds[i].fd = sp->rpollfd->fd; 509 fds[i].events = POLLIN; 510 } 511 512 ret = poll(fds, serv_len(head), timeout); 513 if (errno == EINTR) /* ncurses issue */ 514 ret = 0; 515 516 for (i=0, sp = *head; sp; sp = sp->next, i++) 517 if (sp->status == ConnStatus_connecting 518 || sp->status == ConnStatus_connected) 519 sp->rpollfd->revents = fds[i].revents; 520 521 return ret; 522 } 523 524 void 525 serv_disconnect(struct Server *server, int reconnect, char *msg) { 526 struct Channel *chan; 527 int ret; 528 529 if (msg) 530 serv_write(server, Sched_now, "QUIT :%s\r\n", msg); 531 #ifdef TLS 532 if (server->tls) { 533 if (server->tls_ctx) { 534 do { 535 ret = tls_close(server->tls_ctx); 536 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 537 tls_free(server->tls_ctx); 538 } 539 server->tls_ctx = NULL; 540 } else { 541 #endif /* TLS */ 542 shutdown(server->rfd, SHUT_RDWR); 543 shutdown(server->wfd, SHUT_RDWR); 544 close(server->rfd); 545 close(server->wfd); 546 #ifdef TLS 547 } 548 #endif /* TLS */ 549 550 server->rfd = server->wfd = server->rpollfd->fd = -1; 551 server->status = ConnStatus_notconnected; 552 server->lastrecv = server->pingsent = 0; 553 server->lastconnected = time(NULL); 554 server->reconnect = reconnect; 555 556 /* Create a history item for disconnect: 557 * - shows up in the log 558 * - updates the file's mtime, so hist_laodlog knows when we disconnected */ 559 hist_format(server->history, Activity_none, HIST_LOG, "SELF_DISCONNECT"); 560 for (chan = server->channels; chan; chan = chan->next) { 561 chan_setold(chan, 1); 562 hist_format(chan->history, Activity_none, HIST_LOG, "SELF_DISCONNECT"); 563 } 564 565 windows[Win_buflist].refresh = 1; 566 } 567 568 int 569 serv_selected(struct Server *server) { 570 if (!selected.channel && selected.server == server) 571 return 1; 572 else 573 return 0; 574 } 575 576 char * 577 support_get(struct Server *server, char *key) { 578 struct Support *p; 579 for (p = server->supports; p; p = p->next) 580 if (strcmp(p->key, key) == 0) 581 return p->value; 582 583 return NULL; 584 } 585 586 void 587 support_set(struct Server *server, char *key, char *value) { 588 struct Support *p; 589 590 assert_warn(server,); 591 592 if (!server->supports) { 593 server->supports = emalloc(sizeof(struct Support)); 594 server->supports->prev = server->supports->next = NULL; 595 server->supports->key = key ? strdup(key) : NULL; 596 server->supports->value = value ? strdup(value) : NULL; 597 return; 598 } 599 600 for (p = server->supports; p && p->next; p = p->next) { 601 if (strcmp(p->key, key) == 0) { 602 pfree(&p->value); 603 p->value = strdup(value); 604 return; 605 } 606 } 607 608 p->next = emalloc(sizeof(struct Support)); 609 p->next->prev = p; 610 p->next->next = NULL; 611 p->next->key = key ? strdup(key) : NULL; 612 p->next->value = value ? strdup(value) : NULL; 613 } 614 615 int 616 serv_ischannel(struct Server *server, char *str) { 617 char *chantypes; 618 619 assert_warn(str && server,0); 620 621 chantypes = support_get(server, "CHANTYPES"); 622 if (!chantypes) 623 chantypes = config_gets("def.chantypes"); 624 assert(chantypes != NULL); 625 626 return strchr(chantypes, *str) != NULL; 627 } 628 629 void 630 serv_auto_add(struct Server *server, char *cmd) { 631 char **p; 632 size_t len; 633 634 assert_warn(server && cmd,); 635 636 if (!server->autocmds) { 637 len = 1; 638 server->autocmds = emalloc(sizeof(char *) * (len + 1)); 639 } else { 640 for (p = server->autocmds, len = 1; *p; p++) 641 len++; 642 server->autocmds = erealloc(server->autocmds, sizeof(char *) * (len + 1)); 643 } 644 645 *(server->autocmds + len - 1) = estrdup(cmd); 646 *(server->autocmds + len) = NULL; 647 } 648 649 void 650 serv_auto_free(struct Server *server) { 651 char **p; 652 653 if (!server || !server->autocmds) 654 return; 655 656 for (p = server->autocmds; *p; p++) 657 pfree(&*p); 658 pfree(&server->autocmds); 659 server->autocmds = NULL; 660 } 661 662 void 663 serv_auto_send(struct Server *server) { 664 char **p; 665 int save; 666 667 if (!server || !server->autocmds) 668 return; 669 670 save = nouich; 671 nouich = 1; 672 for (p = server->autocmds; *p; p++) 673 command_eval(server, *p); 674 nouich = save; 675 } 676 677 /* check if autocmds has '/join <chan>' */ 678 int 679 serv_auto_haschannel(struct Server *server, char *chan) { 680 char **p; 681 682 if (!server || !server->autocmds) 683 return 0; 684 685 for (p = server->autocmds; *p; p++) 686 if (strncmp(*p, "/join ", CONSTLEN("/join ")) == 0 && 687 strcmp((*p) + CONSTLEN("/join "), chan) == 0) 688 return 1; 689 return 0; 690 } 691 692 void 693 schedule(struct Server *server, enum Sched when, char *msg) { 694 struct Schedule *p; 695 696 assert_warn(server && msg,); 697 698 if (!server->schedule) { 699 server->schedule = emalloc(sizeof(struct Schedule)); 700 server->schedule->prev = server->schedule->next = NULL; 701 server->schedule->when = when; 702 server->schedule->msg = estrdup(msg); 703 return; 704 } 705 706 for (p = server->schedule; p && p->next; p = p->next); 707 708 p->next = emalloc(sizeof(struct Schedule)); 709 p->next->prev = p; 710 p->next->next = NULL; 711 p->next->when = when; 712 p->next->msg = estrdup(msg); 713 } 714 715 void 716 schedule_send(struct Server *server, enum Sched when) { 717 struct Schedule *p; 718 719 assert_warn(server,); 720 721 for (p = server->schedule; p; p = p->next) { 722 if (p->when == when) { 723 serv_write(server, Sched_now, "%s", p->msg); 724 725 if (p->prev) p->prev->next = p->next; 726 if (p->next) p->next->prev = p->prev; 727 728 if (!p->prev) 729 server->schedule = p->next; 730 731 pfree(&p->msg); 732 pfree(&p); 733 } 734 } 735 } 736 737 void 738 expect_set(struct Server *server, enum Expect cmd, char *about) { 739 if (cmd >= Expect_last || cmd < 0 || nouich) 740 return; 741 742 pfree(&server->expect[cmd]); 743 server->expect[cmd] = about ? estrdup(about) : NULL; 744 } 745 746 char * 747 expect_get(struct Server *server, enum Expect cmd) { 748 if (cmd >= Expect_last || cmd < 0) 749 return NULL; 750 else 751 return server->expect[cmd]; 752 }