fix possible crash on user deletion
[srvx.git] / src / mod-qserver.c
1 /* Direct Query Server module for srvx 1.x
2  * Copyright 2006 Michael Poole <mdpoole@troilus.org>
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 #include "conf.h"
22 #include "hash.h"
23 #include "ioset.h"
24 #include "log.h"
25 #include "modcmd.h"
26 #include "proto.h"
27
28 const char *qserver_module_deps[] = { NULL };
29
30 struct qserverClient {
31     struct userNode *user;
32     struct io_fd *fd;
33     unsigned int id;
34     unsigned int password_ok : 1;
35 };
36
37 static struct log_type *qserver_log;
38 static struct io_fd *qserver_listener;
39 static struct qserverClient **qserver_clients;
40 static dict_t qserver_dict;
41 static unsigned int qserver_nbots;
42
43 static struct {
44     const char *password;
45 } conf;
46
47 static void
48 qserver_privmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified))
49 {
50     struct qserverClient *client;
51
52     client = dict_find(qserver_dict, target->nick, NULL);
53     assert(client->user == target);
54     ioset_printf(client->fd, "%s P :%s\n", user->nick, text);
55 }
56
57 static void
58 qserver_notice(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified))
59 {
60     struct qserverClient *client;
61
62     client = dict_find(qserver_dict, target->nick, NULL);
63     assert(client->user == target);
64     ioset_printf(client->fd, "%s N :%s\n", user->nick, text);
65 }
66
67 static void
68 qserver_readable(struct io_fd *fd)
69 {
70     struct qserverClient *client;
71     struct service *service;
72     char *argv[MAXNUMPARAMS];
73     unsigned int argc;
74     size_t len;
75     int res;
76     char tmpline[MAXLEN];
77
78     client = fd->data;
79     assert(client->fd == fd);
80     res = ioset_line_read(fd, tmpline, sizeof(tmpline));
81     if (res < 0)
82         return;
83     else if (res == 0) {
84         ioset_close(fd, 1);
85         return;
86     }
87     len = strlen(tmpline);
88     while (tmpline[len - 1] == '\r' || tmpline[len - 1] == '\n')
89         tmpline[--len] = '\0';
90     argc = split_line(tmpline, false, ArrayLength(argv), argv);
91     if (argc < 3) {
92         ioset_printf(fd, "MISSING_ARGS\n");
93         return;
94     }
95     if (!strcmp(argv[1], "PASS")
96         && conf.password
97         && !strcmp(argv[2], conf.password)) {
98         client->password_ok = 1;
99     } else if ((client->password_ok || !conf.password)
100                && (service = service_find(argv[1])) != NULL) {
101         ioset_printf(fd, "%s S\n", argv[0]);
102         svccmd_invoke_argv(client->user, service, NULL, argc - 2, argv + 2, 1);
103         ioset_printf(fd, "%s E\n", argv[0]);
104     } else {
105         ioset_printf(fd, "%s X %s\n", argv[0], argv[1]);
106     }
107 }
108
109 static void
110 qserver_destroy_fd(struct io_fd *fd)
111 {
112     struct qserverClient *client;
113
114     client = fd->data;
115     assert(client->fd == fd);
116     dict_remove(qserver_dict, client->user->nick);
117     DelUser(client->user, NULL, 0, "client disconnected");
118     qserver_clients[client->id] = NULL;
119     free(client);
120 }
121
122 static void
123 qserver_accept(UNUSED_ARG(struct io_fd *listener), struct io_fd *fd)
124 {
125     struct qserverClient *client;
126     struct sockaddr_storage ss;
127     socklen_t sa_len;
128     unsigned int ii;
129     unsigned int jj;
130     int res;
131     char nick[NICKLEN+1];
132     char host[HOSTLEN+1];
133     char ip[HOSTLEN+1];
134
135     client = calloc(1, sizeof(*client));
136     fd->data = client;
137     fd->line_reads = 1;
138     fd->readable_cb = qserver_readable;
139     fd->destroy_cb = qserver_destroy_fd;
140
141     for (ii = 0; ii < qserver_nbots; ++ii)
142         if (qserver_clients[ii] == NULL)
143             break;
144     if (ii == qserver_nbots) {
145         qserver_nbots += 8;
146         qserver_clients = realloc(qserver_clients, qserver_nbots * sizeof(qserver_clients[0]));
147         for (jj = ii; jj < qserver_nbots; ++jj)
148             qserver_clients[jj] = NULL;
149     }
150     client->id = ii;
151     client->fd = fd;
152     qserver_clients[client->id] = client;
153     snprintf(nick, sizeof(nick), " QServ%04d", client->id);
154     safestrncpy(host, "srvx.dummy.user", sizeof(host));
155     safestrncpy(ip, "0.0.0.0", sizeof(ip));
156     sa_len = sizeof(ss);
157     res = getpeername(fd->fd, (struct sockaddr*)&ss, &sa_len);
158     if (res == 0) {
159         getnameinfo((struct sockaddr*)&ss, sa_len, ip, sizeof(host), NULL, 0, NI_NUMERICHOST);
160         if (getnameinfo((struct sockaddr*)&ss, sa_len, host, sizeof(host), NULL, 0, 0) != 0)
161             safestrncpy(host, ip, sizeof(host));
162     }
163     client->user = AddLocalUser(nick, nick+1, host, "qserver dummy user", "*+oi");
164     irc_pton(&client->user->ip, NULL, ip);
165     dict_insert(qserver_dict, client->user->nick, client);
166
167     reg_privmsg_func(client->user, qserver_privmsg);
168     reg_notice_func(client->user, qserver_notice);
169 }
170
171 static void
172 qserver_conf_read(void)
173 {
174     struct addrinfo hints;
175     struct addrinfo *ai;
176     dict_t node;
177     const char *str1;
178     const char *str2;
179     int res;
180
181     ioset_close(qserver_listener, 1);
182     qserver_listener = NULL;
183     node = conf_get_data("modules/qserver", RECDB_OBJECT);
184     if (!node)
185         return;
186     str1 = database_get_data(node, "bind_address", RECDB_QSTRING);
187     if (!str1)
188         str1 = database_get_data(node, "address", RECDB_QSTRING);
189     str2 = database_get_data(node, "port", RECDB_QSTRING);
190     if (!str2)
191         return;
192     memset(&hints, 0, sizeof(hints));
193     hints.ai_flags = AI_PASSIVE;
194     hints.ai_socktype = SOCK_STREAM;
195     res = getaddrinfo(str1, str2, &hints, &ai);
196     if (res) {
197         log_module(qserver_log, LOG_ERROR, "Unable to find address [%s]:%s: %s", str1 ? str1 : "", str2, gai_strerror(res));
198     } else if (!(qserver_listener = ioset_listen(ai->ai_addr, ai->ai_addrlen, NULL, qserver_accept))) {
199         log_module(qserver_log, LOG_ERROR, "Unable to listen on [%s]:%s", str1 ? str1 : "", str2);
200     }
201     conf.password = database_get_data(node, "password", RECDB_QSTRING);
202     freeaddrinfo(ai);
203 }
204
205 void
206 qserver_cleanup(void)
207 {
208     unsigned int ii;
209
210     ioset_close(qserver_listener, 1);
211     for (ii = 0; ii < qserver_nbots; ++ii)
212         if (qserver_clients[ii])
213             DelUser(qserver_clients[ii]->user, NULL, 0, "module finalizing");
214     dict_delete(qserver_dict);
215 }
216
217 int
218 qserver_init(void)
219 {
220     qserver_log = log_register_type("QServer", "file:qserver.log");
221     conf_register_reload(qserver_conf_read);
222     qserver_dict = dict_new();
223     reg_exit_func(qserver_cleanup);
224     return 1;
225 }
226
227 int
228 qserver_finalize(void)
229 {
230     return 1;
231 }