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(int fd, irc_server_t *srv)
223 static char buf[BUF_LEN];
224 static char hostname[512];
228 len = recv(fd, buf, sizeof(buf), 0);
229 for (int i = 0; i < len; i++) {
230 if (srv->in_len < BUF_LEN) {
231 srv->in_buf[srv->in_len] = buf[i];
234 if (buf[i] == '\n' || buf[i] == '\r') {
235 srv->in_buf[srv->in_len-1] = '\0';
237 recv_line(srv, srv->in_buf);
243 if (srv->out_len > 0) {
244 len = send(fd, &srv->out_buf[srv->out_pos],
245 srv->out_len-srv->out_pos, 0);
248 if (srv->out_pos == srv->out_len) {
256 socklen_t elen = sizeof(err);
257 if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &elen))
258 error("Error getting socket opt");
260 debug("disconnect: %s -- %s", srv->name, strerror(err));
261 srv->state = IRC_DEAD;
262 poll_del(&srv->poll);
267 if (srv->state == IRC_USER) {
268 if (gethostname(hostname, sizeof(hostname)))
269 error("Error getting hostname");
270 if (send_line(srv, "USER %s %s %s :%s",
271 getenv("USER") ?: "lameuser",
272 hostname, srv->host, srv->nick))
273 srv->state = IRC_NICK;
275 if (srv->state == IRC_NICK) {
276 if (send_line(srv, "NICK %s", srv->nick))
277 srv->state = IRC_READY;
279 if (srv->state == IRC_JOIN) {
280 for (irc_channel_t *chan = channels; chan; chan = chan->next)
282 send_line(srv, "JOIN %s", chan->channel);
283 srv->state = IRC_READY;
286 /* Enable output poll */
288 poll_ctl(&srv->poll, 1, 1, 1);
290 poll_ctl(&srv->poll, 1, 0, 1);
293 static void irc_connect(irc_server_t *srv)
296 struct addrinfo *addrs = NULL;
297 struct addrinfo hints = {};
300 snprintf(service, sizeof(service), "%d", srv->port);
301 hints.ai_family = AF_INET;
302 hints.ai_socktype = SOCK_STREAM;
305 if (getaddrinfo(srv->host, service, &hints, &addrs))
306 error("Error getting address info");
308 if ((sock = socket(addrs->ai_family,
310 addrs->ai_protocol)) < 0)
311 error("Error opening web socket");
313 if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
314 error("Error getting web socket flags");
316 if (fcntl(sock, F_SETFL, flags|O_NONBLOCK) < 0)
317 error("Error setting web socket non-blocking");
319 if (connect(sock, addrs->ai_addr, addrs->ai_addrlen) < 0)
320 if (errno != EINPROGRESS)
321 error("Error connecting socket");
326 srv->state = IRC_USER;
327 poll_add(&srv->poll, sock, (cb_t)on_poll, srv);
333 for (irc_server_t *cur = servers; cur; cur = cur->next) {
334 if (!match(cur->protocol, "irc"))
339 cur->nick = strcopy(getenv("USER"));
341 cur->nick = strcopy("lameuser");
347 void irc_config(const char *group, const char *name, const char *key, const char *value)
352 if (match(group, "server")) {
353 srv = find_server(name, 1);
354 if (match(key, "protocol") &&
356 srv->protocol = get_string(value);
357 if (match(srv->protocol, "irc")) {
358 if (match(key, "connect"))
359 srv->connect = get_bool(value);
360 else if (match(key, "host"))
361 srv->host = get_string(value);
362 else if (match(key, "port"))
363 srv->port = get_number(value);
364 else if (match(key, "nick"))
365 srv->nick = get_string(value);
366 else if (match(key, "auth"))
367 srv->auth = get_string(value);
368 else if (match(key, "pass"))
369 srv->pass = get_string(value);
371 } else if (match(group, "channel")) {
372 chan = find_channel(name, 1);
373 if (match(key, "server") &&
374 find_server(value, 0))
375 chan->server = get_string(value);
377 if (match(key, "channel"))
378 chan->channel = get_string(value);
379 else if (match(key, "join"))
380 chan->join = get_bool(value);
385 void irc_send(const char *channel, const char *msg)
387 irc_channel_t *chan = find_channel(channel, 0);
390 irc_server_t *srv = find_server(chan->server, 0);
393 send_line(srv, "PRIVMSG %s :%s", chan->channel, msg);