Add direct query server module.
[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 is PROPRIETARY, UNPUBLISHED code.
5  *
6  * This file is not part of srvx. The only logical reason for you to have
7  * this file is to be working for GameSurge.
8  *
9  * If this is not the case, you should delete this copy and inform
10  * Michael Poole <mdpoole@troilus.org> where you acquired it.
11  *
12  * The copyright notice above is not evidence that this code is, has been,
13  * or ever will be publicly licensed.
14  */
15
16 #include "conf.h"
17 #include "hash.h"
18 #include "ioset.h"
19 #include "log.h"
20 #include "modcmd.h"
21 #include "proto.h"
22
23 const char *qserver_module_deps[] = { NULL };
24
25 struct qserverClient {
26     struct userNode *user;
27     struct io_fd *fd;
28     unsigned int id;
29     unsigned int password_ok : 1;
30 };
31
32 static struct log_type *qserver_log;
33 static struct io_fd *qserver_listener;
34 static struct qserverClient **qserver_clients;
35 static dict_t qserver_dict;
36 static unsigned int qserver_nbots;
37
38 static struct {
39     const char *password;
40 } conf;
41
42 static void
43 qserver_privmsg(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified))
44 {
45     struct qserverClient *client;
46
47     client = dict_find(qserver_dict, target->nick, NULL);
48     assert(client->user == target);
49     ioset_printf(client->fd, "%s P :%s\n", user->nick, text);
50 }
51
52 static void
53 qserver_notice(struct userNode *user, struct userNode *target, const char *text, UNUSED_ARG(int server_qualified))
54 {
55     struct qserverClient *client;
56
57     client = dict_find(qserver_dict, target->nick, NULL);
58     assert(client->user == target);
59     ioset_printf(client->fd, "%s N :%s\n", user->nick, text);
60 }
61
62 static void
63 qserver_readable(struct io_fd *fd)
64 {
65     struct qserverClient *client;
66     struct service *service;
67     char *argv[MAXNUMPARAMS];
68     unsigned int argc;
69     size_t len;
70     int res;
71     char tmpline[MAXLEN];
72
73     client = fd->data;
74     assert(client->fd == fd);
75     res = ioset_line_read(fd, tmpline, sizeof(tmpline));
76     if (res < 0)
77         return;
78     else if (res == 0) {
79         ioset_close(fd, 1);
80         return;
81     }
82     len = strlen(tmpline);
83     while (tmpline[len - 1] == '\r' || tmpline[len - 1] == '\n')
84         tmpline[--len] = '\0';
85     argc = split_line(tmpline, false, ArrayLength(argv), argv);
86     if (argc < 3) {
87         ioset_printf(fd, "MISSING_ARGS");
88         return;
89     }
90     if (!strcmp(argv[1], "PASS")
91         && conf.password
92         && !strcmp(argv[2], conf.password)) {
93         client->password_ok = 1;
94     } else if ((client->password_ok || !conf.password)
95                && (service = service_find(argv[1])) != NULL) {
96         ioset_printf(fd, "%s S\n", argv[0]);
97         svccmd_invoke_argv(client->user, service, NULL, argc - 2, argv + 2, 1);
98         ioset_printf(fd, "%s E\n", argv[0]);
99     } else {
100         ioset_printf(fd, "%s X %s\n", argv[0], argv[1]);
101     }
102 }
103
104 static void
105 qserver_destroy_fd(struct io_fd *fd)
106 {
107     struct qserverClient *client;
108
109     client = fd->data;
110     assert(client->fd == fd);
111     dict_remove(qserver_dict, client->user->nick);
112     DelUser(client->user, NULL, 0, "client disconnected");
113     qserver_clients[client->id] = NULL;
114     free(client);
115 }
116
117 static void
118 qserver_accept(UNUSED_ARG(struct io_fd *listener), struct io_fd *fd)
119 {
120     struct qserverClient *client;
121     struct sockaddr_storage ss;
122     socklen_t sa_len;
123     unsigned int ii;
124     unsigned int jj;
125     int res;
126     char nick[NICKLEN+1];
127     char host[HOSTLEN+1];
128     char ip[HOSTLEN+1];
129
130     client = calloc(1, sizeof(*client));
131     fd->data = client;
132     fd->wants_reads = 1;
133     fd->line_reads = 1;
134     fd->readable_cb = qserver_readable;
135     fd->destroy_cb = qserver_destroy_fd;
136
137     for (ii = 0; ii < qserver_nbots; ++ii)
138         if (qserver_clients[ii] == NULL)
139             break;
140     if (ii == qserver_nbots) {
141         qserver_nbots += 8;
142         qserver_clients = realloc(qserver_clients, qserver_nbots * sizeof(qserver_clients[0]));
143         for (jj = ii; jj < qserver_nbots; ++jj)
144             qserver_clients[jj] = NULL;
145     }
146     client->id = ii;
147     client->fd = fd;
148     qserver_clients[client->id] = client;
149     snprintf(nick, sizeof(nick), " QServ%04d", client->id);
150     safestrncpy(host, "srvx.dummy.user", sizeof(host));
151     safestrncpy(ip, "0.0.0.0", sizeof(ip));
152     sa_len = sizeof(ss);
153     res = getpeername(fd->fd, (struct sockaddr*)&ss, &sa_len);
154     if (res == 0) {
155         getnameinfo((struct sockaddr*)&ss, sa_len, ip, sizeof(host), NULL, 0, NI_NUMERICHOST);
156         if (getnameinfo((struct sockaddr*)&ss, sa_len, host, sizeof(host), NULL, 0, 0) != 0)
157             safestrncpy(host, ip, sizeof(host));
158     }
159     client->user = AddLocalUser(nick, nick+1, host, "qserver dummy user", "*+i");
160     irc_pton(&client->user->ip, NULL, ip);
161     dict_insert(qserver_dict, client->user->nick, client);
162
163     reg_privmsg_func(client->user, qserver_privmsg);
164     reg_notice_func(client->user, qserver_notice);
165 }
166
167 static void
168 qserver_conf_read(void)
169 {
170     struct addrinfo hints;
171     struct addrinfo *ai;
172     dict_t node;
173     const char *str1;
174     const char *str2;
175     int res;
176
177     ioset_close(qserver_listener, 1);
178     qserver_listener = NULL;
179     node = conf_get_data("modules/qserver", RECDB_OBJECT);
180     if (!node)
181         return;
182     str1 = database_get_data(node, "address", RECDB_QSTRING);
183     str2 = database_get_data(node, "port", RECDB_QSTRING);
184     if (!str2)
185         return;
186     memset(&hints, 0, sizeof(hints));
187     hints.ai_flags = AI_PASSIVE;
188     hints.ai_socktype = SOCK_STREAM;
189     res = getaddrinfo(str1, str2, &hints, &ai);
190     if (res) {
191         log_module(qserver_log, LOG_ERROR, "Unable to find address [%s]:%s: %s", str1 ? str1 : "", str2, gai_strerror(res));
192     } else if (!(qserver_listener = ioset_listen(ai->ai_addr, ai->ai_addrlen, NULL, qserver_accept))) {
193         log_module(qserver_log, LOG_ERROR, "Unable to listen on [%s]:%s", str1 ? str1 : "", str2);
194     }
195     conf.password = database_get_data(node, "password", RECDB_QSTRING);
196     freeaddrinfo(ai);
197 }
198
199 void
200 qserver_cleanup(void)
201 {
202     unsigned int ii;
203
204     for (ii = 0; ii < qserver_nbots; ++ii)
205         if (qserver_clients[ii])
206             DelUser(qserver_clients[ii]->user, NULL, 0, "module finalizing");
207     dict_delete(qserver_dict);
208 }
209
210 int
211 qserver_init(void)
212 {
213     qserver_log = log_register_type("QServer", "file:qserver.log");
214     conf_register_reload(qserver_conf_read);
215     qserver_dict = dict_new();
216     reg_exit_func(qserver_cleanup);
217     return 1;
218 }
219
220 int
221 qserver_finalize(void)
222 {
223     return 1;
224 }