hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

serv.c (18736B)


      1 /*
      2  * src/serv.c from hirc
      3  *
      4  * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <stdlib.h>
     21 #include <string.h>
     22 #include <unistd.h>
     23 #include <errno.h>
     24 #include <netdb.h>
     25 #include <sys/types.h>
     26 #include <sys/socket.h>
     27 #include <poll.h>
     28 #ifdef TLS
     29 #include <tls.h>
     30 #endif /* TLS */
     31 #include "hirc.h"
     32 
     33 /* 1024 is enough to fit two max-length messages in the buffer at once.
     34  * To stress-test this, it is possible to set it to 2 as the lowest value.
     35  * (The last byte is reserved for '\0', so a value of 1 will act weird). */
     36 #define INPUT_BUF_MIN 1024
     37 
     38 void
     39 serv_free(struct Server *server) {
     40 	struct Support *sp, *sprev;
     41 	struct Schedule *ep, *eprev;
     42 
     43 	if (!server)
     44 		return;
     45 
     46 	pfree(&server->name);
     47 	pfree(&server->username);
     48 	pfree(&server->realname);
     49 	pfree(&server->password);
     50 	pfree(&server->host);
     51 	pfree(&server->port);
     52 	pfree(&server->rpollfd);
     53 	nick_free(server->self);
     54 	hist_free_list(server->history);
     55 	chan_free_list(&server->channels);
     56 	chan_free_list(&server->queries);
     57 	sprev = server->supports;
     58 	if (sprev)
     59 		sp = sprev->next;
     60 	while (sprev) {
     61 		pfree(&sprev->key);
     62 		pfree(&sprev->value);
     63 		pfree(&sprev);
     64 		sprev = sp;
     65 		if (sp)
     66 			sp = sp->next;
     67 	}
     68 	eprev = server->schedule;
     69 	if (eprev)
     70 		ep = eprev->next;
     71 	while (eprev) {
     72 		pfree(&eprev->msg);
     73 		pfree(&eprev);
     74 		eprev = ep;
     75 		if (ep)
     76 			ep = ep->next;
     77 	}
     78 #ifdef TLS
     79 	if (server->tls)
     80 		if (server->tls_ctx)
     81 			tls_free(server->tls_ctx);
     82 #endif /* TLS */
     83 	pfree(&server);
     84 }
     85 
     86 struct Server *
     87 serv_create(char *name, char *host, char *port, char *nick, char *username,
     88 		char *realname, char *password, int tls, int tls_verify) {
     89 	struct Server *server;
     90 	int i;
     91 
     92 	assert_warn(name && host && port && nick, NULL);
     93 
     94 	server = emalloc(sizeof(struct Server));
     95 	server->prev = server->next = NULL;
     96 	server->wfd = server->rfd = -1;
     97 	server->input.size = INPUT_BUF_MIN;
     98 	server->input.pos = 0;
     99 	server->input.buf = emalloc(server->input.size);
    100 	server->rpollfd = emalloc(sizeof(struct pollfd));
    101 	server->rpollfd->fd = -1;
    102 	server->rpollfd->events = POLLIN;
    103 	server->rpollfd->revents = 0;
    104 	server->status = ConnStatus_notconnected;
    105 	server->name = estrdup(name);
    106 	server->username = username ? estrdup(username) : NULL;
    107 	server->realname = realname ? estrdup(realname) : NULL;
    108 	server->password = password ? estrdup(password) : NULL;
    109 	server->host = estrdup(host);
    110 	server->port = estrdup(port);
    111 	server->supports = NULL;
    112 	server->self = nick_create(nick, ' ', NULL);
    113 	server->self->self = 1;
    114 	server->history = emalloc(sizeof(struct HistInfo));
    115 	server->history->activity = Activity_none;
    116 	server->history->unread = server->history->ignored = 0;
    117 	server->history->server = server;
    118 	server->history->channel = NULL;
    119 	server->history->history = NULL;
    120 	server->channels = NULL;
    121 	server->queries = NULL;
    122 	server->schedule = NULL;
    123 	server->reconnect = 0;
    124 	for (i=0; i < Expect_last; i++)
    125 		server->expect[i] = NULL;
    126 	server->autocmds = NULL;
    127 	server->connectfail = 0;
    128 	server->lastconnected = server->lastrecv = server->pingsent = 0;
    129 
    130 #ifdef TLS
    131 	server->tls_verify = tls_verify;
    132 	server->tls = tls;
    133 	server->tls_ctx = NULL;
    134 #else
    135 	if (tls)
    136 		hist_format(server->history, Activity_error, HIST_SHOW,
    137 				"SELF_TLSNOTCOMPILED %s", server->name);
    138 #endif /* TLS */
    139 
    140 	return server;
    141 }
    142 
    143 void
    144 serv_update(struct Server *sp, char *nick, char *username,
    145 		char *realname, char *password, int tls, int tls_verify) {
    146 	assert_warn(sp,);
    147 	if (nick) {
    148 		pfree(&sp->self->nick);
    149 		sp->self->nick = estrdup(nick);
    150 	}
    151 	if (username) {
    152 		pfree(&sp->username);
    153 		sp->username = estrdup(nick);
    154 	}
    155 	if (realname) {
    156 		pfree(&sp->realname);
    157 		sp->username = estrdup(nick);
    158 	}
    159 	if (password) {
    160 		pfree(&sp->password);
    161 		sp->password = estrdup(password);
    162 	}
    163 #ifdef TLS
    164 	if (tls >= 0 && !sp->tls) {
    165 		sp->tls = tls;
    166 		if (strcmp(sp->port, "6667") == 0) {
    167 			pfree(&sp->port);
    168 			sp->port = estrdup("6697");
    169 		}
    170 	}
    171 	if (tls_verify >= 0)
    172 		sp->tls_verify = tls_verify;
    173 #endif /* TLS */
    174 }
    175 
    176 struct Server *
    177 serv_add(struct Server **head, char *name, char *host, char *port, char *nick,
    178 		char *username, char *realname, char *password, int tls, int tls_verify) {
    179 	struct Server *new, *p;
    180 
    181 	new = serv_create(name, host, port, nick, username, realname, password, tls, tls_verify);
    182 	assert_warn(new, NULL);
    183 
    184 	if (!*head) {
    185 		*head = new;
    186 		return new;
    187 	}
    188 
    189 	p = *head;
    190 	for (; p && p->next; p = p->next);
    191 	p->next = new;
    192 	new->prev = p;
    193 
    194 	return new;
    195 }
    196 
    197 struct Server *
    198 serv_get(struct Server **head, char *name) {
    199 	struct Server *p;
    200 
    201 	assert_warn(head && name, NULL);
    202 	if (!*head)
    203 		return NULL;
    204 
    205 	for (p = *head; p; p = p->next) {
    206 		if (strcmp(p->name, name) == 0)
    207 			return p;
    208 	}
    209 
    210 	return NULL;
    211 }
    212 
    213 int
    214 serv_remove(struct Server **head, char *name) {
    215 	struct Server *p;
    216 
    217 	assert_warn(head && name, -1);
    218 
    219 	if ((p = serv_get(head, name)) == NULL)
    220 		return 0;
    221 
    222 	if (*head == p)
    223 		*head = p->next;
    224 	if (p->next)
    225 		p->next->prev = p->prev;
    226 	if (p->prev)
    227 		p->prev->next = p->next;
    228 	serv_free(p);
    229 	return 1;
    230 }
    231 
    232 void
    233 serv_connect(struct Server *server) {
    234 	struct tls_config *tls_conf;
    235 	struct Support *s, *prev;
    236 	struct addrinfo hints;
    237 	struct addrinfo *ai = NULL;
    238 	int fd, ret;
    239 
    240 	assert_warn(server,);
    241 
    242 	if (server->status != ConnStatus_notconnected) {
    243 		ui_error("server '%s' is already connected", server->name);
    244 		return;
    245 	}
    246 
    247 	for (s = server->supports, prev = NULL; s; s = s->next) {
    248 		if (prev) {
    249 			pfree(&prev->key);
    250 			pfree(&prev->value);
    251 			pfree(&prev);
    252 		}
    253 		prev = s;
    254 	}
    255 	server->supports = NULL;
    256 	support_set(server, "CHANTYPES", config_gets("def.chantypes"));
    257 	support_set(server, "PREFIX", config_gets("def.prefixes"));
    258 
    259 	server->status = ConnStatus_connecting;
    260 	hist_format(server->history, Activity_status, HIST_SHOW|HIST_MAIN,
    261 			"SELF_CONNECTING %s %s", server->host, server->port);
    262 
    263 	memset(&hints, 0, sizeof(hints));
    264 	hints.ai_family = AF_UNSPEC;
    265 	hints.ai_socktype = SOCK_STREAM;
    266 
    267 	if ((ret = getaddrinfo(server->host, server->port, &hints, &ai)) != 0 || ai == NULL) {
    268 		hist_format(server->history, Activity_error, HIST_SHOW,
    269 				"SELF_LOOKUPFAIL %s %s %s :%s",
    270 				server->name, server->host, server->port, gai_strerror(ret));
    271 		goto fail;
    272 	}
    273 	if ((fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) == -1 || connect(fd, ai->ai_addr, ai->ai_addrlen) == -1) {
    274 		hist_format(server->history, Activity_error, HIST_SHOW,
    275 				"SELF_CONNECTFAIL %s %s %s :%s",
    276 				server->name, server->host, server->port, strerror(errno));
    277 		goto fail;
    278 	}
    279 
    280 	server->rfd = server->wfd = fd;
    281 	hist_format(server->history, Activity_status, HIST_SHOW|HIST_MAIN,
    282 			"SELF_CONNECTED %s %s %s", server->name, server->host, server->port);
    283 
    284 #ifdef TLS
    285 	if (server->tls) {
    286 		if (server->tls_ctx)
    287 			tls_free(server->tls_ctx);
    288 		server->tls_ctx = NULL;
    289 
    290 		if ((tls_conf = tls_config_new()) == NULL) {
    291 			ui_tls_config_error(tls_conf, "tls_config_new()");
    292 			goto fail;
    293 		}
    294 
    295 		if (!server->tls_verify) {
    296 			tls_config_insecure_noverifycert(tls_conf);
    297 			tls_config_insecure_noverifyname(tls_conf);
    298 		}
    299 
    300 		if ((server->tls_ctx = tls_client()) == NULL) {
    301 			ui_perror("tls_client()");
    302 			goto fail;
    303 		}
    304 
    305 		if (tls_configure(server->tls_ctx, tls_conf) == -1) {
    306 			ui_tls_error(server->tls_ctx, "tls_configure()");
    307 			goto fail;
    308 		}
    309 
    310 		if (tls_connect_socket(server->tls_ctx, fd, server->host) == -1) {
    311 			hist_format(server->history, Activity_error, HIST_SHOW,
    312 					"SELF_CONNECTLOST %s %s %s :%s",
    313 					server->name, server->host, server->port, tls_error(server->tls_ctx));
    314 			goto fail;
    315 		}
    316 
    317 		if (tls_handshake(server->tls_ctx) == -1) {
    318 			hist_format(server->history, Activity_error, HIST_SHOW,
    319 					"SELF_CONNECTLOST %s %s %s :%s",
    320 					server->name, server->host, server->port, tls_error(server->tls_ctx));
    321 			goto fail;
    322 		}
    323 
    324 		tls_config_free(tls_conf);
    325 
    326 		if (tls_peer_cert_provided(server->tls_ctx)) {
    327 			hist_format(server->history, Activity_status, HIST_SHOW,
    328 					"SELF_TLS_VERSION %s %s %d %s",
    329 					server->name, tls_conn_version(server->tls_ctx),
    330 					tls_conn_cipher_strength(server->tls_ctx),
    331 					tls_conn_cipher(server->tls_ctx));
    332 			hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_SNI %s :%s",
    333 					server->name, tls_conn_servername(server->tls_ctx));
    334 			hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_ISSUER %s :%s",
    335 					server->name, tls_peer_cert_issuer(server->tls_ctx));
    336 			hist_format(server->history, Activity_status, HIST_SHOW, "SELF_TLS_SUBJECT %s :%s",
    337 					server->name, tls_peer_cert_subject(server->tls_ctx));
    338 		}
    339 	}
    340 #endif /* TLS */
    341 
    342 	freeaddrinfo(ai);
    343 	server->connectfail = 0;
    344 
    345 	if (server->password)
    346 		serv_write(server, Sched_now, "PASS %s\r\n", server->password);
    347 	serv_write(server, Sched_now, "NICK %s\r\n", server->self->nick);
    348 	serv_write(server, Sched_now, "USER %s * * :%s\r\n",
    349 			server->username ? server->username : server->self->nick,
    350 			server->realname ? server->realname : server->self->nick);
    351 
    352 	return;
    353 
    354 fail:
    355 	serv_disconnect(server, 1, NULL);
    356 	if (server->connectfail * config_getl("reconnect.interval") < config_getl("reconnect.maxinterval"))
    357 		server->connectfail += 1;
    358 	if (ai)
    359 		freeaddrinfo(ai);
    360 }
    361 
    362 void
    363 serv_read(struct Server *sp) {
    364 	char *line, *end;
    365 	char *err;
    366 	char *reason = NULL;
    367 	size_t len;
    368 	int ret;
    369 
    370 	assert_warn(sp,);
    371 
    372 #ifdef TLS
    373 	if (sp->tls) {
    374 		switch (ret = tls_read(sp->tls_ctx, &sp->input.buf[sp->input.pos], sp->input.size - sp->input.pos - 1)) {
    375 		case -1:
    376 			err = (char *)tls_error(sp->tls_ctx);
    377 			len = CONSTLEN("tls_read(): ") + strlen(err) + 1;
    378 			reason = smprintf(len, "tls_read(): %s", err);
    379 			/* fallthrough */
    380 		case 0:
    381 			serv_disconnect(sp, 1, "EOF");
    382 			hist_format(sp->history, Activity_error, HIST_SHOW,
    383 					"SELF_CONNECTLOST %s %s %s :%s",
    384 					sp->name, sp->host, sp->port, reason ? reason : "connection closed");
    385 			pfree(&reason);
    386 			return;
    387 		case TLS_WANT_POLLIN:
    388 		case TLS_WANT_POLLOUT:
    389 			return;
    390 		default:
    391 			sp->input.pos += ret;
    392 			break;
    393 		}
    394 	} else {
    395 #endif /* TLS */
    396 		switch (ret = read(sp->rfd, &sp->input.buf[sp->input.pos], sp->input.size - sp->input.pos - 1)) {
    397 		case -1:
    398 			err = estrdup(strerror(errno));
    399 			len = CONSTLEN("read(): ") + strlen(err) + 1;
    400 			reason = smprintf(len, "read(): %s", err);
    401 			pfree(&err);
    402 			/* fallthrough */
    403 		case 0:
    404 			serv_disconnect(sp, 1, "EOF");
    405 			hist_format(sp->history, Activity_error, HIST_SHOW,
    406 					"SELF_CONNECTLOST %s %s %s :%s",
    407 					sp->name, sp->host, sp->port, reason ? reason : "connection closed");
    408 			pfree(&reason);
    409 			return;
    410 		default:
    411 			sp->input.pos += ret;
    412 			break;
    413 		}
    414 #ifdef TLS
    415 	}
    416 #endif /* TLS */
    417 
    418 	sp->input.buf[sp->input.size - 1] = '\0';
    419 	line = sp->input.buf;
    420 	while ((end = strstr(line, "\r\n"))) {
    421 		*end = '\0';
    422 		handle(sp, line);
    423 		line = end + 2;
    424 	}
    425 
    426 	sp->input.pos -= line - sp->input.buf;
    427 	memmove(sp->input.buf, line, sp->input.pos);
    428 
    429 	/* Shrink and grow buffer as needed
    430 	 * If we didn't read everything, serv_read will be called
    431 	 * again in the main loop as poll gives another POLLIN. */
    432 	if (sp->input.pos + ret > sp->input.size / 2) {
    433 		sp->input.size *= 2;
    434 		sp->input.buf = erealloc(sp->input.buf, sp->input.size);
    435 	} else if (sp->input.pos + ret < sp->input.size / 2 && sp->input.size != INPUT_BUF_MIN) {
    436 		sp->input.size /= 2;
    437 		sp->input.buf = erealloc(sp->input.buf, sp->input.size);
    438 	}
    439 }
    440 
    441 int
    442 serv_write(struct Server *server, enum Sched when, char *format, ...) {
    443 	char msg[512];
    444 	va_list ap;
    445 	int ret;
    446 
    447 	assert_warn(server && server->status != ConnStatus_notconnected, -1);
    448 
    449 	va_start(ap, format);
    450 	ret = vsnprintf(msg, sizeof(msg), format, ap);
    451 	va_end(ap);
    452 
    453 	assert_warn(ret >= 0, -1);
    454 
    455 	if (when != Sched_now) {
    456 		switch (when) {
    457 		case Sched_connected:
    458 			if (server->status == ConnStatus_connected)
    459 				goto write;
    460 			break;
    461 		}
    462 		schedule(server, when, msg);
    463 		return 0;
    464 	}
    465 
    466 write:
    467 
    468 #ifdef TLS
    469 	if (server->tls)
    470 		do {
    471 			ret = tls_write(server->tls_ctx, msg, strlen(msg));
    472 		} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
    473 	else
    474 #endif /* TLS */
    475 		ret = write(server->wfd, msg, strlen(msg));
    476 
    477 	if (ret == -1 && server->status == ConnStatus_connected) {
    478 		serv_disconnect(server, 1, NULL);
    479 		hist_format(server->history, Activity_error, HIST_SHOW,
    480 				"SELF_CONNECTLOST %s %s %s :%s",
    481 				server->name, server->host, server->port, strerror(errno));
    482 	} else if (ret == -1 && server->status != ConnStatus_connecting) {
    483 		ui_error("Not connected to server '%s'", server->name);
    484 	}
    485 
    486 	return ret;
    487 }
    488 
    489 
    490 int
    491 serv_len(struct Server **head) {
    492 	struct Server *p;
    493 	int i;
    494 
    495 	for (p = *head, i = 0; p; p = p->next)
    496 		i++;
    497 	return i;
    498 }
    499 
    500 int
    501 serv_poll(struct Server **head, int timeout) {
    502 	struct pollfd fds[64];
    503 	struct Server *sp;
    504 	int i, ret;
    505 
    506 	for (i=0, sp = *head; sp; sp = sp->next, i++) {
    507 		sp->rpollfd->fd = sp->rfd;
    508 		fds[i].fd = sp->rpollfd->fd;
    509 		fds[i].events = POLLIN;
    510 	}
    511 
    512 	ret = poll(fds, serv_len(head), timeout);
    513 	if (errno == EINTR) /* ncurses issue */
    514 		ret = 0;
    515 
    516 	for (i=0, sp = *head; sp; sp = sp->next, i++)
    517 		if (sp->status == ConnStatus_connecting
    518 				|| sp->status == ConnStatus_connected)
    519 			sp->rpollfd->revents = fds[i].revents;
    520 
    521 	return ret;
    522 }
    523 
    524 void
    525 serv_disconnect(struct Server *server, int reconnect, char *msg) {
    526 	struct Channel *chan;
    527 	int ret;
    528 
    529 	if (msg)
    530 		serv_write(server, Sched_now, "QUIT :%s\r\n", msg);
    531 #ifdef TLS
    532 	if (server->tls) {
    533 		if (server->tls_ctx) {
    534 			do {
    535 				ret = tls_close(server->tls_ctx);
    536 			} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
    537 			tls_free(server->tls_ctx);
    538 		}
    539 		server->tls_ctx = NULL;
    540 	} else {
    541 #endif /* TLS */
    542 		shutdown(server->rfd, SHUT_RDWR);
    543 		shutdown(server->wfd, SHUT_RDWR);
    544 		close(server->rfd);
    545 		close(server->wfd);
    546 #ifdef TLS
    547 	}
    548 #endif /* TLS */
    549 
    550 	server->rfd = server->wfd = server->rpollfd->fd = -1;
    551 	server->status = ConnStatus_notconnected;
    552 	server->lastrecv = server->pingsent = 0;
    553 	server->lastconnected = time(NULL);
    554 	server->reconnect = reconnect;
    555 
    556 	/* Create a history item for disconnect:
    557 	 *  - shows up in the log
    558 	 *  - updates the file's mtime, so hist_laodlog knows when we disconnected */
    559 	hist_format(server->history, Activity_none, HIST_LOG, "SELF_DISCONNECT");
    560 	for (chan = server->channels; chan; chan = chan->next) {
    561 		chan_setold(chan, 1);
    562 		hist_format(chan->history, Activity_none, HIST_LOG, "SELF_DISCONNECT");
    563 	}
    564 
    565 	windows[Win_buflist].refresh = 1;
    566 }
    567 
    568 int
    569 serv_selected(struct Server *server) {
    570 	if (!selected.channel && selected.server == server)
    571 		return 1;
    572 	else
    573 		return 0;
    574 }
    575 
    576 char *
    577 support_get(struct Server *server, char *key) {
    578 	struct Support *p;
    579 	for (p = server->supports; p; p = p->next)
    580 		if (strcmp(p->key, key) == 0)
    581 			return p->value;
    582 
    583 	return NULL;
    584 }
    585 
    586 void
    587 support_set(struct Server *server, char *key, char *value) {
    588 	struct Support *p;
    589 
    590 	assert_warn(server,);
    591 
    592 	if (!server->supports) {
    593 		server->supports = emalloc(sizeof(struct Support));
    594 		server->supports->prev = server->supports->next = NULL;
    595 		server->supports->key = key ? strdup(key) : NULL;
    596 		server->supports->value = value ? strdup(value) : NULL;
    597 		return;
    598 	}
    599 
    600 	for (p = server->supports; p && p->next; p = p->next) {
    601 		if (strcmp(p->key, key) == 0) {
    602 			pfree(&p->value);
    603 			p->value = strdup(value);
    604 			return;
    605 		}
    606 	}
    607 
    608 	p->next = emalloc(sizeof(struct Support));
    609 	p->next->prev = p;
    610 	p->next->next = NULL;
    611 	p->next->key = key ? strdup(key) : NULL;
    612 	p->next->value = value ? strdup(value) : NULL;
    613 }
    614 
    615 int
    616 serv_ischannel(struct Server *server, char *str) {
    617 	char *chantypes;
    618 
    619 	assert_warn(str && server,0);
    620 
    621 	chantypes = support_get(server, "CHANTYPES");
    622 	if (!chantypes)
    623 		chantypes = config_gets("def.chantypes");
    624 	assert(chantypes != NULL);
    625 
    626 	return strchr(chantypes, *str) != NULL;
    627 }
    628 
    629 void
    630 serv_auto_add(struct Server *server, char *cmd) {
    631 	char **p;
    632 	size_t len;
    633 
    634 	assert_warn(server && cmd,);
    635 
    636 	if (!server->autocmds) {
    637 		len = 1;
    638 		server->autocmds = emalloc(sizeof(char *) * (len + 1));
    639 	} else {
    640 		for (p = server->autocmds, len = 1; *p; p++)
    641 			len++;
    642 		server->autocmds = erealloc(server->autocmds, sizeof(char *) * (len + 1));
    643 	}
    644 
    645 	*(server->autocmds + len - 1) = estrdup(cmd);
    646 	*(server->autocmds + len) = NULL;
    647 }
    648 
    649 void
    650 serv_auto_free(struct Server *server) {
    651 	char **p;
    652 
    653 	if (!server || !server->autocmds)
    654 		return;
    655 
    656 	for (p = server->autocmds; *p; p++)
    657 		pfree(&*p);
    658 	pfree(&server->autocmds);
    659 	server->autocmds = NULL;
    660 }
    661 
    662 void
    663 serv_auto_send(struct Server *server) {
    664 	char **p;
    665 	int save;
    666 
    667 	if (!server || !server->autocmds)
    668 		return;
    669 
    670 	save = nouich;
    671 	nouich = 1;
    672 	for (p = server->autocmds; *p; p++)
    673 		command_eval(server, *p);
    674 	nouich = save;
    675 }
    676 
    677 /* check if autocmds has '/join <chan>' */
    678 int
    679 serv_auto_haschannel(struct Server *server, char *chan) {
    680 	char **p;
    681 
    682 	if (!server || !server->autocmds)
    683 		return 0;
    684 
    685 	for (p = server->autocmds; *p; p++)
    686 		if (strncmp(*p, "/join ", CONSTLEN("/join ")) == 0 &&
    687 				strcmp((*p) + CONSTLEN("/join "), chan) == 0)
    688 			return 1;
    689 	return 0;
    690 }
    691 
    692 void
    693 schedule(struct Server *server, enum Sched when, char *msg) {
    694 	struct Schedule *p;
    695 
    696 	assert_warn(server && msg,);
    697 
    698 	if (!server->schedule) {
    699 		server->schedule = emalloc(sizeof(struct Schedule));
    700 		server->schedule->prev = server->schedule->next = NULL;
    701 		server->schedule->when = when;
    702 		server->schedule->msg  = estrdup(msg);
    703 		return;
    704 	}
    705 
    706 	for (p = server->schedule; p && p->next; p = p->next);
    707 
    708 	p->next = emalloc(sizeof(struct Schedule));
    709 	p->next->prev = p;
    710 	p->next->next = NULL;
    711 	p->next->when = when;
    712 	p->next->msg  = estrdup(msg);
    713 }
    714 
    715 void
    716 schedule_send(struct Server *server, enum Sched when) {
    717 	struct Schedule *p;
    718 
    719 	assert_warn(server,);
    720 
    721 	for (p = server->schedule; p; p = p->next) {
    722 		if (p->when == when) {
    723 			serv_write(server, Sched_now, "%s", p->msg);
    724 
    725 			if (p->prev) p->prev->next = p->next;
    726 			if (p->next) p->next->prev = p->prev;
    727 
    728 			if (!p->prev)
    729 				server->schedule = p->next;
    730 
    731 			pfree(&p->msg);
    732 			pfree(&p);
    733 		}
    734 	}
    735 }
    736 
    737 void
    738 expect_set(struct Server *server, enum Expect cmd, char *about) {
    739 	if (cmd >= Expect_last || cmd < 0 || nouich)
    740 		return;
    741 
    742 	pfree(&server->expect[cmd]);
    743 	server->expect[cmd] = about ? estrdup(about) : NULL;
    744 }
    745 
    746 char *
    747 expect_get(struct Server *server, enum Expect cmd) {
    748 	if (cmd >= Expect_last || cmd < 0)
    749 		return NULL;
    750 	else
    751 		return server->expect[cmd];
    752 }