/* * $Id: bip.c,v 1.39 2005/04/21 06:58:50 nohar Exp $ * * This file is part of the bip project * Copyright (C) 2004 2005 Arnaud Cornet and Loïc Gomez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * See the file "COPYING" for the exact licensing terms. */ #include "config.h" #include #include #include #include #include #include "irc.h" #include "conf.h" #include "tuple.h" #include "log.h" #include "irc.h" #include "bip.h" #include "line.h" int sighup = 0; char *conf_log_root; char *conf_log_format; int conf_log_level; char *conf_ip; unsigned short conf_port; int conf_css; #ifdef HAVE_LIBSSL char *conf_ssl_certfile; #endif int conf_daemonize; char *conf_pid_file; hash_t conf_networks; hash_t conf_users; char *conf_biphome; hash_t adm_users; /* log options, for sure the trickiest :) */ /* no backlog at all */ int conf_backlog; int conf_memlog; int conf_log; /* number of lines in backlog */ int conf_backlog_lines = 10; /* backlog even lines already backlogged */ int conf_always_backlog; int conf_log_sync_interval; int conf_blreset_on_talk = 0; list_t *parse_conf(FILE *file); static void conf_die(char *fmt, ...); #ifdef HAVE_LIBSSL int adm_trust(struct link_client *ic, struct line *line); #endif static void hash_binary(char *hex, unsigned char **password, unsigned int *seed) { unsigned char *md5; unsigned int buf; int i; if (strlen(hex) != 40) fatal("Incorrect password format %s\n", hex); md5 = malloc(20); for (i = 0; i < 20; i++) { sscanf(hex + 2 * i, "%02x", &buf); md5[i] = buf; } *seed = 0; sscanf(hex, "%02x", &buf); *seed |= buf << 24; sscanf(hex + 2, "%02x", &buf); *seed |= buf << 16; sscanf(hex + 2 * 2, "%02x", &buf); *seed |= buf << 8; sscanf(hex + 2 * 3, "%02x", &buf); *seed |= buf; *password = md5; } void server_free(struct server *s) { free(s->host); free(s); } static int add_server(list_t *serverl, list_t *data) { struct tuple *t; struct server *s; s = calloc(sizeof(struct server), 1); s->port = 6667; /* default port */ while ((t = list_remove_first(data))) { switch (t->type) { case LEX_HOST: s->host = t->pdata; break; case LEX_PORT: s->port = t->ndata; break; default: fatal("Config error in server block (%d)", t->type); } } if (!s->host) { free(s); conf_die("Server conf: host not set"); return 0; } list_add_last(serverl, s); return 1; } #define ERRBUFSZ 128 extern list_t *root_list; int yyparse(); int conf_error; char conf_errstr[ERRBUFSZ]; static void conf_start(void) { conf_error = 0; } static void conf_die(char *fmt, ...) { va_list ap; va_start(ap, fmt); vsnprintf(conf_errstr, ERRBUFSZ, fmt, ap); conf_errstr[ERRBUFSZ - 1] = 0; conf_error = 1; va_end(ap); } FILE *conf_global_log_file; static pid_t daemonize(void) { char buf[4096]; switch (fork()) { case -1: fatal("Fork failed"); break; case 0: break; default: exit(0); } if (setsid() < 0) fatal("setsid() failed"); if (conf_log) { snprintf(buf, 4095, "%s/bip.log", conf_log_root); FILE *f = fopen(buf, "a"); if (!f) fatal("Can't open %s: %s", buf, strerror(errno)); conf_global_log_file = f; } else { conf_global_log_file = stderr; } close(0); close(1); close(2); /* This better be the very last action since fatal makes use of * conf_global_log_file */ return getpid(); } /* RACE CONDITION! */ int do_pid_stuff(void) { char hname[1024]; char longpath[1024]; FILE *f; try_again: f = fopen(conf_pid_file, "r"); if (f) goto pid_is_there; if (gethostname(hname, 1023) == -1) fatal("%s %s", "gethostname", strerror(errno)); snprintf(longpath, 1023, "%s.%s.%ld", conf_pid_file, hname, (long unsigned int)getpid()); int fd; if ((fd = open(longpath, O_CREAT|O_WRONLY, S_IWUSR|S_IRUSR)) == -1) fatal("%s %s", "open", strerror(errno)); if (link(longpath, conf_pid_file) == -1) { struct stat buf; if (stat(longpath, &buf) == -1) { if (buf.st_nlink != 2) { f = fopen(conf_pid_file, "r"); goto pid_is_there; } } } unlink(longpath); return fd; pid_is_there: { pid_t pid; long unsigned int p; if (f) { int c = fscanf(f, "%ld", &p); pid = p; if (c != 1 || p == 0) { mylog(LOG_INFO, "pid file found but invalid " "data inside. Continuing...\n"); if (unlink(conf_pid_file)) { fatal("Cannot delete pid file '%s', " "check permissions.\n", conf_pid_file); } goto try_again; } } else pid = 0; int kr = kill(pid, 0); if (kr == -1 && (errno == ESRCH || errno == EPERM)) { /* that's not bip! */ fclose(f); if (unlink(conf_pid_file)) { fatal("Cannot delete pid file '%s', check " "permissions.\n", conf_pid_file); } goto try_again; } if (pid) mylog(LOG_INFO, "pid file found (pid %ld).", pid); mylog(LOG_STD, "Another instance of bip is certainly runing."); mylog(LOG_STD, "If you are sure this is not the case remove" " %s.", conf_pid_file); exit(2); } return 0; } #define S_CONF "/.bip/bip.conf" static void usage(char *name) { printf( "Usage: %s [-f config_file] [-h] [-n]\n" " -f config_file: Use config_file as the configuration file\n" " If no config file is given %s will try to open ~" S_CONF "\n" " -n: Don't daemonize, log in stderr\n" " -h: This help\n", name, name); exit(1); } void reload_config(int i) { (void)i; sighup = 1; } extern list_t *_connections; void bad_quit(int i) { list_iterator_t it; for (list_it_init(_connections, &it); list_it_item(&it); list_it_next(&it)) { connection_t *c = list_it_item(&it); struct link_any *la = c->user_data; if (c->connected == CONN_OK && la && TYPE(la) == IRC_TYPE_SERVER) { write_line_fast(CONN(la), "QUIT :Coyote finally " "caught me\r\n"); } } unlink(conf_pid_file); exit(i); } void c_network_free(struct c_network *on) { struct server *s; free(on->name); s = list_remove_first(&on->serverl); free(s->host); free(on); } static int add_network(list_t *data) { struct tuple *t; struct c_network *n; struct c_network *old_n; int r; n = calloc(sizeof(struct c_network), 1); list_init(&n->serverl, NULL); while ((t = list_remove_first(data))) { switch (t->type) { case LEX_NAME: n->name = t->pdata; break; #ifdef HAVE_LIBSSL case LEX_SSL: n->ssl = t->ndata; break; #endif case LEX_SERVER: r = add_server(&n->serverl, t->pdata); free(t->pdata); if (!r) return 0; break; default: conf_die("unknown keyword in network statement"); if (t->type == TUPLE_STR) free(t->pdata); return 0; break; } free(t); } if (!n->name) { conf_die("Network with no name"); return 0; } old_n = hash_get(&conf_networks, n->name); if (old_n) { hash_remove(&conf_networks, n->name); c_network_free(old_n); } hash_insert(&conf_networks, n->name, n); return 1; } static int add_channel(list_t *channell, list_t *data) { struct tuple *t; struct c_channel *c; c = calloc(sizeof(struct c_channel), 1); while ((t = list_remove_first(data))) { switch (t->type) { case LEX_NAME: c->name = t->pdata; break; case LEX_KEY: c->key = t->pdata; break; default: conf_die("unknown keyword in channel statement"); if (t->type == TUPLE_STR) free(t->pdata); return 0; break; } free(t); } if (!c->name) conf_die("channel wo a name !"); list_add_last(channell, c); return 1; } void c_connection_free(struct c_connection *c) { /* XXX network free! */ free(c->user); free(c->password); free(c->vhost); struct c_channel *chan; while ((chan = list_remove_first(&c->channell))) { free(chan->name); if (chan->key) free(chan->key); free(chan); } free(c->away_nick); free(c->on_connect_send); } static int add_connection(list_t *connectionl, list_t *data, list_t *old_c_connl) { struct tuple *t; struct c_connection *c, *old_c = NULL; int r; c = calloc(sizeof(struct c_connection), 1); list_init(&c->channell, NULL); list_init(&c->on_connect_send, NULL); while ((t = list_remove_first(data))) { switch (t->type) { case LEX_NAME: c->name = t->pdata; break; case LEX_NETWORK: c->network = hash_get(&conf_networks, t->pdata); if (!c->network) { free(c); conf_die("networkd:%s used but not defined\n", t->pdata); return 0; } break; case LEX_NICK: if (!is_valid_nick(t->pdata)) conf_die("Invalid nickname (%s)", t->pdata); c->nick = t->pdata; break; case LEX_USER: c->user = t->pdata; break; case LEX_REALNAME: c->realname = t->pdata; break; case LEX_PASSWORD: c->password = t->pdata; break; case LEX_VHOST: c->vhost = t->pdata; break; case LEX_CHANNEL: r = add_channel(&c->channell, t->pdata); free(t->pdata); if (!r) return 0; break; case LEX_FOLLOW_NICK: c->follow_nick = t->ndata; break; case LEX_IGN_FIRST_NICK: c->ignore_first_nick = t->ndata; break; case LEX_AWAY_NICK: c->away_nick = t->pdata; break; case LEX_ON_CONNECT_SEND: list_add(&c->on_connect_send, t->pdata); break; default: conf_die("unknown keyword in connection statement"); if (t->type == TUPLE_STR) free(t->pdata); return 0; } free(t); } /* checks that can only be here, or must */ if (!c->network) { conf_die("Missing network in connection block"); return 0; } list_add_last(connectionl, c); if (old_c_connl) { old_c = list_remove_first(old_c_connl); if (old_c) c_connection_free(old_c); } return 1; } void c_user_free(struct c_user *cu) { free(cu->name); free(cu->password); #ifdef HAVE_LIBSSL free(cu->ssl_check_store); #endif struct c_connection *con; while ((con = list_remove_first(&cu->connectionl))) c_connection_free(con); free(cu); } static int add_user(list_t *data) { int r; struct tuple *t; struct c_user *u; struct c_user *old_u = 0; u = calloc(sizeof(struct c_user), 1); list_init(&u->connectionl, NULL); while ((t = list_remove_first(data))) { switch (t->type) { case LEX_NAME: u->name = t->pdata; old_u = hash_get(&conf_users, u->name); break; case LEX_PASSWORD: hash_binary(t->pdata, &u->password, &u->seed); free(t->pdata); break; case LEX_DEFAULT_NICK: u->default_nick = t->pdata; break; case LEX_DEFAULT_USER: u->default_user = t->pdata; break; case LEX_DEFAULT_REALNAME: u->default_realname = t->pdata; break; case LEX_CONNECTION: if (!u->name) conf_die("name statement must be first in user" "block"); if (!old_u) r = add_connection(&u->connectionl, t->pdata, NULL); else r = add_connection(&u->connectionl, t->pdata, &old_u->connectionl); free(t->pdata); if (!r) return 0; break; #ifdef HAVE_LIBSSL case LEX_SSL_CHECK_MODE: if (!strncmp(t->pdata, "basic", 5)) u->ssl_check_mode = SSL_CHECK_BASIC; if (!strncmp(t->pdata, "ca", 2)) u->ssl_check_mode = SSL_CHECK_CA; free(t->pdata); break; case LEX_SSL_CHECK_STORE: u->ssl_check_store = t->pdata; break; #endif default: conf_die("Uknown keyword in user statement"); if (t->type == TUPLE_STR) free(t->pdata); break; } free(t); } if (!u->name) { conf_die("User w/o a name!"); return 0; } if (!u->password) { conf_die("Missing password in user block"); return 0; } if (old_u) { hash_remove(&conf_users, u->name); c_user_free(old_u); } hash_insert(&conf_users, u->name, u); return 1; } int fireup(FILE *conf) { struct tuple *t; list_t *l; int r; conf_start(); l = parse_conf(conf); if (conf_error) return 0; while ((t = list_remove_first(l))) { switch (t->type) { case LEX_LOG_SYNC_INTERVAL: conf_log_sync_interval = t->ndata; break; case LEX_ALWAYS_BACKLOG: conf_always_backlog = t->ndata; break; case LEX_BACKLOG: conf_backlog = t->ndata; break; case LEX_LOG: conf_log = t->ndata; break; case LEX_BACKLOG_LINES: conf_backlog_lines = t->ndata; break; case LEX_LOG_ROOT: if (conf_log_root) free(conf_log_root); conf_log_root = t->pdata; break; case LEX_LOG_FORMAT: if (conf_log_format) free(conf_log_format); conf_log_format = t->pdata; break; case LEX_LOG_LEVEL: conf_log_level = t->ndata; break; case LEX_IP: if (conf_ip) free(conf_ip); conf_ip = t->pdata; break; case LEX_PORT: conf_port = t->ndata; break; case LEX_CSS: conf_css = t->ndata; break; case LEX_PID_FILE: if (conf_pid_file) free(conf_pid_file); conf_pid_file = t->pdata; break; case LEX_BLRESET_ON_TALK: conf_blreset_on_talk = t->ndata; break; case LEX_NETWORK: r = add_network(t->pdata); list_free(t->pdata); if (!r) return 0; break; case LEX_USER: r = add_user(t->pdata); list_free(t->pdata); if (!r) return 0; break; default: if (t->type == TUPLE_STR) free(t->pdata); conf_die("Config error in base config (%d)", t->type); return 0; } free(t); } free(root_list); root_list = NULL; if (conf_backlog && !conf_log) { if (conf_backlog_lines == 0) { conf_die("You must set conf_backlog_lines if " "conf_log = false and " "conf_backlog = true"); return 0; } } if (!conf_log) conf_memlog = 1; else conf_memlog = 0; /* check_networks(networkl); check_clients(userl); */ if (!conf_biphome) { char *home = getenv("HOME"); if (!home) { conf_die("no $HOME !, do you live in a trailer ?"); return 0; } conf_biphome = malloc(strlen(home) + strlen("/.bip") + 1); strcpy(conf_biphome, home); strcat(conf_biphome, "/.bip"); } if (!conf_log_root) { char *ap = "/logs"; conf_log_root = malloc(strlen(conf_biphome) + strlen(ap) + 1); strcpy(conf_log_root, conf_biphome); strcat(conf_log_root, ap); mylog(LOG_INFO, "Default log root: %s", conf_log_root); } if (!conf_pid_file) { char *pid = "/bip.pid"; conf_pid_file = malloc(strlen(conf_biphome) + strlen(pid) + 1); strcpy(conf_pid_file, conf_biphome); strcat(conf_pid_file, pid); mylog(LOG_INFO, "Default pid file: %s", conf_pid_file); } #ifdef HAVE_LIBSSL conf_ssl_certfile = NULL; /* Make into a config option */ if (!conf_ssl_certfile) { char *ap = "/bip.pem"; if (conf_ssl_certfile) { free(conf_ssl_certfile); conf_ssl_certfile = NULL; } conf_ssl_certfile = malloc(strlen(conf_biphome) + strlen(ap) + 1); strcpy(conf_ssl_certfile, conf_biphome); strcat(conf_ssl_certfile, ap); mylog(LOG_INFO, "Default SSL certificate file: %s", conf_ssl_certfile); } #endif if (!conf_log_format) conf_log_format = "%u/%n/%Y-%m/%c.%d.log"; return 1; } void ircize(list_t *ll) { hash_iterator_t it; for (hash_it_init(&conf_users, &it); hash_it_item(&it); hash_it_next(&it)) { struct c_user *u = hash_it_item(&it); hash_t *adm_conn = hash_get(&adm_users, u->name); if (!adm_conn) { adm_conn = hash_new(HASH_NOCASE); hash_insert(&adm_users, u->name, adm_conn); mylog(LOG_DEBUG, "new user: \"%s\"", u->name); } else { mylog(LOG_DEBUG, "old user: \"%s\"", u->name); } /* * TODO: keep track of removed connection on sighup */ /* * A user has multiple connections. * For each connections create a irc_client and a irc_server * instance and register them in connection structure; */ list_iterator_t cit; for (list_it_init(&u->connectionl, &cit); list_it_item(&cit); list_it_next(&cit)) { struct c_connection *c = list_it_item(&cit); struct link *link; int i; if (!c->name) fatal("no name for a connection"); link = hash_get(adm_conn, c->name); if (!link) { mylog(LOG_DEBUG, "new connection: \"%s\"", c->name); link = irc_link_new(); hash_insert(adm_conn, c->name, link); link->name = strmaydup(c->name); link->log = log_new(u->name, link->name); list_iterator_t chit; for (list_it_init(&c->channell, &chit); list_it_item(&chit); list_it_next(&chit)) { struct c_channel *chan = list_it_item(&chit); struct chan_info *ci = chan_info_new(); ci->name = strdup(chan->name); ci->key = strmaydup(chan->key); hash_insert(&link->chan_infos, ci->name, ci); list_add_last(&link->chan_infos_order, ci); } list_add_last(ll, link); } else { mylog(LOG_DEBUG, "old connection: \"%s\"", c->name); #define MAYFREE(a) do { \ if (a) { \ free(a); \ (a) = NULL; \ } \ } while(0); MAYFREE(link->away_nick); MAYFREE(link->no_client_away_msg); MAYFREE(link->password); MAYFREE(link->user); MAYFREE(link->real_name); MAYFREE(link->s_password); MAYFREE(link->connect_nick); MAYFREE(link->vhost); #ifdef HAVE_LIBSSL MAYFREE(link->ssl_check_store); sk_X509_free(link->untrusted_certs); #endif for (i = 0; i < link->serverc; i++) server_free(link->serverv[i]); free(link->serverv); link->serverv = NULL; link->serverc = 0; } link->follow_nick = c->follow_nick; link->ignore_first_nick = c->ignore_first_nick; /* XXX vider laliste on_connect_send */ list_iterator_t ocsit; for (list_it_init(&c->channell, &ocsit); list_it_item(&ocsit); list_it_next(&ocsit)) { free(list_it_item(&ocsit)); } list_init(&link->on_connect_send); list_append(&link->on_connect_send, &c->on_connect_send); link->away_nick = strmaydup(c->away_nick); link->no_client_away_msg = strmaydup(c->no_client_away_msg); link->username = strmaydup(u->name); link->password = malloc(20); memcpy(link->password, u->password, 20); link->seed = u->seed; list_iterator_t seit; for (list_it_init(&c->network->serverl, &seit); list_it_item(&seit); list_it_next(&seit)) { struct server *s = list_it_item(&seit); link->serverv = realloc(link->serverv, (link->serverc + 1) * sizeof(struct server *)); link->serverv[link->serverc] = server_new(); /* XXX: wrong */ link->serverv[link->serverc]->host = strmaydup(s->host); link->serverv[link->serverc]->port = s->port; link->serverc++; } link->user = strmaydup(c->user); if (!link->user) link->user = strmaydup(u->default_user); link->real_name = strmaydup(c->realname); if (!link->real_name) link->real_name = strmaydup(u->default_realname); link->s_password = strmaydup(c->password); link->connect_nick = strmaydup(c->nick); if (!link->connect_nick) link->connect_nick = strmaydup(u->default_nick); link->vhost = strmaydup(c->vhost); link->bind_port = c->source_port; #ifdef HAVE_LIBSSL link->s_ssl = c->network->ssl; link->ssl_check_mode = u->ssl_check_mode; link->ssl_check_store = strmaydup(u->ssl_check_store); link->untrusted_certs = sk_X509_new_null(); #endif if (!link->user) link->user = strmaydup("bip"); if (!link->connect_nick) link->connect_nick = strmaydup("bip"); if (!link->real_name) link->real_name = strmaydup("bip"); } } } int main(int argc, char **argv) { FILE *conf = NULL; char *confpath = NULL; list_t *ll = list_new(NULL); int ch; int r,fd; char buf[30]; conf_ip = strdup("0.0.0.0"); conf_port = 7778; conf_css = 0; hash_init(&adm_users, HASH_NOCASE); hash_init(&conf_users, HASH_NOCASE); hash_init(&conf_networks, HASH_NOCASE); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, reload_config); signal(SIGINT, bad_quit); signal(SIGQUIT, bad_quit); signal(SIGTERM, bad_quit); conf_log_root = NULL; conf_log_format = NULL; conf_log_level = LOG_INFO; conf_backlog = 1; conf_log = 1; conf_backlog_lines = 100; conf_log_sync_interval = 5; conf_daemonize = 1; conf_global_log_file = stderr; conf_pid_file = NULL; while ((ch = getopt(argc, argv, "hnf:s:")) != -1) { switch (ch) { case 'f': confpath = strdup(optarg); break; case 'n': conf_daemonize = 0; break; case 's': conf_biphome = strdup(optarg); break; default: usage(argv[0]); } } if (confpath) { conf = fopen(confpath, "r"); if (!conf) fatal("config file not found"); } if (!conf) { char *home; home = getenv("HOME"); if (!home) fatal("no home"); confpath = malloc(strlen(home) + 1 + strlen(S_CONF) + 1); *confpath = 0; strcat(confpath, home); strcat(confpath, "/"); strcat(confpath, S_CONF); conf = fopen(confpath, "r"); if (!conf) fatal("%s config file not found", confpath); } r = fireup(conf); fclose(conf); if (!r) { fatal("%s", conf_errstr); exit(28); } fd = do_pid_stuff(); pid_t pid = 0; if (conf_daemonize) pid = daemonize(); else pid = getpid(); snprintf(buf, 29, "%ld\n", (long unsigned int)pid); write(fd, buf, strlen(buf)); close(fd); connection_t *inc; inc = listen_new(conf_ip, conf_port, conf_css); if (!inc) fatal("Could not create listening socket"); for (;;) { if (r) ircize(ll); if (conf_error) mylog(LOG_ERROR, "conf error: %s", conf_errstr); irc_main(inc, ll); sighup = 0; conf = fopen(confpath, "r"); if (!conf) fatal("%s config file not found", confpath); r = fireup(conf); fclose(conf); } return 1; } void write_user_list(connection_t *c, char *dest) { hash_iterator_t it; list_iterator_t lit; char buf[4096]; WRITE_LINE2(c, P_IRCMASK, "PRIVMSG", dest, "bip user list:"); for (hash_it_init(&conf_users, &it); hash_it_item(&it); hash_it_next(&it)) { struct c_user *u = hash_it_item(&it); snprintf(buf, 4095, "* %s:", u->name); buf[4095] = 0; WRITE_LINE2(c, P_IRCMASK, "PRIVMSG", dest, buf); for (list_it_init(&u->connectionl, &lit); list_it_item(&lit); list_it_next(&lit)) { struct c_connection *con = list_it_item(&lit); snprintf(buf, 4095, " - %s", con->name); buf[4095] = 0; WRITE_LINE2(c, P_IRCMASK, "PRIVMSG", dest, buf); } } WRITE_LINE2(c, P_IRCMASK, "PRIVMSG", dest, "End of bip user list"); } #ifdef HAVE_LIBSSL int link_add_untrusted(struct link_server *ls, X509 *cert) { int i; /* Check whether the cert is already in the stack */ for (i = 0; i < sk_X509_num(LINK(ls)->untrusted_certs); i++) { if (!X509_cmp(cert, sk_X509_value(LINK(ls)->untrusted_certs, i))) return 1; } return sk_X509_push(LINK(ls)->untrusted_certs, cert); } int ssl_check_trust(struct link_client *ic) { X509 *trustcert = NULL; char subject[270]; char issuer[270]; unsigned char fp[EVP_MAX_MD_SIZE]; char fpstr[EVP_MAX_MD_SIZE * 3 + 20]; unsigned int fplen; int i; if(!LINK(ic)->untrusted_certs || sk_X509_num(LINK(ic)->untrusted_certs) <= 0) return 0; trustcert = sk_X509_value(LINK(ic)->untrusted_certs, 0); strcpy(subject, "Subject: "); strcpy(issuer, "Issuer: "); strcpy(fpstr, "MD5 fingerprint: "); X509_NAME_oneline(X509_get_subject_name(trustcert), subject + 9, 256); X509_NAME_oneline(X509_get_issuer_name(trustcert), issuer + 9, 256); X509_digest(trustcert, EVP_md5(), fp, &fplen); for(i = 0; i < (int)fplen; i++) sprintf(fpstr + 17 + (i * 3), "%02X%c", fp[i], (i == (int)fplen - 1) ? '\0' : ':'); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", "This server SSL certificate was not " "accepted because it is not in your store " "of trusted certificates:"); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", subject); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", issuer); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", fpstr); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", "WARNING: if you've already trusted a " "certificate for this server before, that " "probably means it has changed."); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", "If so, YOU MAY BE SUBJECT OF A " "MAN-IN-THE-MIDDLE ATTACK! PLEASE DON'T TRUST " "THIS CERTIFICATE IF YOU'RE NOT SURE THIS IS " "NOT THE CASE."); WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", "Type /QUOTE BIP TRUST OK to trust this " "certificate, /QUOTE BIP TRUST NO to discard it."); return 1; } #if 0 static int ssl_trust_next_cert(struct link_client *ic) { (void)ic; } static int ssl_discard_next_cert(struct link_client *ic) { (void)ic; } #endif /* 0 */ #endif #ifdef HAVE_LIBSSL int adm_trust(struct link_client *ic, struct line *line) { if (ic->allow_trust != 1) { mylog(LOG_ERROR, "User attempted TRUST command without " "being allowed to!"); unbind_from_link(ic); return OK_CLOSE; } if(!LINK(ic)->untrusted_certs || sk_X509_num(LINK(ic)->untrusted_certs) <= 0) { /* shouldn't have been asked to /QUOTE BIP TRUST but well... */ WRITE_LINE2(CONN(ic), P_SERV, "NOTICE", "TrustEm", "No untrusted certificates."); return ERR_PROTOCOL; } if (line->elemc != 3) return ERR_PROTOCOL; if (!strcasecmp(line->elemv[2], "OK")) { /* OK, attempt to trust the cert! */ BIO *bio = BIO_new_file(LINK(ic)->ssl_check_store, "a+"); X509 *trustcert = sk_X509_shift(LINK(ic)->untrusted_certs); if(!bio || !trustcert || PEM_write_bio_X509(bio, trustcert) <= 0) write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " ":==== Error while trusting test!\r\n"); else write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " ":==== Certificate now trusted.\r\n"); BIO_free_all(bio); X509_free(trustcert); } else if (!strcasecmp(line->elemv[2], "NO")) { /* NO, discard the cert! */ write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " ":==== Certificate discarded.\r\n"); X509_free(sk_X509_shift(LINK(ic)->untrusted_certs)); } else return ERR_PROTOCOL; if (!ssl_check_trust(ic)) { write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " ":No more certificates waiting awaiting " "user trust, thanks!\r\n"); write_line_fast(CONN(ic), ":irc.bip.net NOTICE pouet " ":If the certificate is trusted, bip should " "be able to connect to the server on the " "next retry. Please wait a while and try " "connecting your client again.\r\n"); LINK(ic)->recon_timer = 1; /* Speed up reconnection... */ unbind_from_link(ic); return OK_CLOSE; } return OK_FORGET; } #endif extern struct link_client *reloading_client; void adm_blreset(struct link_client *ic) { hash_iterator_t it; for (hash_it_init(&LINK(ic)->log->logfgs, &it); hash_it_item(&it); hash_it_next(&it)) { logfilegroup_t *lfg = hash_it_item(&it); log_reset(lfg); } } void adm_follow_nick(struct link_client *ic, char *val) { struct link *link = LINK(ic); if (strncasecmp(val, "TRUE", 4) == 0) { link->follow_nick = 1; } else { link->follow_nick = 0; } } void adm_ignore_first_nick(struct link_client *ic, char *val) { struct link *link = LINK(ic); if (strncasecmp(val, "TRUE", 4) == 0) { link->ignore_first_nick = 1; } else { link->ignore_first_nick = 0; } } void adm_on_connect_send(struct link_client *ic, char *val) { struct link *link = LINK(ic); if (link->on_connect_send) { free(link->on_connect_send); link->on_connect_send = NULL; } if (val != NULL) link->on_connect_send = strdup(val); } void adm_away_nick(struct link_client *ic, char *val) { struct link *link = LINK(ic); if (link->away_nick) { free(link->away_nick); link->away_nick = NULL; } if (val != NULL) link->away_nick = strdup(val); } int adm_bip(struct link_client *ic, struct line *line); int adm_bip(struct link_client *ic, struct line *line) { char *nick; if (LINK(ic)->l_server) nick = LINK(ic)->l_server->nick; else nick = LINK(ic)->prev_nick; if (line->elemc < 2) return OK_FORGET; if (strcasecmp(line->elemv[1], "RELOAD") == 0) { reloading_client = ic; sighup = 1; } else if (strcasecmp(line->elemv[1], "LIST") == 0) { write_user_list(CONN(ic), nick); } else if (strcasecmp(line->elemv[1], "JUMP") == 0) { if (LINK(ic)->l_server) { WRITE_LINE1(CONN(LINK(ic)->l_server), NULL, "QUIT", "jumpin' jumpin'"); connection_close(CONN(LINK(ic)->l_server)); } } else if (strcasecmp(line->elemv[1], "BLRESET") == 0) { adm_blreset(ic); } else if (strcasecmp(line->elemv[1], "HELP") == 0) { WRITE_LINE2(CONN(ic), P_IRCMASK, "PRIVMSG", nick, "/BIP (RELOAD|LIST|JUMP|BLRESET|HELP)"); } else if (strcasecmp(line->elemv[1], "FOLLOW_NICK") == 0) { if (line->elemc != 3) return OK_FORGET; adm_follow_nick(ic, line->elemv[2]); } else if (strcasecmp(line->elemv[1], "IGNORE_FIRST_NICK") == 0) { if (line->elemc != 3) return OK_FORGET; adm_ignore_first_nick(ic, line->elemv[2]); } else if (strcasecmp(line->elemv[1], "ON_CONNECT_SEND") == 0) { if (line->elemc == 2) adm_on_connect_send(ic, NULL); else if (line->elemc == 3) adm_on_connect_send(ic, line->elemv[2]); else return OK_FORGET; } else if (strcasecmp(line->elemv[1], "AWAY_NICK") == 0) { if (line->elemc == 2) adm_away_nick(ic, NULL); else if (line->elemc == 3) adm_away_nick(ic, line->elemv[2]); else return OK_FORGET; #ifdef HAVE_LIBSSL } else if (strcasecmp(line->elemv[1], "TRUST") == 0) { return adm_trust(ic, line); #endif } return OK_FORGET; } void free_conf(list_t *l) { struct tuple *t; list_iterator_t li; for (list_it_init(l, &li); (t = list_it_item(&li)); list_it_next(&li)) { switch (t->tuple_type) { case TUPLE_STR: free(t->pdata); /* no break, for the style */ case TUPLE_INT: free(t); break; case TUPLE_LIST: free_conf(t->pdata); break; default: fatal("internal error free_conf"); break; } } }