about summary refs log tree commit diff
diff options
context:
space:
mode:
authormultiplexd <multi@in-addr.xyz>2020-02-12 01:02:37 +0000
committerJune McEnroe <june@causal.agency>2020-02-13 00:05:52 +0000
commit1e544ce482ba97f9c4e2be114f88f11e49bdbf1b (patch)
tree5c25157df46e06fe062edb0d91809653b8587c25
parentAdd /list (diff)
downloadcatgirl-1e544ce482ba97f9c4e2be114f88f11e49bdbf1b.tar.gz
catgirl-1e544ce482ba97f9c4e2be114f88f11e49bdbf1b.zip
Implement source address selection
This commit adds a '-S' command line option and a "bind"
configuration file option to specify the source address to bind to when
connecting to the IRC server.
-rw-r--r--catgirl.16
-rw-r--r--chat.c7
-rw-r--r--chat.h2
-rw-r--r--irc.c35
4 files changed, 42 insertions, 8 deletions
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;