| 1 |
|
/* |
| 2 |
|
* Copyright (c) 2002-2003 Erik Fears |
| 3 |
< |
* Copyright (c) 2014-2017 ircd-hybrid development team |
| 3 |
> |
* Copyright (c) 2014-2020 ircd-hybrid development team |
| 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 as published by |
| 35 |
|
#include <stdarg.h> |
| 36 |
|
#include <regex.h> |
| 37 |
|
#include <assert.h> |
| 38 |
+ |
#ifdef HAVE_LIBCRYPTO |
| 39 |
+ |
#include <openssl/ssl.h> |
| 40 |
+ |
#include <openssl/x509v3.h> |
| 41 |
+ |
#include <openssl/err.h> |
| 42 |
+ |
#endif |
| 43 |
|
|
| 44 |
|
#include "config.h" |
| 45 |
|
#include "irc.h" |
| 70 |
|
static time_t IRC_LAST; /* Last full line of data from irc server */ |
| 71 |
|
static time_t IRC_LASTRECONNECT; /* Time of last reconnection */ |
| 72 |
|
|
| 73 |
+ |
#ifdef HAVE_LIBCRYPTO |
| 74 |
+ |
static SSL_CTX *ssl_ctx; |
| 75 |
+ |
static SSL *ssl_handle; |
| 76 |
+ |
#endif |
| 77 |
+ |
|
| 78 |
|
|
| 79 |
|
/* get_channel |
| 80 |
|
* |
| 91 |
|
{ |
| 92 |
|
node_t *node; |
| 93 |
|
|
| 94 |
< |
LIST_FOREACH(node, IRCItem->channels->head) |
| 94 |
> |
LIST_FOREACH(node, IRCItem.channels.head) |
| 95 |
|
{ |
| 96 |
|
struct ChannelConf *item = node->data; |
| 97 |
|
|
| 119 |
|
{ |
| 120 |
|
node_t *node; |
| 121 |
|
|
| 122 |
< |
log_printf("IRC -> Connected to %s/%d", IRCItem->server, IRCItem->port); |
| 122 |
> |
log_printf("IRC -> Connected to %s/%d", IRCItem.server, IRCItem.port); |
| 123 |
|
|
| 124 |
|
/* Identify to nickserv if needed */ |
| 125 |
< |
if (!EmptyString(IRCItem->nickserv)) |
| 126 |
< |
irc_send("%s", IRCItem->nickserv); |
| 125 |
> |
if (!EmptyString(IRCItem.nickserv)) |
| 126 |
> |
irc_send("%s", IRCItem.nickserv); |
| 127 |
|
|
| 128 |
|
/* Oper */ |
| 129 |
< |
irc_send("OPER %s", IRCItem->oper); |
| 129 |
> |
irc_send("OPER %s", IRCItem.oper); |
| 130 |
|
|
| 131 |
|
/* Set modes */ |
| 132 |
< |
irc_send("MODE %s %s", IRCItem->nick, IRCItem->mode); |
| 132 |
> |
irc_send("MODE %s %s", IRCItem.nick, IRCItem.mode); |
| 133 |
|
|
| 134 |
|
/* Set Away */ |
| 135 |
< |
if (!EmptyString(IRCItem->away)) |
| 136 |
< |
irc_send("AWAY :%s", IRCItem->away); |
| 135 |
> |
if (!EmptyString(IRCItem.away)) |
| 136 |
> |
irc_send("AWAY :%s", IRCItem.away); |
| 137 |
|
|
| 138 |
|
/* Perform */ |
| 139 |
< |
LIST_FOREACH(node, IRCItem->performs->head) |
| 139 |
> |
LIST_FOREACH(node, IRCItem.performs.head) |
| 140 |
|
irc_send("%s", node->data); |
| 141 |
|
|
| 142 |
|
/* Join all listed channels. */ |
| 143 |
< |
LIST_FOREACH(node, IRCItem->channels->head) |
| 143 |
> |
LIST_FOREACH(node, IRCItem.channels.head) |
| 144 |
|
{ |
| 145 |
|
const struct ChannelConf *channel = node->data; |
| 146 |
|
|
| 188 |
|
static void |
| 189 |
|
m_invite(char *parv[], unsigned int parc, const char *msg, const char *source_p) |
| 190 |
|
{ |
| 181 |
– |
const struct ChannelConf *channel = NULL; |
| 182 |
– |
|
| 191 |
|
if (parc < 4) |
| 192 |
|
return; |
| 193 |
|
|
| 194 |
|
log_printf("IRC -> Invited to %s by %s", parv[3], parv[0]); |
| 195 |
|
|
| 196 |
< |
if ((channel = get_channel(parv[3])) == NULL) |
| 196 |
> |
const struct ChannelConf *channel = get_channel(parv[3]); |
| 197 |
> |
if (channel == NULL) |
| 198 |
|
return; |
| 199 |
|
|
| 200 |
|
irc_send("JOIN %s %s", channel->name, channel->key); |
| 230 |
|
static void |
| 231 |
|
m_privmsg(char *parv[], unsigned int parc, const char *msg, const char *source_p) |
| 232 |
|
{ |
| 224 |
– |
const struct ChannelConf *channel = NULL; |
| 225 |
– |
|
| 233 |
|
if (source_p == NULL) |
| 234 |
|
return; |
| 235 |
|
|
| 248 |
|
return; |
| 249 |
|
|
| 250 |
|
/* Get a target */ |
| 251 |
< |
if ((channel = get_channel(parv[2])) == NULL) |
| 251 |
> |
const struct ChannelConf *channel = get_channel(parv[2]); |
| 252 |
> |
if (channel == NULL) |
| 253 |
|
return; |
| 254 |
|
|
| 255 |
|
int hit = strncasecmp(parv[3], "!all ", 5) == 0; |
| 256 |
|
if (hit == 0) |
| 257 |
|
{ |
| 258 |
< |
size_t nick_len = strlen(IRCItem->nick); |
| 258 |
> |
size_t nick_len = strlen(IRCItem.nick); |
| 259 |
|
|
| 260 |
< |
if (strncasecmp(parv[3], IRCItem->nick, nick_len) == 0) |
| 261 |
< |
hit = *(parv[3] + nick_len) == ' '; |
| 260 |
> |
if (strncasecmp(parv[3], IRCItem.nick, nick_len) == 0) |
| 261 |
> |
hit = *(parv[3] + nick_len) == ' ' || |
| 262 |
> |
*(parv[3] + nick_len) == ',' || |
| 263 |
> |
*(parv[3] + nick_len) == ':'; |
| 264 |
|
} |
| 265 |
|
|
| 266 |
|
if (hit) |
| 267 |
|
/* XXX command_parse will alter parv[3]. */ |
| 268 |
< |
command_parse(parv[3], channel, source_p); |
| 268 |
> |
command_parse(parv[3], channel->name, source_p); |
| 269 |
|
} |
| 270 |
|
|
| 271 |
|
/* m_notice |
| 300 |
|
{ |
| 301 |
|
preg = xcalloc(sizeof(*preg)); |
| 302 |
|
|
| 303 |
< |
if ((errnum = regcomp(preg, IRCItem->connregex, REG_ICASE | REG_EXTENDED))) |
| 303 |
> |
if ((errnum = regcomp(preg, IRCItem.connregex, REG_ICASE | REG_EXTENDED))) |
| 304 |
|
{ |
| 305 |
|
char errmsg[256]; |
| 306 |
|
|
| 344 |
|
log_printf("IRC REGEX -> Parsed %s!%s@%s [%s] from connection notice.", |
| 345 |
|
user[0], user[1], user[2], user[3]); |
| 346 |
|
|
| 347 |
< |
LIST_FOREACH(node, IRCItem->notices->head) |
| 347 |
> |
LIST_FOREACH(node, IRCItem.notices.head) |
| 348 |
|
irc_send("NOTICE %s :%s", user[0], node->data); |
| 349 |
|
|
| 350 |
|
/* Pass this information off to scan.c */ |
| 385 |
|
static void |
| 386 |
|
m_cannot_join(char *parv[], unsigned int parc, const char *msg, const char *source_p) |
| 387 |
|
{ |
| 378 |
– |
const struct ChannelConf *channel = NULL; |
| 379 |
– |
|
| 388 |
|
if (parc < 5) |
| 389 |
|
return; |
| 390 |
|
|
| 391 |
|
/* Is it one of our channels? */ |
| 392 |
< |
if ((channel = get_channel(parv[3])) == NULL) |
| 392 |
> |
const struct ChannelConf *channel = get_channel(parv[3]); |
| 393 |
> |
if (channel == NULL) |
| 394 |
|
return; |
| 395 |
|
|
| 396 |
|
if (EmptyString(channel->invite)) |
| 468 |
|
static void |
| 469 |
|
irc_init(void) |
| 470 |
|
{ |
| 471 |
< |
const void *address = NULL; |
| 471 |
> |
const void *address; |
| 472 |
|
|
| 473 |
|
assert(IRC_FD == -1); |
| 474 |
|
|
| 475 |
|
memset(&IRC_SVR, 0, sizeof(IRC_SVR)); |
| 476 |
|
|
| 477 |
|
/* Resolve IRC host. */ |
| 478 |
< |
if ((address = firedns_resolveip6(IRCItem->server))) |
| 478 |
> |
if ((address = firedns_resolveip6(IRCItem.server))) |
| 479 |
|
{ |
| 480 |
|
struct sockaddr_in6 *in = (struct sockaddr_in6 *)&IRC_SVR; |
| 481 |
|
|
| 482 |
|
svr_addrlen = sizeof(*in); |
| 483 |
|
IRC_SVR.ss_family = AF_INET6; |
| 484 |
< |
in->sin6_port = htons(IRCItem->port); |
| 484 |
> |
in->sin6_port = htons(IRCItem.port); |
| 485 |
|
memcpy(&in->sin6_addr, address, sizeof(in->sin6_addr)); |
| 486 |
|
} |
| 487 |
< |
else if ((address = firedns_resolveip4(IRCItem->server))) |
| 487 |
> |
else if ((address = firedns_resolveip4(IRCItem.server))) |
| 488 |
|
{ |
| 489 |
|
struct sockaddr_in *in = (struct sockaddr_in *)&IRC_SVR; |
| 490 |
|
|
| 491 |
|
svr_addrlen = sizeof(*in); |
| 492 |
|
IRC_SVR.ss_family = AF_INET; |
| 493 |
< |
in->sin_port = htons(IRCItem->port); |
| 493 |
> |
in->sin_port = htons(IRCItem.port); |
| 494 |
|
memcpy(&in->sin_addr, address, sizeof(in->sin_addr)); |
| 495 |
|
} |
| 496 |
|
else |
| 497 |
|
{ |
| 498 |
< |
log_printf("IRC -> firedns_resolveip(\"%s\"): %s", IRCItem->server, |
| 498 |
> |
log_printf("IRC -> firedns_resolveip(\"%s\"): %s", IRCItem.server, |
| 499 |
|
firedns_strerror(firedns_errno)); |
| 500 |
|
exit(EXIT_FAILURE); |
| 501 |
|
} |
| 510 |
|
} |
| 511 |
|
|
| 512 |
|
/* Bind */ |
| 513 |
< |
if (!EmptyString(IRCItem->vhost)) |
| 513 |
> |
if (!EmptyString(IRCItem.vhost)) |
| 514 |
|
{ |
| 515 |
|
struct addrinfo hints, *res; |
| 507 |
– |
int n; |
| 516 |
|
|
| 517 |
|
memset(&hints, 0, sizeof(hints)); |
| 518 |
|
|
| 520 |
|
hints.ai_socktype = SOCK_STREAM; |
| 521 |
|
hints.ai_flags = AI_NUMERICHOST; |
| 522 |
|
|
| 523 |
< |
if ((n = getaddrinfo(IRCItem->vhost, NULL, &hints, &res))) |
| 523 |
> |
int n = getaddrinfo(IRCItem.vhost, NULL, &hints, &res); |
| 524 |
> |
if (n) |
| 525 |
|
{ |
| 526 |
< |
log_printf("IRC -> error binding to %s: %s", IRCItem->vhost, gai_strerror(n)); |
| 526 |
> |
log_printf("IRC -> error binding to %s: %s", IRCItem.vhost, gai_strerror(n)); |
| 527 |
|
exit(EXIT_FAILURE); |
| 528 |
|
} |
| 529 |
|
else if (bind(IRC_FD, res->ai_addr, res->ai_addrlen)) |
| 530 |
|
{ |
| 531 |
< |
log_printf("IRC -> error binding to %s: %s", IRCItem->vhost, strerror(errno)); |
| 531 |
> |
log_printf("IRC -> error binding to %s: %s", IRCItem.vhost, strerror(errno)); |
| 532 |
|
exit(EXIT_FAILURE); |
| 533 |
|
} |
| 534 |
|
|
| 535 |
|
freeaddrinfo(res); |
| 536 |
|
} |
| 537 |
+ |
|
| 538 |
+ |
if (IRCItem.tls) |
| 539 |
+ |
{ |
| 540 |
+ |
#ifdef HAVE_LIBCRYPTO |
| 541 |
+ |
/* Initialize SSL */ |
| 542 |
+ |
static int tls_init = 0; |
| 543 |
+ |
if (tls_init == 0) |
| 544 |
+ |
{ |
| 545 |
+ |
tls_init = 1; |
| 546 |
+ |
|
| 547 |
+ |
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); |
| 548 |
+ |
if (ssl_ctx == NULL) |
| 549 |
+ |
{ |
| 550 |
+ |
log_printf("IRC -> unable to create SSL context"); |
| 551 |
+ |
exit(EXIT_FAILURE); |
| 552 |
+ |
} |
| 553 |
+ |
|
| 554 |
+ |
SSL_CTX_set_default_verify_paths(ssl_ctx); |
| 555 |
+ |
} |
| 556 |
+ |
|
| 557 |
+ |
ssl_handle = SSL_new(ssl_ctx); |
| 558 |
+ |
SSL_set_fd(ssl_handle, IRC_FD); |
| 559 |
+ |
SSL_set_hostflags(ssl_handle, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); |
| 560 |
+ |
|
| 561 |
+ |
if (!SSL_set1_host(ssl_handle, IRCItem.server)) |
| 562 |
+ |
{ |
| 563 |
+ |
log_printf("IRC -> unable to set expected DNS hostname"); |
| 564 |
+ |
/* OpenSSL is unable to verify the server hostname at this point, so we exit. */ |
| 565 |
+ |
exit(EXIT_FAILURE); |
| 566 |
+ |
} |
| 567 |
+ |
|
| 568 |
+ |
SSL_set_verify(ssl_handle, SSL_VERIFY_PEER, NULL); |
| 569 |
+ |
#else |
| 570 |
+ |
log_printf("IRC -> HOPM is not compiled with OpenSSL support"); |
| 571 |
+ |
exit(EXIT_FAILURE); |
| 572 |
+ |
#endif |
| 573 |
+ |
} |
| 574 |
|
} |
| 575 |
|
|
| 576 |
|
/* irc_reconnect |
| 588 |
|
|
| 589 |
|
time(&present); |
| 590 |
|
|
| 591 |
< |
/* Only try to reconnect every IRCItem->reconnectinterval seconds */ |
| 592 |
< |
if ((present - IRC_LASTRECONNECT) < IRCItem->reconnectinterval) |
| 591 |
> |
/* Only try to reconnect every IRCItem.reconnectinterval seconds */ |
| 592 |
> |
if ((present - IRC_LASTRECONNECT) < IRCItem.reconnectinterval) |
| 593 |
|
{ |
| 594 |
|
/* Sleep to avoid excessive CPU */ |
| 595 |
|
sleep(1); |
| 600 |
|
|
| 601 |
|
if (IRC_FD > -1) |
| 602 |
|
{ |
| 603 |
+ |
#ifdef HAVE_LIBCRYPTO |
| 604 |
+ |
if (ssl_handle) |
| 605 |
+ |
{ |
| 606 |
+ |
SSL_shutdown(ssl_handle); |
| 607 |
+ |
SSL_free(ssl_handle); |
| 608 |
+ |
ssl_handle = NULL; |
| 609 |
+ |
} |
| 610 |
+ |
#endif |
| 611 |
+ |
|
| 612 |
|
close(IRC_FD); |
| 613 |
|
IRC_FD = -1; /* Set IRC_FD -1 for reconnection on next irc_cycle(). */ |
| 614 |
|
} |
| 615 |
|
|
| 616 |
< |
log_printf("IRC -> Connection to (%s) failed, reconnecting.", IRCItem->server); |
| 616 |
> |
log_printf("IRC -> Connection to (%s) failed, reconnecting.", IRCItem.server); |
| 617 |
|
} |
| 618 |
|
|
| 619 |
|
/* irc_connect |
| 631 |
|
if (connect(IRC_FD, (struct sockaddr *)&IRC_SVR, svr_addrlen) == -1) |
| 632 |
|
{ |
| 633 |
|
log_printf("IRC -> connect(): error connecting to %s: %s", |
| 634 |
< |
IRCItem->server, strerror(errno)); |
| 634 |
> |
IRCItem.server, strerror(errno)); |
| 635 |
|
|
| 636 |
|
if (errno == EISCONN /* Already connected */ || errno == EALREADY /* Previous attempt not complete */) |
| 637 |
|
return; |
| 641 |
|
return; |
| 642 |
|
} |
| 643 |
|
|
| 644 |
< |
irc_send("NICK %s", IRCItem->nick); |
| 644 |
> |
#ifdef HAVE_LIBCRYPTO |
| 645 |
> |
if (ssl_handle) |
| 646 |
> |
{ |
| 647 |
> |
int ret = SSL_connect(ssl_handle); |
| 648 |
> |
if (ret != 1) |
| 649 |
> |
{ |
| 650 |
> |
const char *error = ERR_error_string(ERR_get_error(), NULL); |
| 651 |
> |
log_printf("IRC -> connect(): error performing TLS handshake with %s: %s", |
| 652 |
> |
IRCItem.server, error); |
| 653 |
> |
|
| 654 |
> |
/* Try to connect again */ |
| 655 |
> |
irc_reconnect(); |
| 656 |
> |
return; |
| 657 |
> |
} |
| 658 |
> |
} |
| 659 |
> |
#endif |
| 660 |
> |
|
| 661 |
> |
irc_send("NICK %s", IRCItem.nick); |
| 662 |
|
|
| 663 |
< |
if (!EmptyString(IRCItem->password)) |
| 664 |
< |
irc_send("PASS %s", IRCItem->password); |
| 663 |
> |
if (!EmptyString(IRCItem.password)) |
| 664 |
> |
irc_send("PASS %s", IRCItem.password); |
| 665 |
|
|
| 666 |
|
irc_send("USER %s %s %s :%s", |
| 667 |
< |
IRCItem->username, |
| 668 |
< |
IRCItem->username, |
| 669 |
< |
IRCItem->username, |
| 670 |
< |
IRCItem->realname); |
| 667 |
> |
IRCItem.username, |
| 668 |
> |
IRCItem.username, |
| 669 |
> |
IRCItem.username, |
| 670 |
> |
IRCItem.realname); |
| 671 |
|
time(&IRC_LAST); |
| 672 |
|
} |
| 673 |
|
|
| 692 |
|
* source did not exist |
| 693 |
|
*/ |
| 694 |
|
char *parv[17]; |
| 695 |
+ |
static const unsigned int parv_size = sizeof(parv) / sizeof(parv[0]); |
| 696 |
|
unsigned int parc = 1; |
| 697 |
|
char msg[MSGLENMAX]; /* Temporarily stores IRC msg to pass to handlers */ |
| 698 |
|
|
| 738 |
|
parv[0] = IRC_RAW + 1; |
| 739 |
|
else |
| 740 |
|
{ |
| 741 |
< |
parv[0] = IRCItem->server; |
| 741 |
> |
parv[0] = IRCItem.server; |
| 742 |
|
parv[parc++] = IRC_RAW; |
| 743 |
|
} |
| 744 |
|
|
| 745 |
|
pos = IRC_RAW; |
| 746 |
|
|
| 747 |
< |
while ((pos = strchr(pos, ' ')) && parc <= 17) |
| 747 |
> |
while ((pos = strchr(pos, ' ')) && parc < parv_size) |
| 748 |
|
{ |
| 749 |
|
/* Avoid excessive spaces and end of IRC_RAW */ |
| 750 |
|
if (*(pos + 1) == ' ' || *(pos + 1) == '\0') |
| 798 |
|
ssize_t len; |
| 799 |
|
char c; |
| 800 |
|
|
| 801 |
< |
while ((len = read(IRC_FD, &c, 1)) > 0) |
| 801 |
> |
while (1) |
| 802 |
|
{ |
| 803 |
+ |
#ifdef HAVE_LIBCRYPTO |
| 804 |
+ |
if (ssl_handle) |
| 805 |
+ |
len = SSL_read(ssl_handle, &c, 1); |
| 806 |
+ |
else |
| 807 |
+ |
#endif |
| 808 |
+ |
len = recv(IRC_FD, &c, 1, 0); |
| 809 |
+ |
|
| 810 |
+ |
if (len <= 0) |
| 811 |
+ |
break; |
| 812 |
|
if (c == '\r') |
| 813 |
|
continue; |
| 814 |
|
|
| 858 |
|
if (IRC_FD == -1) |
| 859 |
|
{ |
| 860 |
|
/* Initialize negative cache. */ |
| 861 |
< |
if (OptionsItem->negcache) |
| 861 |
> |
if (OptionsItem.negcache) |
| 862 |
|
negcache_init(); |
| 863 |
|
|
| 864 |
|
/* Resolve remote host. */ |
| 904 |
|
{ |
| 905 |
|
va_list arglist; |
| 906 |
|
char buf[MSGLENMAX]; |
| 825 |
– |
size_t len = 0; |
| 907 |
|
|
| 908 |
|
va_start(arglist, data); |
| 909 |
< |
len = vsnprintf(buf, sizeof(buf), data, arglist); |
| 909 |
> |
ssize_t len = vsnprintf(buf, sizeof(buf), data, arglist); |
| 910 |
|
va_end(arglist); |
| 911 |
|
|
| 912 |
|
if (OPT_DEBUG >= 2) |
| 918 |
|
buf[len++] = '\r'; |
| 919 |
|
buf[len++] = '\n'; |
| 920 |
|
|
| 921 |
< |
if (send(IRC_FD, buf, len, 0) == -1) |
| 921 |
> |
ssize_t sent; |
| 922 |
> |
#ifdef HAVE_LIBCRYPTO |
| 923 |
> |
if (ssl_handle) |
| 924 |
> |
sent = SSL_write(ssl_handle, buf, len); |
| 925 |
> |
else |
| 926 |
> |
#endif |
| 927 |
> |
sent = send(IRC_FD, buf, len, 0); |
| 928 |
> |
|
| 929 |
> |
if (sent != len) |
| 930 |
|
{ |
| 931 |
|
/* Return of -1 indicates error sending data; we reconnect. */ |
| 932 |
|
log_printf("IRC -> Error sending data to server: %s", strerror(errno)); |
| 955 |
|
vsnprintf(buf, sizeof(buf), data, arglist); |
| 956 |
|
va_end(arglist); |
| 957 |
|
|
| 958 |
< |
LIST_FOREACH(node, IRCItem->channels->head) |
| 958 |
> |
LIST_FOREACH(node, IRCItem.channels.head) |
| 959 |
|
{ |
| 960 |
|
const struct ChannelConf *chan = node->data; |
| 961 |
|
|
| 979 |
|
|
| 980 |
|
delta = present - IRC_LAST; |
| 981 |
|
|
| 982 |
< |
/* No data in IRCItem->readtimeout seconds */ |
| 983 |
< |
if (delta >= IRCItem->readtimeout) |
| 982 |
> |
/* No data in IRCItem.readtimeout seconds */ |
| 983 |
> |
if (delta >= IRCItem.readtimeout) |
| 984 |
|
{ |
| 985 |
|
log_printf("IRC -> Timeout awaiting data from server."); |
| 986 |
|
irc_reconnect(); |
| 988 |
|
/* Make sure we don't do this again for a while */ |
| 989 |
|
time(&IRC_LAST); |
| 990 |
|
} |
| 991 |
< |
else if (delta >= IRCItem->readtimeout / 2) |
| 991 |
> |
else if (delta >= IRCItem.readtimeout / 2) |
| 992 |
|
{ |
| 993 |
|
/* |
| 994 |
|
* Generate some data so high ping times don't cause uneeded |