2 * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include <arpa/inet.h>
29 #include <netinet/tcp.h>
48 typedef struct irc_server_t {
63 char out_buf[BUF_LEN];
67 struct irc_server_t *next;
70 typedef struct irc_channel_t {
75 struct irc_channel_t *next;
79 static irc_server_t *servers;
80 static irc_channel_t *channels;
83 static irc_server_t *find_server(const char *name, int create)
85 irc_server_t *cur = NULL, *last = NULL;
86 for (cur = servers; cur; last = cur, cur = cur->next)
87 if (match(cur->name, name))
90 cur = new0(irc_server_t);
91 cur->name = get_name(name);
100 static irc_channel_t *find_channel(const char *name, int create)
102 irc_channel_t *cur = NULL, *last = NULL;
103 for (cur = channels; cur; last = cur, cur = cur->next)
104 if (match(cur->name, name))
106 if (!cur && create) {
107 cur = new0(irc_channel_t);
108 cur->name = get_name(name);
117 static int send_line(irc_server_t *srv, const char *fmt, ...)
125 len = vsnprintf(srv->out_buf, BUF_LEN, fmt, ap);
131 srv->out_buf[len] = '\n';
134 sent = send(srv->poll.fd, srv->out_buf, len, 0);
143 static void recv_line(irc_server_t *srv, const char *line)
145 static char src[BUF_LEN]; // (:([^ ]+) +)?
146 static char cmd[BUF_LEN]; // (([A-Z0-9]+) +)
147 static char dst[BUF_LEN]; // (([^ ]+)[= ]+)?
148 static char arg[BUF_LEN]; // (([^: ]+) *)?
149 static char msg[BUF_LEN]; // (:(.*))?
150 static char from[BUF_LEN];
153 const char *c = line;
156 src[0] = cmd[0] = dst[0] = arg[0] = msg[0] = from[0] = '\0';
161 for (i = 0; *c && *c != ' '; i++)
166 for (i = 0; src[i] && src[i] != '!' && src[i] != ' '; i++)
172 for (i = 0; isalnum(*c); i++)
179 if ((*c && *c != ' ') &&
180 (strchr(c+1, ' ') || strchr(c+1, '='))) {
181 for (i = 0; *c && *c != ' '; i++)
183 while (*c == '=' || *c == ' ')
189 if (*c && *c != ':' && *c != ' ') {
190 for (i = 0; *c && *c != ' '; i++)
205 debug("got line: %s", line);
206 //debug(" src %s", src);
207 //debug(" cmd %s", cmd);
208 //debug(" dst %s", dst);
209 //debug(" arg %s", arg);
210 //debug(" msg %s", msg);
213 if (match(cmd, "001") || strstr(msg, "Welcome"))
214 srv->state = IRC_JOIN;
215 if (match(cmd, "PING"))
216 send_line(srv, "PING %s", msg);
217 if (match(cmd, "PRIVMSG"))
218 chat_recv(dst, from, msg);
221 static void on_poll(void *_srv)
223 static char buf[BUF_LEN];
224 static char hostname[512];
226 irc_server_t *srv = _srv;
230 len = recv(srv->poll.fd, buf, sizeof(buf), 0);
232 debug("recv: error: %s -- %s", srv->name, strerror(errno));
235 debug("recv: close: %s", srv->name);
236 srv->state = IRC_DEAD;
237 poll_del(&srv->poll);
241 debug("recv: ready: %s -- [%.*s]", srv->name, len, buf);
243 for (int i = 0; i < len; i++) {
244 if (srv->in_len < BUF_LEN) {
245 srv->in_buf[srv->in_len] = buf[i];
248 if (buf[i] == '\n' || buf[i] == '\r') {
249 srv->in_buf[srv->in_len-1] = '\0';
251 recv_line(srv, srv->in_buf);
257 if (srv->out_len > 0) {
258 len = send(srv->poll.fd, &srv->out_buf[srv->out_pos],
259 srv->out_len-srv->out_pos, 0);
262 if (srv->out_pos == srv->out_len) {
270 socklen_t elen = sizeof(err);
271 if (getsockopt(srv->poll.fd, SOL_SOCKET, SO_ERROR, &err, &elen))
272 error("Error getting socket opt");
274 debug("disconnect: %s -- %s", srv->name, strerror(err));
275 srv->state = IRC_DEAD;
276 poll_del(&srv->poll);
281 if (srv->state == IRC_USER) {
282 if (gethostname(hostname, sizeof(hostname)))
283 error("Error getting hostname");
284 if (send_line(srv, "USER %s %s %s :%s",
285 getenv("USER") ?: "lameuser",
286 hostname, srv->host, srv->nick))
287 srv->state = IRC_NICK;
289 if (srv->state == IRC_NICK) {
290 if (send_line(srv, "NICK %s", srv->nick))
291 srv->state = IRC_READY;
293 if (srv->state == IRC_JOIN) {
294 for (irc_channel_t *chan = channels; chan; chan = chan->next)
296 send_line(srv, "JOIN %s", chan->channel);
297 srv->state = IRC_READY;
300 /* Enable output poll */
302 poll_ctl(&srv->poll, 1, 1, 1);
304 poll_ctl(&srv->poll, 1, 0, 1);
307 static void irc_connect(irc_server_t *srv)
310 struct addrinfo *addrs = NULL;
311 struct addrinfo hints = {};
314 snprintf(service, sizeof(service), "%d", srv->port);
315 hints.ai_family = AF_INET;
316 hints.ai_socktype = SOCK_STREAM;
319 if (getaddrinfo(srv->host, service, &hints, &addrs))
320 error("Error getting address info");
322 if ((sock = socket(addrs->ai_family,
324 addrs->ai_protocol)) < 0)
325 error("Error opening irc socket");
327 if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
328 error("Error getting irc socket flags");
330 if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
331 error("Error setting irc socket non-blocking");
333 if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
334 if (errno != EINPROGRESS)
335 error("Error connecting socket");
340 srv->state = IRC_USER;
341 poll_add(&srv->poll, sock, on_poll, srv);
347 for (irc_server_t *cur = servers; cur; cur = cur->next) {
348 if (!match(cur->protocol, "irc"))
353 cur->nick = strcopy(getenv("USER"));
355 cur->nick = strcopy("lameuser");
361 void irc_config(const char *group, const char *name, const char *key, const char *value)
366 if (match(group, "server")) {
367 srv = find_server(name, 1);
368 if (match(key, "protocol") &&
370 srv->protocol = get_string(value);
371 if (match(srv->protocol, "irc")) {
372 if (match(key, "connect"))
373 srv->connect = get_bool(value);
374 else if (match(key, "host"))
375 srv->host = get_string(value);
376 else if (match(key, "port"))
377 srv->port = get_number(value);
378 else if (match(key, "nick"))
379 srv->nick = get_string(value);
380 else if (match(key, "auth"))
381 srv->auth = get_string(value);
382 else if (match(key, "pass"))
383 srv->pass = get_string(value);
385 } else if (match(group, "channel")) {
386 chan = find_channel(name, 1);
387 if (match(key, "server") &&
388 find_server(value, 0))
389 chan->server = get_string(value);
391 if (match(key, "channel"))
392 chan->channel = get_string(value);
393 else if (match(key, "join"))
394 chan->join = get_bool(value);
399 void irc_send(const char *channel, const char *msg)
401 irc_channel_t *chan = find_channel(channel, 0);
404 irc_server_t *srv = find_server(chan->server, 0);
407 send_line(srv, "PRIVMSG %s :%s", chan->channel, msg);