commands.c (46049B)
1 /* 2 * src/commands.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 <ncurses.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <limits.h> 25 #include <ctype.h> 26 #include <regex.h> 27 #include <errno.h> 28 #include <pwd.h> 29 #include <sys/types.h> 30 #include "hirc.h" 31 32 #define command_toofew(cmd) ui_error("/%s: too few arguments", cmd) 33 #define command_toomany(cmd) ui_error("/%s: too many arguments", cmd) 34 #define command_needselected(cmd, type) ui_error("/%s: no %s selected", cmd, type) 35 36 /* 37 * There are some commands that may be useful that I haven't bothered to implement. 38 * 39 * /notify may be useful but would require storing data in the server, and the 40 * ability to perform actions at certain time intervals. 41 * 42 * I don't think I have ever used /knock 43 * 44 * A lot of commands related to server administration are nonstandard and/or 45 * unwieldy, and as such aren't implemented here. These can be used via 46 * aliases, eg: /alias /kline /quote kline. 47 * 48 */ 49 50 #include "data/commands.h" 51 52 static char *command_optarg; 53 enum { 54 opt_error = -2, 55 opt_done = -1, 56 CMD_NARG, 57 CMD_ARG, 58 }; 59 60 struct Alias *aliases = NULL; 61 62 COMMAND( 63 command_away) { 64 struct Server *sp; 65 char *format; 66 int all = 1, ret; 67 enum { opt_one }; 68 static struct CommandOpts opts[] = { 69 {"one", CMD_NARG, opt_one}, 70 {"NULL", 0, 0}, 71 }; 72 73 while ((ret = command_getopt(&str, opts)) != opt_done) { 74 switch (ret) { 75 case opt_error: 76 return; 77 case opt_one: 78 all = 0; 79 break; 80 } 81 } 82 83 if (str) 84 format = "AWAY :%s\r\n"; 85 else 86 format = "AWAY\r\n"; 87 88 if (all) { 89 for (sp = servers; sp; sp = sp->next) 90 serv_write(sp, Sched_connected, format, str); 91 } else if (server) { 92 serv_write(server, Sched_connected, format, str); 93 } else { 94 ui_error("-one specified, but no server selected", NULL); 95 } 96 } 97 98 COMMAND( 99 command_msg) { 100 struct Channel *chan = NULL; 101 char *target, *message; 102 103 if (!str) { 104 command_toofew("msg"); 105 return; 106 } 107 108 target = strtok_r(str, " ", &message); 109 110 if (serv_ischannel(server, target)) 111 chan = chan_get(&server->channels, target, -1); 112 else 113 chan = chan_get(&server->queries, target, -1); 114 115 serv_write(server, Sched_connected, "PRIVMSG %s :%s\r\n", target, message); 116 if (chan) { 117 hist_format(chan->history, Activity_self, 118 HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%s", target, message); 119 } 120 } 121 122 COMMAND( 123 command_notice) { 124 struct Channel *chan = NULL; 125 char *target, *message; 126 127 if (!str) { 128 command_toofew("notice"); 129 return; 130 } 131 132 target = strtok_r(str, " ", &message); 133 134 if (serv_ischannel(server, target)) 135 chan = chan_get(&server->channels, target, -1); 136 else 137 chan = chan_get(&server->queries, target, -1); 138 139 serv_write(server, Sched_connected, "NOTICE %s :%s\r\n", target, message); 140 if (chan) { 141 hist_format(chan->history, Activity_self, 142 HIST_SHOW|HIST_LOG|HIST_SELF, "NOTICE %s :%s", target, message); 143 } 144 } 145 146 COMMAND( 147 command_me) { 148 if (!str) 149 str = ""; 150 151 serv_write(server, Sched_connected, "PRIVMSG %s :%cACTION %s%c\r\n", channel->name, 1, str, 1); 152 hist_format(channel->history, Activity_self, 153 HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%cACTION %s%c", channel->name, 1, str, 1); 154 } 155 156 COMMAND( 157 command_ctcp) { 158 struct Channel *chan; 159 char *target, *ctcp; 160 161 if (!str) { 162 command_toofew("ctcp"); 163 return; 164 } 165 166 target = strtok_r(str, " ", &ctcp); 167 168 if (!ctcp) { 169 ctcp = target; 170 target = channel->name; 171 } 172 173 if ((chan = chan_get(&server->channels, target, -1)) == NULL) 174 chan = chan_get(&server->queries, target, -1); 175 176 /* XXX: if we CTCP a channel, responses should go to that channel. 177 * This requires more than just expect_set, so might never be 178 * implemented. */ 179 serv_write(server, Sched_connected, "PRIVMSG %s :%c%s%c\r\n", target, 1, ctcp, 1); 180 if (chan) { 181 hist_format(channel->history, Activity_self, 182 HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%c%s%c", 183 target, 1, ctcp, 1); 184 } 185 } 186 187 COMMAND( 188 command_query) { 189 struct Channel *query; 190 191 if (!str) { 192 command_toofew("query"); 193 return; 194 } 195 196 if (strchr(str, ' ')) { 197 command_toomany("query"); 198 return; 199 } 200 201 if (serv_ischannel(server, str)) { 202 ui_error("can't query a channel", NULL); 203 return; 204 } 205 206 if ((query = chan_get(&server->queries, str, -1)) == NULL) 207 query = chan_add(server, &server->queries, str, 1); 208 209 if (!nouich) 210 ui_select(server, query); 211 } 212 213 COMMAND( 214 command_quit) { 215 cleanup(str ? str : config_gets("def.quitmessage")); 216 exit(EXIT_SUCCESS); 217 } 218 219 COMMAND( 220 command_join) { 221 char msg[512]; 222 223 if (!str) { 224 command_toofew("join"); 225 return; 226 } 227 228 if (serv_ischannel(server, str)) 229 snprintf(msg, sizeof(msg), "JOIN %s\r\n", str); 230 else 231 snprintf(msg, sizeof(msg), "JOIN %c%s\r\n", '#', str); 232 233 serv_write(server, Sched_connected, "%s", msg); 234 expect_set(server, Expect_join, str); 235 } 236 237 COMMAND( 238 command_part) { 239 char *chan = NULL, *reason = NULL; 240 char msg[512]; 241 242 if (str) { 243 if (serv_ischannel(server, str)) 244 chan = strtok_r(str, " ", &reason); 245 else 246 reason = str; 247 } 248 249 if (!chan) { 250 if (channel) { 251 chan = channel->name; 252 } else { 253 command_toofew("part"); 254 return; 255 } 256 } 257 258 snprintf(msg, sizeof(msg), "PART %s :%s\r\n", chan, reason ? reason : config_gets("def.partmessage")); 259 260 serv_write(server, Sched_connected, "%s", msg); 261 expect_set(server, Expect_part, chan); 262 } 263 264 COMMAND( 265 command_cycle) { 266 char *chan = NULL; 267 268 if (str && serv_ischannel(server, str)) 269 chan = strtok(str, " "); 270 if (!chan && channel) { 271 chan = channel->name; 272 } else if (!chan) { 273 command_toofew("cycle"); 274 return; 275 } 276 277 command_part(server, channel, str); 278 command_join(server, channel, chan); 279 } 280 281 COMMAND( 282 command_kick) { 283 char *chan, *nick, *reason; 284 char *s; 285 286 if (!str) { 287 command_toofew("kick"); 288 return; 289 } 290 291 s = strtok_r(str, " ", &reason); 292 293 if (serv_ischannel(server, s)) { 294 chan = s; 295 nick = strtok_r(NULL, " ", &reason); 296 } else { 297 if (channel == NULL) { 298 command_needselected("kick", "channel"); 299 return; 300 } 301 302 chan = channel->name; 303 nick = s; 304 } 305 306 if (reason) 307 serv_write(server, Sched_connected, "KICK %s %s :%s\r\n", chan, nick, reason); 308 else 309 serv_write(server, Sched_connected, "KICK %s %s\r\n", chan, nick); 310 } 311 312 COMMAND( 313 command_mode) { 314 char *chan, *modes; 315 char *s = NULL; 316 317 if (str) 318 s = strtok_r(str, " ", &modes); 319 320 if (serv_ischannel(server, s)) { 321 chan = s; 322 } else { 323 if (channel == NULL) { 324 command_needselected("mode", "channel"); 325 return; 326 } 327 328 chan = channel->name; 329 if (modes) { 330 *(modes - 1) = ' '; 331 modes = s; 332 } 333 } 334 335 if (modes) { 336 if (channel && chan == channel->name) 337 expect_set(server, Expect_nosuchnick, chan); 338 serv_write(server, Sched_connected, "MODE %s %s\r\n", chan, modes); 339 } else { 340 expect_set(server, Expect_channelmodeis, chan); 341 serv_write(server, Sched_connected, "MODE %s\r\n", chan); 342 } 343 } 344 345 COMMAND( 346 command_nick) { 347 if (!str) { 348 command_toofew("nick"); 349 return; 350 } 351 352 if (strchr(str, ' ')) { 353 command_toomany("nick"); 354 return; 355 } 356 357 serv_write(server, Sched_now, "NICK %s\r\n", str); 358 expect_set(server, Expect_nicknameinuse, str); 359 } 360 361 COMMAND( 362 command_list) { 363 if (str) { 364 command_toomany("list"); 365 return; 366 } 367 368 serv_write(server, Sched_connected, "LIST\r\n", str); 369 } 370 371 COMMAND( 372 command_whois) { 373 char *tserver, *nick; 374 375 if (!str) { 376 nick = server->self->nick; 377 tserver = NULL; 378 } else { 379 tserver = strtok_r(str, " ", &nick); 380 if (!nick || !*nick) { 381 nick = tserver; 382 tserver = NULL; 383 } 384 } 385 386 if (tserver) 387 serv_write(server, Sched_connected, "WHOIS %s :%s\r\n", tserver, nick); 388 else 389 serv_write(server, Sched_connected, "WHOIS %s\r\n", nick); 390 } 391 392 COMMAND( 393 command_who) { 394 if (!str) 395 str = "*"; /* wildcard */ 396 397 serv_write(server, Sched_connected, "WHO %s\r\n", str); 398 } 399 400 COMMAND( 401 command_whowas) { 402 char *nick, *count, *tserver; 403 404 if (!str) { 405 nick = server->self->nick; 406 count = tserver = NULL; 407 } else { 408 nick = strtok_r(str, " ", &tserver); 409 count = strtok_r(NULL, " ", &tserver); 410 } 411 412 if (tserver) 413 serv_write(server, Sched_connected, "WHOWAS %s %s :%s\r\n", nick, count, tserver); 414 else if (count) 415 serv_write(server, Sched_connected, "WHOWAS %s %s\r\n", nick, count); 416 else 417 serv_write(server, Sched_connected, "WHOWAS %s 5\r\n", nick); 418 } 419 420 COMMAND( 421 command_ping) { 422 if (!str) { 423 command_toofew("ping"); 424 return; 425 } 426 427 serv_write(server, Sched_now, "PING :%s\r\n", str); 428 expect_set(server, Expect_pong, str); 429 } 430 431 COMMAND( 432 command_quote) { 433 if (!str) { 434 command_toofew("quote"); 435 return; 436 } 437 438 serv_write(server, Sched_connected, "%s\r\n", str); 439 } 440 441 COMMAND( 442 command_connect) { 443 struct Server *tserver; 444 char *network = NULL; 445 char *host = NULL; 446 char *port = NULL; 447 char *nick = NULL; 448 char *username = NULL; 449 char *realname = NULL; 450 char *password = NULL; 451 int tls = -1, tls_verify = -1; /* tell serv_update not to change */ 452 int ret; 453 struct passwd *user; 454 enum { 455 opt_network, 456 opt_nick, 457 opt_username, 458 opt_realname, 459 opt_password, 460 #ifdef TLS 461 opt_tls, 462 opt_tls_verify, 463 #endif /* TLS */ 464 }; 465 static struct CommandOpts opts[] = { 466 {"network", CMD_ARG, opt_network}, 467 {"nick", CMD_ARG, opt_nick}, 468 469 {"username", CMD_ARG, opt_username}, 470 {"user", CMD_ARG, opt_username}, 471 472 {"realname", CMD_ARG, opt_realname}, 473 {"real", CMD_ARG, opt_realname}, 474 {"comment", CMD_ARG, opt_realname}, 475 476 {"pass", CMD_ARG, opt_password}, 477 {"password", CMD_ARG, opt_password}, 478 {"auth", CMD_ARG, opt_password}, 479 #ifdef TLS 480 {"tls", CMD_NARG, opt_tls}, 481 {"ssl", CMD_NARG, opt_tls}, 482 {"verify", CMD_NARG, opt_tls_verify}, 483 #endif /* TLS */ 484 {NULL, 0, 0}, 485 }; 486 487 while ((ret = command_getopt(&str, opts)) != opt_done) { 488 switch (ret) { 489 case opt_error: 490 return; 491 case opt_network: 492 network = command_optarg; 493 break; 494 case opt_nick: 495 nick = command_optarg; 496 break; 497 case opt_username: 498 username = command_optarg; 499 break; 500 case opt_realname: 501 realname = command_optarg; 502 break; 503 case opt_password: 504 password = command_optarg; 505 break; 506 #ifdef TLS 507 case opt_tls: 508 tls = 1; 509 break; 510 case opt_tls_verify: 511 tls_verify = 1; 512 break; 513 #endif /* TLS */ 514 } 515 } 516 517 host = strtok(str, " "); 518 port = strtok(NULL, " "); 519 520 if (!host) { 521 if (network) { 522 if (!(tserver = serv_get(&servers, network))) 523 ui_error("no such network", NULL); 524 } else { 525 if (!server) 526 ui_error("must specify host", NULL); 527 else 528 tserver = server; 529 } 530 if (server) { 531 serv_update(tserver, nick, username, realname, password, tls, tls_verify); 532 serv_connect(tserver); 533 } 534 return; 535 } 536 537 if (tls <= 0) 538 tls = 0; 539 if (tls_verify <= 0) 540 tls_verify = 0; 541 542 if (!nick && !(nick = config_gets("def.nick"))) { 543 user = getpwuid(geteuid()); 544 nick = user ? user->pw_name : "null"; 545 } 546 if (!username && !(username = config_gets("def.user"))) 547 username = nick; 548 if (!realname && !(realname = config_gets("def.real"))) 549 realname = nick; 550 if (!network) 551 network = host; 552 if (!port) { 553 /* no ifdef required, as -tls only works with -DTLS */ 554 if (tls) 555 port = "6697"; 556 else 557 port = "6667"; 558 } 559 560 tserver = serv_add(&servers, network, host, port, nick, 561 username, realname, password, tls, tls_verify); 562 serv_connect(tserver); 563 if (!nouich) 564 ui_select(tserver, NULL); 565 return; 566 } 567 568 COMMAND( 569 command_disconnect) { 570 struct Server *sp; 571 struct Channel *chp; 572 int len; 573 char *msg = NULL; 574 575 if (str) { 576 len = strcspn(str, " "); 577 for (sp = servers; sp; sp = sp->next) { 578 if (strlen(sp->name) == len && strncmp(sp->name, str, len) == 0) { 579 msg = strchr(str, ' '); 580 if (msg && *msg) 581 msg++; 582 break; 583 } 584 } 585 586 if (sp == NULL) { 587 sp = server; 588 msg = str; 589 } 590 } else sp = server; 591 592 if (!msg || !*msg) 593 msg = config_gets("def.quitmessage"); 594 595 /* Add fake quit messages to history. 596 * Theoretically, we could send QUIT and then wait for a 597 * reply, but I don't see the advantage. (Unless the 598 * server fucks with our quit messages somehow). 599 * 600 * Since HIST_SELF is used, there is no need to fake a prefix. */ 601 hist_format(sp->history, Activity_self, HIST_DFL|HIST_SELF, "QUIT :%s", msg); 602 for (chp = sp->channels; chp; chp = chp->next) 603 hist_format(chp->history, Activity_self, HIST_DFL|HIST_SELF, "QUIT :%s", msg); 604 serv_disconnect(sp, 0, msg); 605 } 606 607 COMMAND( 608 command_select) { 609 struct Server *sp; 610 struct Channel *chp; 611 char *tserver = NULL; 612 char *tchannel = NULL; 613 int ret, buf = 0; 614 enum { 615 opt_server, 616 opt_channel, 617 opt_test, 618 }; 619 static struct CommandOpts opts[] = { 620 {"server", CMD_ARG, opt_server}, 621 {"network", CMD_ARG, opt_server}, 622 {"channel", CMD_ARG, opt_channel}, 623 {NULL, 0, 0}, 624 }; 625 626 while ((ret = command_getopt(&str, opts)) != opt_done) { 627 switch (ret) { 628 case opt_error: 629 return; 630 case opt_server: 631 tserver = command_optarg; 632 break; 633 case opt_channel: 634 tchannel = command_optarg; 635 break; 636 } 637 } 638 639 if (tserver || tchannel) { 640 /* TODO: find closest match instead of perfect matches */ 641 if (!tserver) { 642 ui_error("must specify server and channel, or just server", NULL); 643 return; 644 } 645 646 if (!(sp = serv_get(&servers, tserver))) { 647 ui_error("could not find server '%s'", tserver); 648 return; 649 } 650 651 if (tchannel) { 652 for (chp = sp->channels; chp; chp = chp->next) 653 if (strcmp(chp->name, tchannel) == 0) 654 break; 655 656 if (!chp) { 657 ui_error("could not find channel '%s'", tchannel); 658 return; 659 } 660 } else chp = NULL; 661 662 ui_select(sp, chp); 663 664 if (str) 665 ui_error("ignoring trailing arguments: '%s'", str); 666 } else if (str) { 667 buf = atoi(str); 668 if (!buf) 669 ui_error("invalid buffer index: '%s'", str); 670 if (ui_buflist_get(buf, &sp, &chp) != -1) 671 ui_select(sp, chp); 672 } else { 673 command_toofew("select"); 674 } 675 } 676 677 COMMAND( 678 command_set) { 679 char *name, *val; 680 681 if (!str) { 682 command_toofew("set"); 683 return; 684 } 685 name = strtok_r(str, " ", &val); 686 config_set(name, val); 687 } 688 689 COMMAND( 690 command_toggle) { 691 struct Config *conf; 692 693 if (!str) { 694 command_toofew("toggle"); 695 return; 696 } 697 if (strchr(str, ' ')) { 698 command_toomany("toggle"); 699 return; 700 } 701 if (!(conf = config_getp(str))) { 702 ui_error("no such configuration variable", NULL); 703 return; 704 } 705 if (conf->valtype != Val_bool) { 706 ui_error("%s is not a boolean variable", str); 707 return; 708 } 709 710 config_setl(conf, !conf->num); 711 } 712 713 COMMAND( 714 command_format) { 715 char *newstr; 716 int len; 717 718 if (!str) { 719 command_toofew("format"); 720 return; 721 } 722 723 len = strlen(str) + CONSTLEN("format.") + 1; 724 newstr = smprintf(len, "format.%s", str); 725 command_set(server, channel, newstr); 726 pfree(&newstr); 727 } 728 729 COMMAND( 730 command_server) { 731 struct Server *nserver; 732 char *tserver, *cmd, *arg; 733 char **acmds; 734 int i, ret, mode; 735 enum { opt_norm, opt_auto, opt_clear }; 736 struct CommandOpts opts[] = { 737 {"auto", CMD_NARG, opt_auto}, 738 {"clear", CMD_NARG, opt_clear}, 739 {NULL, 0, 0}, 740 }; 741 742 mode = opt_norm; 743 while ((ret = command_getopt(&str, opts)) != opt_done) { 744 switch (ret) { 745 case opt_error: 746 return; 747 case opt_auto: 748 case opt_clear: 749 if (mode != opt_norm) { 750 ui_error("conflicting flags", NULL); 751 return; 752 } 753 mode = ret; 754 } 755 } 756 757 tserver = strtok_r(str, " ", &arg); 758 if (mode == opt_norm) 759 cmd = strtok_r(NULL, " ", &arg); 760 761 if (!tserver) { 762 command_toofew("server"); 763 return; 764 } 765 766 if ((nserver = serv_get(&servers, tserver)) == NULL) { 767 ui_error("no such server: '%s'", tserver); 768 return; 769 } 770 771 switch (mode) { 772 case opt_norm: 773 if (!cmd || !*cmd) { 774 command_toofew("server"); 775 return; 776 } 777 778 if (*cmd == '/') 779 cmd++; 780 781 for (i=0; commands[i].name && commands[i].func; i++) { 782 if (strcmp(commands[i].name, cmd) == 0) { 783 commands[i].func(nserver, channel, arg); 784 return; 785 } 786 } 787 ui_error("no such commands: '%s'", cmd); 788 break; 789 case opt_auto: 790 if (!arg || !*arg) { 791 hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_START %s :Autocmds for %s:", 792 nserver->name, nserver->name); 793 for (acmds = nserver->autocmds; acmds && *acmds; acmds++) 794 hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_LIST %s :%s", 795 nserver->name, *acmds); 796 hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_END %s :End of autocmds for %s", 797 nserver->name, nserver->name); 798 } else { 799 if (*arg == '/') 800 cmd = arg; 801 else 802 cmd = smprintf(strlen(arg) + 2, "/%s", arg); 803 804 serv_auto_add(nserver, cmd); 805 } 806 break; 807 case opt_clear: 808 if (*arg) { 809 command_toomany("server"); 810 break; 811 } 812 813 serv_auto_free(nserver); 814 break; 815 } 816 } 817 818 COMMAND( 819 command_names) { 820 char *chan, *save = NULL; 821 822 chan = strtok_r(str, " ", &save); 823 if (!chan) 824 chan = channel ? channel->name : NULL; 825 826 if (!chan) { 827 command_needselected("names", "channel"); 828 return; 829 } 830 831 if (save && *save) { 832 command_toomany("names"); 833 return; 834 } 835 836 serv_write(server, Sched_connected, "NAMES %s\r\n", chan); 837 expect_set(server, Expect_names, chan); 838 } 839 840 COMMAND( 841 command_topic) { 842 char *chan, *topic = NULL; 843 int clear = 0, ret; 844 enum { opt_clear, }; 845 struct CommandOpts opts[] = { 846 {"clear", CMD_NARG, opt_clear}, 847 {NULL, 0, 0}, 848 }; 849 850 while ((ret = command_getopt(&str, opts)) != opt_done) { 851 switch (ret) { 852 case opt_error: 853 return; 854 case opt_clear: 855 clear = 1; 856 break; 857 } 858 } 859 860 if (str) 861 chan = strtok_r(str, " ", &topic); 862 else 863 chan = topic = NULL; 864 865 if (chan && !serv_ischannel(server, chan)) { 866 topic = chan; 867 chan = NULL; 868 } 869 870 if (!chan && channel) { 871 chan = channel->name; 872 } else if (!chan) { 873 command_needselected("topic", "channel"); 874 return; 875 } 876 877 if (clear) { 878 if (topic) { 879 command_toomany("topic"); 880 return; 881 } 882 serv_write(server, Sched_connected, "TOPIC %s :\r\n", chan); 883 return; 884 } 885 886 if (!topic) { 887 serv_write(server, Sched_connected, "TOPIC %s\r\n", chan); 888 expect_set(server, Expect_topic, chan); 889 } else serv_write(server, Sched_connected, "TOPIC %s :%s\r\n", chan, topic); 890 } 891 892 COMMAND( 893 command_oper) { 894 char *user, *pass; 895 896 if (!str) { 897 command_toofew("oper"); 898 return; 899 } 900 901 user = strtok_r(str, " ", &pass); 902 if (pass && strchr(pass, ' ')) { 903 command_toomany("oper"); 904 return; 905 } 906 if (!pass) { 907 pass = user; 908 user = server->self->nick; 909 } 910 911 serv_write(server, Sched_connected, "OPER %s %s\r\n", user, pass); 912 } 913 914 static void 915 command_send0(struct Server *server, char *cmd, char *cmdname, char *str) { 916 if (str) 917 command_toomany(cmdname); 918 else 919 serv_write(server, Sched_connected, "%s\r\n", cmd); 920 } 921 922 COMMAND( 923 command_lusers) { 924 command_send0(server, "LUSERS", "lusers", str); 925 } 926 927 COMMAND( 928 command_map) { 929 command_send0(server, "MAP", "map", str); 930 } 931 932 static void 933 command_send1(struct Server *server, char *cmd, char *cmdname, char *str) { 934 if (str && strchr(str, ' ')) 935 command_toomany(cmdname); 936 else if (str) 937 serv_write(server, Sched_connected, "%s %s\r\n", cmd, str); 938 else 939 serv_write(server, Sched_connected, "%s\r\n", cmd); 940 } 941 942 COMMAND( 943 command_motd) { 944 command_send1(server, "MOTD", "motd", str); 945 } 946 947 COMMAND( 948 command_time) { 949 command_send1(server, "TIME", "time", str); 950 } 951 952 static void 953 command_send2(struct Server *server, char *cmd, char *cmdname, char *str) { 954 if (str && strchr(str, ' ') != strrchr(str, ' ')) 955 command_toomany(cmdname); 956 else if (str) 957 serv_write(server, Sched_connected, "%s %s\r\n", cmd, str); 958 else 959 serv_write(server, Sched_connected, "%s\r\n", cmd); 960 } 961 962 COMMAND( 963 command_links) { 964 command_send2(server, "LINKS", "links", str); 965 } 966 967 COMMAND( 968 command_stats) { 969 command_send2(server, "STATS", "stats", str); 970 } 971 972 COMMAND( 973 command_kill) { 974 char *nick, *reason; 975 976 if (!str) { 977 command_toofew("kill"); 978 return; 979 } 980 981 nick = strtok_r(str, " ", &reason); 982 if (!reason) 983 reason = config_gets("def.killmessage"); 984 serv_write(server, Sched_connected, "KILL %s :%s\r\n", nick, reason); 985 } 986 987 COMMAND( 988 command_bind) { 989 struct Keybind *p; 990 char *binding = NULL, *cmd = NULL; 991 int delete = 0, ret; 992 enum { opt_delete, }; 993 struct CommandOpts opts[] = { 994 {"delete", CMD_NARG, opt_delete}, 995 {NULL, 0, 0}, 996 }; 997 998 while ((ret = command_getopt(&str, opts)) != opt_done) { 999 switch (ret) { 1000 case opt_error: 1001 return; 1002 case opt_delete: 1003 delete = 1; 1004 break; 1005 } 1006 } 1007 1008 if (str) 1009 binding = strtok_r(str, " ", &cmd); 1010 1011 if (delete) { 1012 if (ui_unbind(binding) == -1) 1013 ui_error("no such keybind: '%s'", binding); 1014 return; 1015 } 1016 1017 if (!binding) { 1018 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_START :Keybindings:"); 1019 for (p = keybinds; p; p = p->next) 1020 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd); 1021 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_END :End of keybindings"); 1022 } else if (!cmd) { 1023 for (p = keybinds; p; p = p->next) { 1024 if (strcmp(p->binding, binding) == 0) { 1025 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_START :Keybindings:"); 1026 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd); 1027 hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_END :End of keybindings"); 1028 return; 1029 } 1030 } 1031 1032 ui_error("no such keybind: '%s'", binding); 1033 } else { 1034 if (ui_bind(binding, cmd) == -1 && !nouich) 1035 ui_error("keybind already exists: '%s'", binding); 1036 } 1037 } 1038 1039 COMMAND( 1040 command_alias) { 1041 struct Alias *p; 1042 char *alias = NULL, *cmd = NULL; 1043 int delete = 0, ret; 1044 enum { opt_delete, }; 1045 struct CommandOpts opts[] = { 1046 {"delete", CMD_NARG, opt_delete}, 1047 {NULL, 0, 0}, 1048 }; 1049 1050 while ((ret = command_getopt(&str, opts)) != opt_done) { 1051 switch (ret) { 1052 case opt_error: 1053 return; 1054 case opt_delete: 1055 delete = 1; 1056 break; 1057 } 1058 } 1059 1060 if (str) 1061 alias = strtok_r(str, " ", &cmd); 1062 1063 if (delete) { 1064 if (alias_remove(alias) == -1) 1065 ui_error("no such alias: '%s'", alias); 1066 return; 1067 } 1068 1069 if (!alias) { 1070 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_START :Aliases:"); 1071 for (p = aliases; p; p = p->next) 1072 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_LIST %s :%s", p->alias, p->cmd); 1073 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_END :End of aliases"); 1074 } else if (!cmd) { 1075 for (p = aliases; p; p = p->next) { 1076 if (strcmp(p->alias, alias) == 0) { 1077 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_START :Aliases:"); 1078 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_LIST %s :%s", p->alias, p->cmd); 1079 hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_END :End of aliases"); 1080 return; 1081 } 1082 } 1083 1084 ui_error("no such alias: '%s'", alias); 1085 } else { 1086 if (alias_add(alias, cmd) == -1 && !nouich) 1087 ui_error("alias already exists: '%s'", alias); 1088 } 1089 } 1090 1091 1092 COMMAND( 1093 command_help) { 1094 int cmdonly = 0; 1095 int found = 0; 1096 int i, j; 1097 1098 if (!str) { 1099 command_help(server, channel, "/help"); 1100 return; 1101 } 1102 1103 if (strcmp(str, "commands") == 0) { 1104 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", str); 1105 for (i=0; commands[i].name && commands[i].func; i++) 1106 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP : /%s", commands[i].name); 1107 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help"); 1108 return; 1109 } 1110 1111 if (strcmp(str, "variables") == 0) { 1112 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", str); 1113 for (i=0; config[i].name; i++) 1114 hist_format(selected.history, Activity_none, HIST_UI, "SELF_UI : %s", config[i].name); 1115 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help"); 1116 return; 1117 } 1118 1119 if (*str == '/') { 1120 cmdonly = 1; 1121 str++; 1122 } 1123 1124 for (i=0; commands[i].name && commands[i].func; i++) { 1125 if (strncmp(commands[i].name, str, strlen(str)) == 0) { 1126 found = 1; 1127 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", commands[i].name); 1128 for (j=0; commands[i].description[j]; j++) 1129 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP :%s", commands[i].description[j]); 1130 if (strcmp(commands[i].name, str) == 0) 1131 goto end; /* only print one for an exact match, i,e, /help format should only print the command, not all formats. */ 1132 } 1133 } 1134 1135 if (!cmdonly) { 1136 for (i=0; config[i].name; i++) { 1137 if (strncmp(config[i].name, str, strlen(str)) == 0) { 1138 found = 1; 1139 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", config[i].name); 1140 for (j=0; config[i].description[j]; j++) 1141 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP :%s", config[i].description[j]); 1142 if (strcmp(config[i].name, str) == 0) 1143 goto end; 1144 } 1145 } 1146 } 1147 1148 end: 1149 if (found) 1150 hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help"); 1151 else 1152 ui_error("no help on '%s'", str); 1153 } 1154 1155 COMMAND( 1156 command_echo) { 1157 if (!str) 1158 str = ""; 1159 1160 hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP, "SELF_UI :%s", str); 1161 } 1162 1163 COMMAND( 1164 command_grep) { 1165 struct History *p; 1166 regex_t re; 1167 size_t i; 1168 int regopt = 0, ret, raw = 0; 1169 char errbuf[1024], *s; 1170 enum { opt_extended, opt_icase, opt_raw }; 1171 static struct CommandOpts opts[] = { 1172 {"E", CMD_NARG, opt_extended}, 1173 {"i", CMD_NARG, opt_icase}, 1174 {"raw", CMD_NARG, opt_raw}, 1175 {NULL, 0, 0}, 1176 }; 1177 1178 hist_purgeopt(selected.history, HIST_GREP); 1179 windows[Win_main].refresh = 1; 1180 if (!str) { 1181 return; 1182 } 1183 1184 while ((ret = command_getopt(&str, opts)) != opt_done) { 1185 switch (ret) { 1186 case opt_error: 1187 return; 1188 case opt_extended: 1189 regopt |= REG_EXTENDED; 1190 break; 1191 case opt_icase: 1192 regopt |= REG_ICASE; 1193 break; 1194 case opt_raw: 1195 raw = 1; 1196 break; 1197 } 1198 } 1199 1200 if (config_getl("regex.extended")) 1201 regopt |= REG_EXTENDED; 1202 if (config_getl("regex.icase")) 1203 regopt |= REG_ICASE; 1204 1205 if ((ret = regcomp(&re, str, regopt)) != 0) { 1206 regerror(ret, &re, errbuf, sizeof(errbuf)); 1207 regfree(&re); 1208 ui_error("unable to compile regex '%s': %s", str, errbuf); 1209 return; 1210 } 1211 1212 hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_GREP, "SELF_GREP_START :%s", str); 1213 1214 /* Get oldest, but don't set p to NULL */ 1215 for (p = selected.history->history; p && p->next; p = p->next); 1216 1217 /* Traverse until we hit a message already generated by /grep */ 1218 for (; p && !(p->options & HIST_GREP); p = p->prev) { 1219 if (!raw && !p->format) 1220 p->format = estrdup(format(&windows[Win_main], NULL, p)); 1221 if (!raw && !p->rformat) { 1222 p->rformat = emalloc(strlen(p->format) + 1); 1223 /* since only one or zero characters are added to 1224 * rformat for each char in format, and both are the 1225 * same size, there is no need to expand rformat or 1226 * truncate it. */ 1227 for (i = 0, s = p->format; s && *s; s++) { 1228 switch (*s) { 1229 case 2: case 9: case 15: case 18: case 21: 1230 break; 1231 case 3: 1232 if (*s && isdigit(*(s+1))) 1233 s += 1; 1234 if (*s && isdigit(*(s+1))) 1235 s += 1; 1236 if (*s && *(s+1) == ',' && isdigit(*(s+2))) 1237 s += 2; 1238 else 1239 break; /* break here to avoid needing to check back for comma in next if */ 1240 if (isdigit(*(s+1))) 1241 s += 1; 1242 break; 1243 case '\n': case ' ': 1244 while (*(s+1) == ' ') 1245 s++; 1246 /* fallthrough */ 1247 default: 1248 p->rformat[i++] = *s != '\n' ? *s : ' '; 1249 } 1250 } 1251 } 1252 if (regexec(&re, raw ? p->raw : p->rformat, 0, NULL, 0) == 0) 1253 hist_addp(selected.history, p, p->activity, p->options | HIST_GREP | HIST_TMP); 1254 } 1255 1256 hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_GREP, "SELF_GREP_END :end of /grep command"); 1257 } 1258 1259 COMMAND( 1260 command_clear) { 1261 int ret, cleared = 0; 1262 enum { opt_tmp, opt_err, opt_serr, opt_log }; 1263 static struct CommandOpts opts[] = { 1264 {"tmp", CMD_NARG, opt_tmp}, 1265 {"err", CMD_NARG, opt_err}, 1266 {"serr", CMD_NARG, opt_serr}, 1267 {"log", CMD_NARG, opt_log}, 1268 {NULL, 0, 0}, 1269 }; 1270 1271 if (str) { 1272 while ((ret = command_getopt(&str, opts)) != opt_done) { 1273 switch (ret) { 1274 case opt_error: 1275 return; 1276 case opt_tmp: 1277 hist_purgeopt(selected.history, HIST_TMP); 1278 cleared = 1; 1279 break; 1280 case opt_err: 1281 hist_purgeopt(selected.history, HIST_ERR); 1282 cleared = 1; 1283 break; 1284 case opt_serr: 1285 hist_purgeopt(selected.history, HIST_SERR); 1286 cleared = 1; 1287 break; 1288 case opt_log: 1289 hist_purgeopt(selected.history, HIST_RLOG); 1290 cleared = 1; 1291 break; 1292 } 1293 } 1294 1295 if (*str) { 1296 command_toomany("clear"); 1297 return; 1298 } 1299 } 1300 1301 if (!cleared) 1302 hist_purgeopt(selected.history, HIST_ALL); 1303 windows[Win_main].refresh = 1; 1304 } 1305 1306 COMMAND( 1307 command_scroll) { 1308 int ret, winid = Win_main; 1309 long diff; 1310 enum { opt_buflist, opt_nicklist }; 1311 static struct CommandOpts opts[] = { 1312 {"buflist", CMD_NARG, opt_buflist}, 1313 {"nicklist", CMD_NARG, opt_nicklist}, 1314 {NULL, 0, 0}, 1315 }; 1316 1317 if (!str) 1318 goto narg; 1319 1320 while (!(*str == '-' && isdigit(*(str+1))) && (ret = command_getopt(&str, opts)) != opt_done) { 1321 switch (ret) { 1322 case opt_error: 1323 return; 1324 case opt_buflist: 1325 winid = Win_buflist; 1326 break; 1327 case opt_nicklist: 1328 winid = Win_nicklist; 1329 break; 1330 } 1331 1332 if (*str == '-' && isdigit(*(str+1))) 1333 break; 1334 } 1335 1336 if (!*str) 1337 goto narg; 1338 1339 diff = strtol(str, NULL, 10); 1340 if (diff == 0 || diff == LONG_MIN) 1341 windows[winid].scroll = -1; /* no scroll, tracking */ 1342 else if (windows[winid].scroll >= 0) 1343 windows[winid].scroll += diff; 1344 else 1345 windows[winid].scroll = diff; 1346 1347 windows[winid].refresh = 1; 1348 return; 1349 1350 narg: 1351 command_toofew("scroll"); 1352 } 1353 1354 COMMAND( 1355 command_source) { 1356 if (!str) { 1357 command_toofew("source"); 1358 return; 1359 } 1360 config_read(homepath(str)); 1361 } 1362 1363 COMMAND( 1364 command_dump) { 1365 FILE *file; 1366 int selected = 0; 1367 int def = 0, ret; 1368 int i; 1369 char **aup; 1370 struct Server *sp; 1371 struct Channel *chp; 1372 struct Alias *ap; 1373 struct Keybind *kp; 1374 struct Ignore *ip; 1375 enum { 1376 opt_aliases = 1, 1377 opt_bindings = 2, 1378 opt_formats = 4, 1379 opt_config = 8, 1380 opt_servers = 16, 1381 opt_channels = 32, 1382 opt_queries = 64, 1383 opt_autocmds = 128, 1384 opt_ignores = 256, 1385 opt_default = 512, 1386 }; 1387 static struct CommandOpts opts[] = { 1388 {"aliases", CMD_NARG, opt_aliases}, 1389 {"bindings", CMD_NARG, opt_bindings}, 1390 {"formats", CMD_NARG, opt_formats}, 1391 {"config", CMD_NARG, opt_config}, 1392 {"servers", CMD_NARG, opt_servers}, 1393 {"autocmds", CMD_NARG, opt_autocmds}, 1394 {"channels", CMD_NARG, opt_channels}, 1395 {"queries", CMD_NARG, opt_queries}, 1396 {"ignores", CMD_NARG, opt_ignores}, 1397 {"default", CMD_NARG, opt_default}, 1398 {NULL, 0, 0}, 1399 }; 1400 1401 while ((ret = command_getopt(&str, opts)) != opt_done) { 1402 switch (ret) { 1403 case opt_error: 1404 return; 1405 case opt_aliases: 1406 case opt_bindings: 1407 case opt_formats: 1408 case opt_config: 1409 case opt_servers: 1410 case opt_channels: 1411 case opt_autocmds: 1412 case opt_ignores: 1413 selected |= ret; 1414 break; 1415 case opt_default: 1416 def = 1; 1417 break; 1418 } 1419 } 1420 1421 if (!selected) 1422 selected = opt_default - 1; 1423 1424 if (!str || !*str) { 1425 command_toofew("dump"); 1426 return; 1427 } 1428 str = homepath(str); 1429 1430 if ((file = fopen(str, "wb")) == NULL) { 1431 ui_error("cannot open file '%s': %s", str, strerror(errno)); 1432 return; 1433 } 1434 1435 if (selected & (opt_servers|opt_channels|opt_queries|opt_autocmds) && servers) { 1436 if (selected & opt_servers) 1437 fprintf(file, "Network connections\n"); 1438 1439 for (sp = servers; sp; sp = sp->next) { 1440 if (selected & opt_servers) { 1441 fprintf(file, "/connect -network %s ", sp->name); 1442 if (strcmp_n(sp->self->nick, config_gets("def.nick")) != 0) 1443 fprintf(file, "-nick %s ", sp->self->nick); 1444 if (strcmp_n(sp->username, config_gets("def.user")) != 0) 1445 fprintf(file, "-user %s ", sp->username); 1446 if (strcmp_n(sp->realname, config_gets("def.real")) != 0) 1447 fprintf(file, "-real %s ", sp->realname); 1448 #ifdef TLS 1449 if (sp->tls) 1450 fprintf(file, "-tls "); 1451 #endif /* TLS */ 1452 fprintf(file, "%s %s\n", sp->host, sp->port); 1453 } 1454 if (selected & opt_autocmds) { 1455 for (aup = sp->autocmds; *aup; aup++) 1456 fprintf(file, "/server -auto %s %s\n", sp->name, *aup); 1457 } 1458 if (selected & opt_channels) { 1459 for (chp = sp->channels; chp; chp = chp->next) 1460 if (!(selected & opt_autocmds) || !serv_auto_haschannel(sp, chp->name)) 1461 fprintf(file, "/server %s /join %s\n", sp->name, chp->name); 1462 } 1463 if (selected & opt_queries) { 1464 for (chp = sp->queries; chp; chp = chp->next) 1465 fprintf(file, "/server %s /query %s\n", sp->name, chp->name); 1466 } 1467 fprintf(file, "\n"); 1468 } 1469 } 1470 1471 if (selected & opt_aliases && aliases) { 1472 fprintf(file, "Aliases\n"); 1473 for (ap = aliases; ap; ap = ap->next) 1474 fprintf(file, "/alias %s %s\n", ap->alias, ap->cmd); 1475 fprintf(file, "\n"); 1476 } 1477 1478 if (selected & opt_bindings && keybinds) { 1479 fprintf(file, "Keybindings\n"); 1480 for (kp = keybinds; kp; kp = kp->next) 1481 fprintf(file, "/bind %s %s\n", ui_unctrl(kp->binding), kp->cmd); 1482 fprintf(file, "\n"); 1483 } 1484 1485 if (selected & opt_formats || selected & opt_config) { 1486 fprintf(file, "Configuration variables\n"); 1487 for (i = 0; config[i].name; i++) { 1488 if (!config[i].isdef || def) { 1489 if (selected & opt_formats && strncmp(config[i].name, "format.", CONSTLEN("format.")) == 0) { 1490 fprintf(file, "/format %s %s\n", config[i].name + CONSTLEN("format."), config[i].str); 1491 } else if (selected & opt_config && strncmp(config[i].name, "format.", CONSTLEN("format.")) != 0) { 1492 fprintf(file, "/set %s %s\n", config[i].name, config_get_pretty(&config[i], 0)); 1493 } 1494 } 1495 } 1496 fprintf(file, "\n"); 1497 } 1498 1499 if (selected & opt_ignores && ignores) { 1500 fprintf(file, "Ignore rules\n"); 1501 for (ip = ignores; ip; ip = ip->next) { 1502 if (ip->server) 1503 fprintf(file, "/server %s /ignore -server ", ip->server); 1504 else 1505 fprintf(file, "/ignore "); 1506 1507 if (ip->format) 1508 fprintf(file, "-format %s ", ip->format); 1509 if (ip->regopt & REG_EXTENDED) 1510 fprintf(file, "-E "); 1511 if (ip->regopt & REG_ICASE) 1512 fprintf(file, "-i "); 1513 1514 fprintf(file, "%s\n", ip->text); 1515 } 1516 fprintf(file, "\n"); 1517 } 1518 1519 fclose(file); 1520 } 1521 1522 COMMAND( 1523 command_close) { 1524 struct Server *sp; 1525 struct Channel *chp; 1526 int buf; 1527 1528 if (str) { 1529 buf = atoi(str); 1530 if (!buf) 1531 ui_error("invalid buffer index: '%s'", str); 1532 if (ui_buflist_get(buf, &sp, &chp) == -1) 1533 return; 1534 } else { 1535 sp = selected.server; 1536 chp = selected.channel; 1537 } 1538 1539 if (!sp) { 1540 ui_error("cannot close main buffer", NULL); 1541 return; 1542 } 1543 1544 if (chp) { 1545 if (serv_ischannel(sp, chp->name)) { 1546 serv_write(sp, Sched_connected, "PART %s\r\n", chp->name); 1547 chan_remove(&sp->channels, chp->name); 1548 } else { 1549 chan_remove(&sp->queries, chp->name); 1550 } 1551 ui_select(sp, NULL); 1552 } else { 1553 if (sp->status != ConnStatus_notconnected) { 1554 ui_error("can't close connected server", NULL); 1555 return; 1556 } else { 1557 serv_remove(&servers, sp->name); 1558 } 1559 ui_select(NULL, NULL); 1560 } 1561 } 1562 1563 static char * 1564 strregopt(int regopt) { 1565 if (regopt & REG_EXTENDED && regopt & REG_ICASE) 1566 return "extended+icase"; 1567 if (regopt & REG_EXTENDED) 1568 return "extended"; 1569 if (regopt & REG_ICASE) 1570 return "basic+icase"; 1571 return "basic"; 1572 } 1573 1574 COMMAND( 1575 command_ignore) { 1576 struct Ignore *ign, *p; 1577 char errbuf[BUFSIZ], *format = NULL; 1578 size_t len; 1579 long id; 1580 int ret, noact = 0, i, regopt = 0, serv = 0; 1581 enum { opt_show, opt_hide, opt_extended, opt_icase, 1582 opt_server, opt_delete, opt_format, opt_noact }; 1583 static struct CommandOpts opts[] = { 1584 {"E", CMD_NARG, opt_extended}, 1585 {"i", CMD_NARG, opt_icase}, 1586 {"show", CMD_NARG, opt_show}, 1587 {"hide", CMD_NARG, opt_hide}, 1588 {"server", CMD_NARG, opt_server}, 1589 {"noact", CMD_NARG, opt_noact}, 1590 {"delete", CMD_NARG, opt_delete}, 1591 {"format", CMD_ARG, opt_format}, 1592 {NULL, 0, 0}, 1593 }; 1594 1595 if (!str) { 1596 hist_format(selected.history, Activity_none, HIST_UI, "SELF_IGNORES_START :Ignoring:"); 1597 for (p = ignores, i = 1; p; p = p->next, i++) 1598 if (!serv || !p->server || strcmp(server->name, p->server) == 0) 1599 hist_format(selected.history, Activity_none, HIST_UI|HIST_NIGN, "SELF_IGNORES_LIST %d %s %s %s %s :%s", 1600 i, p->server ? p->server : "ANY", 1601 p->noact ? "yes" : "no", 1602 p->format ? p->format : "ANY", 1603 strregopt(p->regopt), p->text); 1604 hist_format(selected.history, Activity_none, HIST_UI, "SELF_IGNORES_END :End of ignore list"); 1605 return; 1606 } 1607 1608 while ((ret = command_getopt(&str, opts)) != opt_done) { 1609 switch (ret) { 1610 case opt_error: 1611 return; 1612 case opt_show: case opt_hide: 1613 if (*str) { 1614 command_toomany("ignore"); 1615 } else { 1616 selected.showign = ret == opt_show; 1617 windows[Win_main].refresh = 1; 1618 } 1619 return; 1620 case opt_delete: 1621 if (!strisnum(str, 0)) { 1622 ui_error("invalid id: %s", str); 1623 return; 1624 } 1625 id = strtol(str, NULL, 10); 1626 if (!id || id > INT_MAX || id < INT_MIN) 1627 goto idrange; 1628 for (p = ignores, i = 1; p; p = p->next, i++) { 1629 if (i == id) { 1630 if (i == 1) 1631 ignores = p->next; 1632 if (p->next) 1633 p->next->prev = p->prev; 1634 if (p->prev) 1635 p->prev->next = p->next; 1636 regfree(&p->regex); 1637 pfree(&p->text); 1638 pfree(&p->server); 1639 free(p); 1640 return; 1641 } 1642 } 1643 idrange: 1644 ui_error("id out of range: %s", str); 1645 return; 1646 case opt_format: 1647 if (strncmp(command_optarg, "format.", CONSTLEN("format.")) == 0) { 1648 format = strdup(command_optarg); 1649 } else { 1650 len = strlen(command_optarg) + 8; 1651 format = smprintf(len, "format.%s", command_optarg); 1652 } 1653 1654 if (!config_gets(format)) { 1655 ui_error("no such format: %s", format + CONSTLEN("format")); 1656 free(format); 1657 return; 1658 } 1659 break; 1660 case opt_noact: 1661 noact = 1; 1662 break; 1663 case opt_extended: 1664 regopt |= REG_EXTENDED; 1665 break; 1666 case opt_icase: 1667 regopt |= REG_ICASE; 1668 break; 1669 case opt_server: 1670 serv = 1; 1671 break; 1672 } 1673 } 1674 1675 if (config_getl("regex.extended")) 1676 regopt |= REG_EXTENDED; 1677 if (config_getl("regex.icase")) 1678 regopt |= REG_ICASE; 1679 1680 if (!*str) { 1681 command_toofew("ignore"); 1682 return; 1683 } 1684 1685 ign = emalloc(sizeof(struct Ignore)); 1686 ign->next = ign->prev = NULL; 1687 if ((ret = regcomp(&ign->regex, str, regopt)) != 0) { 1688 regerror(ret, &ign->regex, errbuf, sizeof(errbuf)); 1689 ui_error("%s: %s", errbuf, str); 1690 free(ign); 1691 return; 1692 } 1693 ign->text = strdup(str); 1694 ign->format = format; 1695 ign->regopt = regopt; 1696 ign->noact = noact; 1697 ign->server = serv ? strdup(server->name) : NULL; 1698 1699 if (!nouich) 1700 hist_format(selected.history, Activity_none, HIST_UI|HIST_NIGN, 1701 "SELF_IGNORES_ADDED %s %s %s %s :%s", 1702 serv ? server->name : "ANY", 1703 noact ? "yes" : "no", 1704 format ? format : "ANY", 1705 strregopt(regopt), str); 1706 1707 if (!ignores) { 1708 ignores = ign; 1709 } else { 1710 for (p = ignores; p && p->next; p = p->next); 1711 ign->prev = p; 1712 p->next = ign; 1713 } 1714 } 1715 1716 static void 1717 modelset(char *cmd, struct Server *server, struct Channel *channel, 1718 int remove, char mode, char *args) { 1719 char *percmds, *p; 1720 char *modes; 1721 int percmd; 1722 int i; 1723 size_t len; 1724 1725 if (!args) { 1726 command_toofew(cmd); 1727 return; 1728 } 1729 1730 percmds = support_get(server, "MODES"); 1731 if (percmds) 1732 percmd = atoi(percmds); 1733 else 1734 percmd = config_getl("def.modes"); 1735 1736 /* 1737 * Now, I'd hope that servers do something clever like determining this 1738 * value from the max length of channels, nicks and IRC messages 1739 * overall, so that it is impossible to send messages that are too 1740 * long, but I doubt it. 1741 * TODO: some maths for limiting percmd based on lengths. 1742 * 1743 * It'd be useful to be able to check if the desired priviledge is 1744 * supported by the server. Theoretically some servers could also use 1745 * different modes for priviledges, in this case it would make sense 1746 * that 'char mode' be the symbol, i,e, ~&@%+ and then we would look up 1747 * the mode in PREFIX. 1748 * TODO: check PREFIX=... 1749 * 1750 */ 1751 1752 /* 2 = null byte and +/- */ 1753 len = percmd + 2; 1754 modes = emalloc(len); 1755 *modes = remove ? '-' : '+'; 1756 for (i = 0; i < percmd; i++) 1757 *(modes + i + 1) = mode; 1758 *(modes + len - 1) = '\0'; 1759 1760 while (*args) { 1761 for (i = 0, p = args; *p && i < percmd; p++) 1762 if (*p == ' ' && *(p+1) != ' ') 1763 i++; 1764 if (*p) 1765 *(p - 1) = '\0'; 1766 else 1767 i++; 1768 *(modes + i + 1) = '\0'; 1769 1770 serv_write(server, Sched_connected, "MODE %s %s %s\r\n", channel->name, modes, args); 1771 1772 args = p; 1773 } 1774 1775 expect_set(server, Expect_nosuchnick, channel->name); 1776 pfree(&modes); 1777 } 1778 1779 COMMAND( 1780 command_op) { 1781 modelset("op", server, channel, 0, 'o', str); 1782 } 1783 1784 COMMAND( 1785 command_voice) { 1786 modelset("voice", server, channel, 0, 'v', str); 1787 } 1788 1789 COMMAND( 1790 command_halfop) { 1791 modelset("halfop", server, channel, 0, 'h', str); 1792 } 1793 1794 COMMAND( 1795 command_admin) { 1796 modelset("admin", server, channel, 0, 'a', str); 1797 } 1798 1799 COMMAND( 1800 command_owner) { 1801 modelset("owner", server, channel, 0, 'q', str); 1802 } 1803 1804 COMMAND( 1805 command_deop) { 1806 modelset("deop", server, channel, 1, 'o', str); 1807 } 1808 1809 COMMAND( 1810 command_devoice) { 1811 modelset("devoice", server, channel, 1, 'v', str); 1812 } 1813 1814 COMMAND( 1815 command_dehalfop) { 1816 modelset("dehalfop", server, channel, 1, 'h', str); 1817 } 1818 1819 COMMAND( 1820 command_deadmin) { 1821 modelset("deadmin", server, channel, 1, 'a', str); 1822 } 1823 1824 COMMAND( 1825 command_deowner) { 1826 modelset("deowner", server, channel, 1, 'q', str); 1827 } 1828 1829 /* In most IRC clients /ban would create a mask from a nickname. I decided 1830 * against doing this as there would have to be some way of templating a mask, 1831 * eg, ${nick}*!*@*.{host}. This could've been done through splitting the 1832 * variable handling out of ui_format and using that here as well, however, 1833 * all though it could simplify ui_format by processing variables first, then 1834 * formats, it could cause problems as the variables themselves could contain 1835 * text that needs to be escaped or dealt with. Of course, there's nothing 1836 * stopping me from leaving the one in ui_format and creating another, but at 1837 * that point I decided it wasn't worth it for one command. Another approach I 1838 * though of was having the ability to create templates with combinatorial 1839 * options, but I think the mental overhead for doing so could be spent 1840 * constructing the masks manually instead. *shrug*/ 1841 COMMAND( 1842 command_ban) { 1843 modelset("ban", server, channel, 0, 'b', str); 1844 } 1845 1846 COMMAND( 1847 command_unban) { 1848 modelset("unban", server, channel, 1, 'b', str); 1849 } 1850 1851 COMMAND( 1852 command_invite) { 1853 char *nick, *chan; 1854 1855 if (!str) { 1856 command_toofew("invite"); 1857 return; 1858 } 1859 1860 nick = strtok_r(str, " ", &chan); 1861 if (!chan) 1862 chan = channel->name; 1863 1864 serv_write(server, Sched_connected, "INVITE %s %s\r\n", nick, chan); 1865 } 1866 1867 int 1868 command_getopt(char **str, struct CommandOpts *opts) { 1869 char *opt; 1870 1871 if (!str || !*str || **str != '-') { 1872 if (str && *str && **str == '\\' && *((*str)+1) == '-') 1873 (*str)++; 1874 return opt_done; 1875 } 1876 1877 opt = struntil((*str)+1, ' '); 1878 1879 for (; opts->opt; opts++) { 1880 if (strcmp(opts->opt, opt) == 0) { 1881 *str = strchr(*str, ' '); 1882 if (*str) 1883 (*str)++; 1884 1885 if (opts->arg) { 1886 command_optarg = *str; 1887 if (*str) 1888 *str = strchr(*str, ' '); 1889 if (*str && **str) { 1890 **str = '\0'; 1891 (*str)++; 1892 } 1893 } else { 1894 if (*str && **str) 1895 *((*str)-1) = '\0'; 1896 } 1897 1898 /* Always return something that's not NULL */ 1899 if (!*str) 1900 *str = ""; 1901 1902 return opts->ret; 1903 } 1904 } 1905 1906 ui_error("no such option '%s'", opt); 1907 return opt_error; 1908 } 1909 1910 int 1911 alias_add(char *alias, char *cmd) { 1912 struct Alias *p; 1913 char *tmp; 1914 1915 if (!alias || !cmd) 1916 return -1; 1917 1918 if (*alias != '/') { 1919 tmp = smprintf(strlen(alias) + 2, "/%s", alias); 1920 } 1921 1922 for (p = aliases; p; p = p->next) 1923 if (strcmp(p->alias, tmp) == 0) 1924 return -1; 1925 1926 p = emalloc(sizeof(struct Alias)); 1927 if (*alias != '/') 1928 p->alias = tmp; 1929 else 1930 p->alias = estrdup(alias); 1931 1932 if (*cmd != '/') { 1933 tmp = smprintf(strlen(cmd) + 2, "/%s", cmd); 1934 p->cmd = tmp; 1935 } else p->cmd = estrdup(cmd); 1936 p->prev = NULL; 1937 p->next = aliases; 1938 if (aliases) 1939 aliases->prev = p; 1940 aliases = p; 1941 1942 return 0; 1943 } 1944 1945 int 1946 alias_remove(char *alias) { 1947 struct Alias *p; 1948 char *tmp = NULL; 1949 1950 if (!alias) 1951 return -1; 1952 if (*alias != '/') { 1953 tmp = smprintf(strlen(alias) + 2, "/%s", alias); 1954 alias = tmp; 1955 /* tmp is guaranteed NULL or freeable */ 1956 }; 1957 1958 for (p=aliases; p; p = p->next) { 1959 if (strcmp(p->alias, alias) == 0) { 1960 if (p->prev) 1961 p->prev->next = p->next; 1962 else 1963 aliases = p->next; 1964 1965 if (p->next) 1966 p->next->prev = p->prev; 1967 1968 pfree(&p->alias); 1969 pfree(&p->cmd); 1970 pfree(&p); 1971 pfree(&tmp); 1972 return 0; 1973 } 1974 } 1975 1976 return -1; 1977 } 1978 1979 char * 1980 alias_eval(char *cmd) { 1981 static char ret[8192]; 1982 struct Alias *p; 1983 int len, rc = 0; 1984 char *s; 1985 1986 if ((s = strchr(cmd, ' ')) != NULL) 1987 len = s - cmd; 1988 else 1989 len = strlen(cmd); 1990 1991 for (p = aliases; p; p = p->next) { 1992 if (p->cmd && strlen(p->alias) == len && strncmp(p->alias, cmd, len) == 0) { 1993 rc += strlcpy(&ret[rc], p->cmd, sizeof(ret) - rc); 1994 break; 1995 } 1996 } 1997 1998 if (!rc) 1999 return cmd; 2000 2001 rc += strlcpy(&ret[rc], cmd + len, sizeof(ret) - rc); 2002 return ret; 2003 } 2004 2005 void 2006 command_eval(struct Server *server, char *str) { 2007 struct Command *cmdp; 2008 char msg[512]; 2009 char *cmd; 2010 char *s, *dup; 2011 2012 s = dup = estrdup(alias_eval(str)); 2013 2014 if (*s != '/' || strncmp(s, "/ /", CONSTLEN("/ /")) == 0) { 2015 /* Provide a way to escape commands 2016 * "/ /cmd" --> "/cmd" */ 2017 if (strncmp(s, "/ /", CONSTLEN("/ /")) == 0) 2018 s += 2; 2019 2020 if (selected.channel && selected.server) { 2021 // TODO: message splitting 2022 snprintf(msg, sizeof(msg), "PRIVMSG %s :%s", selected.channel->name, s); 2023 serv_write(selected.server, Sched_connected, "%s\r\n", msg); 2024 hist_format(selected.channel->history, Activity_self, HIST_SHOW|HIST_LOG|HIST_SELF, "%s", msg); 2025 } else { 2026 ui_error("channel not selected, message ignored", NULL); 2027 } 2028 goto end; 2029 } else { 2030 s++; 2031 cmd = s; 2032 s = strchr(s, ' '); 2033 if (s && *s) { 2034 *s = '\0'; 2035 s++; 2036 if (*s == '\0') 2037 s = NULL; 2038 } 2039 2040 for (cmdp = commands; cmdp->name && cmdp->func; cmdp++) { 2041 if (strcmp(cmdp->name, cmd) == 0) { 2042 if (cmdp->need == 2 && !selected.channel) 2043 ui_error("/%s requires a channel to be selected", cmdp->name); 2044 else if (cmdp->need == 2 && selected.channel->server != server) 2045 ui_error("/%s cannot be run with /server", cmdp->name); 2046 else if (cmdp->need == 1 && !server) 2047 ui_error("/%s requires a server to be selected or provided by /server", cmdp->name); 2048 else 2049 cmdp->func(server, selected.channel, s); 2050 2051 2052 goto end; 2053 } 2054 } 2055 2056 ui_error("no such command: '%s'", cmd); 2057 } 2058 end: 2059 pfree(&dup); 2060 }