diff --git a/catgirl.1 b/catgirl.1 index dc5aefb..672abfc 100644 --- a/catgirl.1 +++ b/catgirl.1 @@ -12,6 +12,7 @@ .Op Fl C Ar copy .Op Fl H Ar hash .Op Fl O Ar open +.Op Fl S Ar bind .Op Fl a Ar auth .Op Fl c Ar cert .Op Fl h Ar host @@ -85,6 +86,11 @@ Disable the .Ic /quote commands. . +.It Fl S Ar host , Cm bind = Ar host +Bind to source address +.Ar host +when connecting to the server. +. .It Fl a Ar user Ns : Ns Ar pass , Cm sasl-plain = Ar user Ns : Ns Ar pass Authenticate as .Ar user diff --git a/chat.c b/chat.c index 2c41d43..c0950fb 100644 --- a/chat.c +++ b/chat.c @@ -82,6 +82,7 @@ int main(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); bool insecure = false; + const char *bind = NULL; const char *host = NULL; const char *port = "6697"; const char *cert = NULL; @@ -93,13 +94,14 @@ int main(int argc, char *argv[]) { const char *user = NULL; const char *real = NULL; - const char *Opts = "!C:H:O:Ra:c:eh:j:k:n:p:r:s:u:vw:"; + const char *Opts = "!C:H:O:RS:a:c:eh:j:k:n:p:r:s:u:vw:"; const struct option LongOpts[] = { { "insecure", no_argument, NULL, '!' }, { "copy", required_argument, NULL, 'C' }, { "hash", required_argument, NULL, 'H' }, { "open", required_argument, NULL, 'O' }, { "restrict", no_argument, NULL, 'R' }, + { "bind", required_argument, NULL, 'S' }, { "sasl-plain", required_argument, NULL, 'a' }, { "cert", required_argument, NULL, 'c' }, { "sasl-external", no_argument, NULL, 'e' }, @@ -124,6 +126,7 @@ int main(int argc, char *argv[]) { break; case 'H': hashInit = strtoul(optarg, NULL, 0); break; case 'O': utilPush(&urlOpenUtil, optarg); break; case 'R': self.restricted = true; + break; case 'S': bind = optarg; break; case 'a': sasl = true; self.plain = optarg; break; case 'c': cert = optarg; break; case 'e': sasl = true; @@ -182,7 +185,7 @@ int main(int argc, char *argv[]) { uiFormat(Network, Cold, NULL, "Traveling..."); uiDraw(); - int irc = ircConnect(host, port); + int irc = ircConnect(bind, host, port); if (pass) ircFormat("PASS :%s\r\n", pass); if (sasl) ircFormat("CAP REQ :sasl\r\n"); ircFormat("CAP LS\r\n"); diff --git a/chat.h b/chat.h index 6af5ef7..ed6dc2f 100644 --- a/chat.h +++ b/chat.h @@ -125,7 +125,7 @@ struct Message { }; void ircConfig(bool insecure, FILE *cert, FILE *priv); -int ircConnect(const char *host, const char *port); +int ircConnect(const char *bind, const char *host, const char *port); void ircRecv(void); void ircSend(const char *ptr, size_t len); void ircFormat(const char *format, ...) diff --git a/irc.c b/irc.c index 05f8f9d..3ecc582 100644 --- a/irc.c +++ b/irc.c @@ -97,22 +97,47 @@ void ircConfig(bool insecure, FILE *cert, FILE *priv) { tls_config_free(config); } -int ircConnect(const char *host, const char *port) { +int ircConnect(const char *bindHost, const char *host, const char *port) { assert(client); + int error; + int sock = -1; struct addrinfo *head; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_protocol = IPPROTO_TCP, }; - int error = getaddrinfo(host, port, &hints, &head); + + if (bindHost) { + error = getaddrinfo(bindHost, NULL, &hints, &head); + if (error) errx(EX_NOHOST, "%s: %s", host, gai_strerror(error)); + + for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) err(EX_OSERR, "socket"); + + error = bind(sock, ai->ai_addr, ai->ai_addrlen); + if (!error) { + hints.ai_family = ai->ai_family; + break; + } + + close(sock); + sock = -1; + } + if (sock < 0) err(EX_UNAVAILABLE, "%s", bindHost); + freeaddrinfo(head); + } + + error = getaddrinfo(host, port, &hints, &head); if (error) errx(EX_NOHOST, "%s:%s: %s", host, port, gai_strerror(error)); - int sock = -1; for (struct addrinfo *ai = head; ai; ai = ai->ai_next) { - sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sock < 0) err(EX_OSERR, "socket"); + if (sock < 0) { + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) err(EX_OSERR, "socket"); + } error = connect(sock, ai->ai_addr, ai->ai_addrlen); if (!error) break;