+/*
+ * Written by David Herrmann.
+ * Dedicated to the Public Domain.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+
+#include "iauth.h"
+
+/* File which is spawned on a query. */
+char *iauth_scriptfile = NULL;
+
+const struct iauth_result *iauth_query(struct iauth_client *cli) {
+ static struct iauth_result res;
+ static char buffer[IAUTH_DATALEN * 5 + 5];
+ pid_t cpid;
+ signed int fds[2], ret;
+ char ** parv;
+ char portbuf[6], portbuf2[6];
+ char c = 0;
+ char *arg, *tread;
+
+ memset(&res, 0, sizeof(res));
+ memset(portbuf, 0, 6);
+ memset(portbuf2, 0, 6);
+ if(!iauth_scriptfile) return &res;
+ if(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
+ iauth_flog(IAUTH_WARNING, "socketpair() failed; errno = '%d'.", errno);
+ return NULL;
+ }
+
+ cpid = fork();
+ if(cpid < 0) {
+ close(fds[0]);
+ close(fds[1]);
+ iauth_flog(IAUTH_WARNING, "fork() failed; errno = '%d'.", errno);
+ return NULL;
+ }
+
+ if(cpid == 0) {
+ /* This is the child.
+ * Close the parents socket. Spawn the DB process with the socket
+ * set as stdout.
+ * When done, close the socket and exit the child.
+ * We directly fork() again to prevent zombies.
+ */
+ close(fds[0]);
+ cpid = fork();
+ if(cpid < 0) exit(EXIT_FAILURE);
+ if(cpid != 0) exit(EXIT_SUCCESS);
+
+ #define ISIZE 18
+ parv = iauth_malloc(sizeof(char*) * ISIZE);
+ parv[0] = iauth_scriptfile;
+ parv[1] = cli->ip?cli->ip:&c;
+ snprintf(portbuf, 5, "%hu", cli->port);
+ parv[2] = portbuf;
+ parv[3] = cli->lo_ip?cli->lo_ip:&c;
+ snprintf(portbuf2, 5, "%hu", cli->lo_port);
+ parv[4] = portbuf2;
+ parv[5] = cli->host?cli->host:&c;
+ parv[6] = cli->c_host?cli->c_host:&c;
+ parv[7] = cli->c_serv?cli->c_serv:&c;
+ parv[8] = cli->nick?cli->nick:&c;
+ parv[9] = cli->username?cli->username:&c;
+ parv[10] = cli->realname?cli->realname:&c;
+ parv[11] = cli->account?cli->account:&c;
+ parv[12] = cli->fakehost?cli->fakehost:&c;
+ parv[13] = cli->cclass?cli->cclass:&c;
+ parv[14] = cli->password?cli->password:&c;
+ parv[15] = cli->ident?cli->ident:&c;
+ parv[16] = iauth_servname;
+ parv[ISIZE - 1] = NULL;
+ #undef ISIZE
+
+ if(dup2(fds[1], 1) != -1) {
+ execvp(iauth_scriptfile, parv);
+ iauth_flog(IAUTH_WARNING, "execvp() failed; errno = '%d'.", errno);
+ }
+ else iauth_flog(IAUTH_WARNING, "dup2() failed; errno = '%d'.", errno);
+ iauth_free(parv);
+ close(fds[1]);
+ exit(EXIT_FAILURE);
+ }
+
+ /* We are the parent. Read on the file descriptor until it is closed. */
+ close(fds[1]);
+
+ /* Wait for client. The client directly forked again, so this should
+ * not *really* block.
+ * This whole mechanism prevents zombies.
+ */
+ wait(NULL);
+
+ tread = buffer;
+ if((ret = read(fds[0], buffer, sizeof(buffer))) < 1) {
+ close(fds[0]);
+ return NULL;
+ }
+ buffer[sizeof(buffer) - 1] = 0;
+
+ /* Read class. */
+ if(!(arg = strchr(tread, ' '))) {
+ close(fds[0]);
+ return NULL;
+ }
+ *arg++ = 0;
+ if(strcmp(tread, "$") == 0 || strlen(tread) > IAUTH_DATALEN) res.cclass[0] = 0;
+ else strcpy(res.cclass, tread);
+
+ /* Read ident. */
+ tread = arg;
+ if(!(arg = strchr(tread, ' '))) {
+ close(fds[0]);
+ return NULL;
+ }
+ *arg++ = 0;
+ if(strcmp(tread, "$") == 0 || strlen(tread) > IAUTH_DATALEN) res.ident[0] = 0;
+ else strcpy(res.ident, tread);
+
+ /* Read host. */
+ tread = arg;
+ if(!(arg = strchr(tread, ' '))) {
+ close(fds[0]);
+ return NULL;
+ }
+ *arg++ = 0;
+ if(strcmp(tread, "$") == 0 || strlen(tread) > IAUTH_DATALEN) res.host[0] = 0;
+ else strcpy(res.host, tread);
+
+ /* Read ip. */
+ tread = arg;
+ if(!(arg = strchr(tread, ' '))) {
+ close(fds[0]);
+ return NULL;
+ }
+ *arg++ = 0;
+ if(strcmp(tread, "$") == 0 || strlen(tread) > IAUTH_DATALEN) res.ip[0] = 0;
+ else strcpy(res.ip, tread);
+
+ /* Read modes string. */
+ tread = arg;
+ if(!*tread) {
+ close(fds[0]);
+ return NULL;
+ }
+ if(strcmp(tread, "$") == 0 || strlen(tread) > IAUTH_DATALEN) res.modes[0] = 0;
+ else strcpy(res.modes, tread);
+
+ close(fds[0]);
+ return &res;
+}
+
+/* User management.
+ * Adds or removes a user.
+ */
+struct iauth_client *iauth_clients = NULL;
+unsigned int iauth_clients_size = 0;
+
+void iauth_setcap(unsigned int cap) {
+ if(iauth_clients_size) return;
+ iauth_clients = iauth_malloc(sizeof(struct iauth_client) * cap);
+ iauth_clients_size = cap;
+}
+
+void iauth_addid(signed int id) {
+ if(id < 0) return;
+ if(!iauth_clients_size || id >= iauth_clients_size) return;
+ memset(&iauth_clients[id], 0, sizeof(struct iauth_client));
+ iauth_clients[id].state_r = 1;
+ iauth_clients[id].id = id;
+}
+
+void iauth_delid(signed int id) {
+ if(id < 0) return;
+ if(!iauth_clients_size || id >= iauth_clients_size) return;
+ iauth_free(iauth_clients[id].ip);
+ iauth_free(iauth_clients[id].lo_ip);
+ iauth_free(iauth_clients[id].host);
+ iauth_free(iauth_clients[id].c_host);
+ iauth_free(iauth_clients[id].c_serv);
+ iauth_free(iauth_clients[id].nick);
+ iauth_free(iauth_clients[id].username);
+ iauth_free(iauth_clients[id].realname);
+ iauth_free(iauth_clients[id].account);
+ iauth_free(iauth_clients[id].fakehost);
+ iauth_free(iauth_clients[id].cclass);
+ iauth_free(iauth_clients[id].password);
+ iauth_free(iauth_clients[id].ident);
+ memset(&iauth_clients[id], 0, sizeof(struct iauth_client));
+}
+
+static unsigned int loc_allow = 0;
+static unsigned int loc_deny = 0;
+static unsigned int def_allow = 0;
+static unsigned int def_deny = 0;
+
+void iauth_stats_report() {
+ static unsigned int stats = 0;
+ static char buffer[512];
+
+ ++stats;
+
+ if(stats == 1) {
+ iauth_query_newconf();
+ iauth_query_config("*", "loc", "Login-On-Connect handler");
+ iauth_query_config("*", "def", "Default handler");
+ }
+ if(stats < 10) goto report;
+ else if((stats % 10) == 0) goto report;
+ else return;
+
+ report:
+ iauth_query_newstats();
+ sprintf(buffer, "Count: %u Allowed: %u Denied: %u", loc_allow + loc_deny, loc_allow, loc_deny);
+ iauth_query_stats("loc", buffer);
+ sprintf(buffer, "Count: %u Allowed: %u Denied: %u", def_allow + def_deny, def_allow, def_deny);
+ iauth_query_stats("def", buffer);
+}
+
+void iauth_stats_loc_allow() {
+ ++loc_allow;
+ iauth_stats_report();
+}
+
+void iauth_stats_loc_deny() {
+ ++loc_deny;
+ iauth_stats_report();
+}
+
+void iauth_stats_def_allow() {
+ ++def_allow;
+ iauth_stats_report();
+}
+
+void iauth_stats_def_deny() {
+ ++def_deny;
+ iauth_stats_report();
+}
+
+/* Sends a specific request to the ircd.
+ * This is a less generic but easier to use interface
+ * for the iauth_[vf]send() commands. This interface also
+ * correctly sends statistics.
+ */
+
+/* Operator Notification: > :<message text> */
+extern void iauth_query_fnotify(const char *format, ...) {
+ va_list arg;
+
+ va_start(arg, format);
+ iauth_query_vnotify(format, arg);
+ va_end(arg);
+}
+extern void iauth_query_vnotify(const char *format, va_list list) {
+ char buffer[IAUTH_LINE + 1];
+
+ buffer[IAUTH_LINE] = 0;
+ sprintf(buffer, "> :");
+ vsnprintf(buffer, IAUTH_LINE - 4, format, list);
+ iauth_fsend(buffer);
+}
+
+/* Set Debug Level: G <level> */
+extern void iauth_query_debug(unsigned int debuglevel) {
+ iauth_fsend("G %u", debuglevel);
+}
+
+/* Set Policy Options: O <options> */
+extern void iauth_query_policy(const char *policy) {
+ iauth_fsend("O %s", policy);
+}
+
+/* iauth Program Version: V :<version string> */
+extern void iauth_query_version(const char *version) {
+ iauth_fsend("V :%s", version);
+}
+
+/* Start of new configuration: a */
+extern void iauth_query_newconf() {
+ iauth_fsend("a");
+}
+
+/* Configuration Information: A <hosts?> <module> :<options> */
+extern void iauth_query_config(const char *hosts, const char *module, const char *value) {
+ iauth_fsend("A %s %s :%s", hosts, module, value);
+}
+
+/* Start of new statistics: s */
+extern void iauth_query_newstats() {
+ iauth_fsend("s");
+}
+
+/* Statistics Information: S <module> :<module information> */
+extern void iauth_query_stats(const char *module, const char *value) {
+ iauth_fsend("S %s :%s", module, value);
+}
+
+/* Forced Username: o <id> <remoteip> <remoteport> <username> */
+extern void iauth_query_set_username(signed int id, const char *username) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("o %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, username);
+}
+
+/* Trusted Username: U <id> <remoteip> <remoteport> <username> */
+extern void iauth_query_trust_username(signed int id, const char *username) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("U %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, username);
+}
+
+/* Untrusted Username: u <id> <remoteip> <remoteport> <username> */
+extern void iauth_query_distrust_username(signed int id, const char *username) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("u %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, username);
+}
+
+/* Client Hostname: N <id> <remoteip> <remoteport> <hostname> */
+extern void iauth_query_sethost(signed int id, const char *hostname) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("N %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, hostname);
+}
+
+/* Client IP Address: I <id> <currentip> <remoteport> <newip> */
+extern void iauth_query_setip(signed int id, const char *ip) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("I %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, ip);
+ iauth_free(iauth_clients[id].ip);
+ iauth_clients[id].ip = iauth_strdup(ip);
+}
+
+/* Adjust User Mode: M <id> <remoteip> <remoteport> +<mode changes> */
+extern void iauth_query_setmodes(signed int id, const char *modes) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("M %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, modes);
+}
+
+/* Challenge User: C <id> <remoteip> <remoteport> :<challenge string> */
+extern void iauth_query_challenge(signed int id, const char *challenge) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("C %d %s %hu :%s", id, iauth_clients[id].ip, iauth_clients[id].port, challenge);
+}
+
+/* Quietly Kill Client: k <id> <remoteip> <remoteport> :<reason> */
+extern void iauth_query_reject(signed int id, const char *reason) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("k %d %s %hu :%s", id, iauth_clients[id].ip, iauth_clients[id].port, reason);
+ iauth_delid(id);
+}
+
+/* Kill Client: K <id> <remoteip> <remoteport> :<reason> */
+extern void iauth_query_kill(signed int id, const char *reason) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ iauth_fsend("K %d %s %hu :%s", id, iauth_clients[id].ip, iauth_clients[id].port, reason);
+ iauth_delid(id);
+}
+
+/* Done Checking: D <id> <remoteip> <remoteport> [class] */
+extern void iauth_query_assign(signed int id, const char *cclass) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ if(cclass)
+ iauth_fsend("D %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, cclass);
+ else
+ iauth_fsend("D %d %s %hu", id, iauth_clients[id].ip, iauth_clients[id].port);
+ iauth_delid(id);
+}
+
+/* Registered User: R <id> <remoteip> <remoteport> <account> [class] */
+extern void iauth_query_register(signed int id, const char *account, const char *cclass) {
+ if(id < 0 || id >= iauth_clients_size) return;
+ if(cclass)
+ iauth_fsend("D %d %s %hu %s %s", id, iauth_clients[id].ip, iauth_clients[id].port, account, cclass);
+ else
+ iauth_fsend("D %d %s %hu %s", id, iauth_clients[id].ip, iauth_clients[id].port, account);
+ iauth_delid(id);
+}
+