1 |
/* Nickname access list module. |
2 |
* |
3 |
* IRC Services is copyright (c) 1996-2009 Andrew Church. |
4 |
* E-mail: <achurch@achurch.org> |
5 |
* Parts written by Andrew Kempe and others. |
6 |
* This program is free but copyrighted software; see the file GPL.txt for |
7 |
* details. |
8 |
*/ |
9 |
|
10 |
#include "services.h" |
11 |
#include "modules.h" |
12 |
#include "conffile.h" |
13 |
#include "language.h" |
14 |
#include "commands.h" |
15 |
#include "databases.h" |
16 |
#include "modules/operserv/operserv.h" |
17 |
|
18 |
#include "nickserv.h" |
19 |
#include "ns-local.h" |
20 |
|
21 |
/*************************************************************************/ |
22 |
|
23 |
static Module *module_nickserv; |
24 |
|
25 |
static int32 NSAccessMax; |
26 |
static int NSFirstAccessEnable; |
27 |
static int NSFirstAccessWild; |
28 |
|
29 |
/*************************************************************************/ |
30 |
|
31 |
static void do_access(User *u); |
32 |
|
33 |
static Command cmds[] = { |
34 |
{"ACCESS", do_access, NULL, NICK_HELP_ACCESS, |
35 |
-1, NICK_OPER_HELP_ACCESS}, |
36 |
{ NULL } |
37 |
}; |
38 |
|
39 |
/*************************************************************************/ |
40 |
/**************************** Database stuff *****************************/ |
41 |
/*************************************************************************/ |
42 |
|
43 |
/* See autojoin.c for why we don't create our own table. */ |
44 |
|
45 |
/* Temporary structure for loading/saving access records */ |
46 |
typedef struct { |
47 |
uint32 nickgroup; |
48 |
char *mask; |
49 |
} DBRecord; |
50 |
static DBRecord dbrec_static; |
51 |
|
52 |
/* Iterators for first/next routines */ |
53 |
static NickGroupInfo *db_ngi_iterator; |
54 |
static int db_array_iterator; |
55 |
|
56 |
/*************************************************************************/ |
57 |
|
58 |
/* Table access routines */ |
59 |
|
60 |
static void *new_access(void) |
61 |
{ |
62 |
return memset(&dbrec_static, 0, sizeof(dbrec_static)); |
63 |
} |
64 |
|
65 |
static void free_access(void *record) |
66 |
{ |
67 |
free(((DBRecord *)record)->mask); |
68 |
} |
69 |
|
70 |
static void insert_access(void *record) |
71 |
{ |
72 |
DBRecord *dbrec = record; |
73 |
NickGroupInfo *ngi = get_nickgroupinfo(dbrec->nickgroup); |
74 |
if (!ngi) { |
75 |
module_log("Discarding access record for missing nickgroup %u: %s", |
76 |
dbrec->nickgroup, dbrec->mask); |
77 |
free_access(record); |
78 |
} else { |
79 |
ARRAY_EXTEND(ngi->access); |
80 |
ngi->access[ngi->access_count-1] = dbrec->mask; |
81 |
} |
82 |
} |
83 |
|
84 |
static void *next_access(void) |
85 |
{ |
86 |
while (db_ngi_iterator |
87 |
&& db_array_iterator >= db_ngi_iterator->access_count |
88 |
) { |
89 |
db_ngi_iterator = next_nickgroupinfo(); |
90 |
db_array_iterator = 0; |
91 |
} |
92 |
if (db_ngi_iterator) { |
93 |
dbrec_static.nickgroup = db_ngi_iterator->id; |
94 |
dbrec_static.mask = db_ngi_iterator->access[db_array_iterator++]; |
95 |
return &dbrec_static; |
96 |
} else { |
97 |
return NULL; |
98 |
} |
99 |
} |
100 |
|
101 |
static void *first_access(void) |
102 |
{ |
103 |
db_ngi_iterator = first_nickgroupinfo(); |
104 |
db_array_iterator = 0; |
105 |
return next_access(); |
106 |
} |
107 |
|
108 |
/*************************************************************************/ |
109 |
|
110 |
/* Database table definition */ |
111 |
|
112 |
#define FIELD(name,type,...) \ |
113 |
{ #name, type, offsetof(DBRecord,name) , ##__VA_ARGS__ } |
114 |
|
115 |
static DBField access_dbfields[] = { |
116 |
FIELD(nickgroup, DBTYPE_UINT32), |
117 |
FIELD(mask, DBTYPE_STRING), |
118 |
{ NULL } |
119 |
}; |
120 |
|
121 |
static DBTable access_dbtable = { |
122 |
.name = "nick-access", |
123 |
.newrec = new_access, |
124 |
.freerec = free_access, |
125 |
.insert = insert_access, |
126 |
.first = first_access, |
127 |
.next = next_access, |
128 |
.fields = access_dbfields, |
129 |
}; |
130 |
|
131 |
#undef FIELD |
132 |
|
133 |
/*************************************************************************/ |
134 |
/**************************** Local routines *****************************/ |
135 |
/*************************************************************************/ |
136 |
|
137 |
/* Handle the ACCESS command. */ |
138 |
|
139 |
static void do_access(User *u) |
140 |
{ |
141 |
char *cmd = strtok(NULL, " "); |
142 |
char *mask = strtok(NULL, " "); |
143 |
NickInfo *ni; |
144 |
NickGroupInfo *ngi; |
145 |
int i; |
146 |
|
147 |
if (cmd && stricmp(cmd, "LIST") == 0 && mask && is_services_admin(u)) { |
148 |
ni = get_nickinfo(mask); |
149 |
ngi = NULL; |
150 |
if (!ni) { |
151 |
notice_lang(s_NickServ, u, NICK_X_NOT_REGISTERED, mask); |
152 |
} else if (ni->status & NS_VERBOTEN) { |
153 |
notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, mask); |
154 |
} else if (!(ngi = get_ngi(ni))) { |
155 |
notice_lang(s_NickServ, u, INTERNAL_ERROR); |
156 |
} else if (ngi->access_count == 0) { |
157 |
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X_EMPTY, mask); |
158 |
} else { |
159 |
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_X, mask); |
160 |
ARRAY_FOREACH (i, ngi->access) |
161 |
notice(s_NickServ, u->nick, " %s", ngi->access[i]); |
162 |
} |
163 |
put_nickinfo(ni); |
164 |
put_nickgroupinfo(ngi); |
165 |
|
166 |
} else if (!cmd || ((stricmp(cmd,"LIST")==0) ? mask!=NULL : mask==NULL)) { |
167 |
syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX); |
168 |
|
169 |
} else if (mask && !strchr(mask, '@')) { |
170 |
notice_lang(s_NickServ, u, BAD_USERHOST_MASK); |
171 |
notice_lang(s_NickServ, u, MORE_INFO, s_NickServ, "ACCESS"); |
172 |
|
173 |
} else if (ngi = u->ngi, !(ni = u->ni)) { |
174 |
notice_lang(s_NickServ, u, NICK_NOT_REGISTERED); |
175 |
|
176 |
} else if (!user_identified(u)) { |
177 |
notice_lang(s_NickServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ); |
178 |
|
179 |
} else if (stricmp(cmd, "ADD") == 0) { |
180 |
if (readonly) { |
181 |
notice_lang(s_NickServ, u, NICK_ACCESS_DISABLED); |
182 |
return; |
183 |
} |
184 |
if (ngi->access_count >= NSAccessMax) { |
185 |
notice_lang(s_NickServ, u, NICK_ACCESS_REACHED_LIMIT, NSAccessMax); |
186 |
return; |
187 |
} |
188 |
ARRAY_FOREACH (i, ngi->access) { |
189 |
if (stricmp(ngi->access[i], mask) == 0) { |
190 |
notice_lang(s_NickServ, u, NICK_ACCESS_ALREADY_PRESENT, mask); |
191 |
return; |
192 |
} |
193 |
} |
194 |
if (strchr(mask, '!')) |
195 |
notice_lang(s_NickServ, u, NICK_ACCESS_NO_NICKS); |
196 |
ARRAY_EXTEND(ngi->access); |
197 |
ngi->access[ngi->access_count-1] = sstrdup(mask); |
198 |
notice_lang(s_NickServ, u, NICK_ACCESS_ADDED, mask); |
199 |
|
200 |
} else if (stricmp(cmd, "DEL") == 0) { |
201 |
if (readonly) { |
202 |
notice_lang(s_NickServ, u, NICK_ACCESS_DISABLED); |
203 |
return; |
204 |
} |
205 |
/* First try for an exact match; then, a case-insensitive one. */ |
206 |
ARRAY_SEARCH_PLAIN(ngi->access, mask, strcmp, i); |
207 |
if (i == ngi->access_count) |
208 |
ARRAY_SEARCH_PLAIN(ngi->access, mask, stricmp, i); |
209 |
if (i == ngi->access_count) { |
210 |
notice_lang(s_NickServ, u, NICK_ACCESS_NOT_FOUND, mask); |
211 |
return; |
212 |
} |
213 |
notice_lang(s_NickServ, u, NICK_ACCESS_DELETED, ngi->access[i]); |
214 |
free(ngi->access[i]); |
215 |
ARRAY_REMOVE(ngi->access, i); |
216 |
|
217 |
} else if (stricmp(cmd, "LIST") == 0) { |
218 |
if (ngi->access_count == 0) { |
219 |
notice_lang(s_NickServ, u, NICK_ACCESS_LIST_EMPTY); |
220 |
} else { |
221 |
notice_lang(s_NickServ, u, NICK_ACCESS_LIST); |
222 |
ARRAY_FOREACH (i, ngi->access) |
223 |
notice(s_NickServ, u->nick, " %s", ngi->access[i]); |
224 |
} |
225 |
|
226 |
} else { |
227 |
syntax_error(s_NickServ, u, "ACCESS", NICK_ACCESS_SYNTAX); |
228 |
|
229 |
} |
230 |
} |
231 |
|
232 |
/*************************************************************************/ |
233 |
/*************************** Callback routines ***************************/ |
234 |
/*************************************************************************/ |
235 |
|
236 |
/* Nick-registration callback (initializes access list). */ |
237 |
|
238 |
static int do_registered(User *u, NickInfo *ni, NickGroupInfo *ngi, |
239 |
int *replied) |
240 |
{ |
241 |
if (NSFirstAccessEnable) { |
242 |
ngi->access_count = 1; |
243 |
ngi->access = smalloc(sizeof(char *)); |
244 |
if (NSFirstAccessWild) { |
245 |
ngi->access[0] = create_mask(u, 0); |
246 |
} else { |
247 |
ngi->access[0] = smalloc(strlen(u->username)+strlen(u->host)+2); |
248 |
sprintf(ngi->access[0], "%s@%s", u->username, u->host); |
249 |
} |
250 |
} |
251 |
return 0; |
252 |
} |
253 |
|
254 |
/*************************************************************************/ |
255 |
|
256 |
/* Check whether a user is on the access list of the nick they're using. |
257 |
* Return 1 if on the access list, 0 if not. |
258 |
*/ |
259 |
|
260 |
static int check_on_access(User *u) |
261 |
{ |
262 |
int i; |
263 |
char buf[BUFSIZE]; |
264 |
|
265 |
if (!u->ni || !u->ngi) { |
266 |
module_log("check_on_access() BUG: ni or ngi is NULL!"); |
267 |
return 0; |
268 |
} |
269 |
if (u->ngi->access_count == 0) |
270 |
return 0; |
271 |
i = strlen(u->username); |
272 |
snprintf(buf, sizeof(buf), "%s@%s", u->username, u->host); |
273 |
ARRAY_FOREACH (i, u->ngi->access) { |
274 |
if (match_wild_nocase(u->ngi->access[i], buf)) |
275 |
return 1; |
276 |
} |
277 |
return 0; |
278 |
} |
279 |
|
280 |
/*************************************************************************/ |
281 |
/***************************** Module stuff ******************************/ |
282 |
/*************************************************************************/ |
283 |
|
284 |
ConfigDirective module_config[] = { |
285 |
{ "NSAccessMax", { { CD_POSINT, CF_DIRREQ, &NSAccessMax } } }, |
286 |
{ "NSFirstAccessEnable",{{CD_SET, 0, &NSFirstAccessEnable } } }, |
287 |
{ "NSFirstAccessWild",{ { CD_SET, 0, &NSFirstAccessWild } } }, |
288 |
{ NULL } |
289 |
}; |
290 |
|
291 |
/*************************************************************************/ |
292 |
|
293 |
int init_module() |
294 |
{ |
295 |
if (NSAccessMax > MAX_NICK_ACCESS) { |
296 |
module_log("NSAccessMax upper-bounded at MAX_NICK_ACCESS (%d)", |
297 |
MAX_NICK_ACCESS); |
298 |
NSAccessMax = MAX_NICK_ACCESS; |
299 |
} |
300 |
|
301 |
module_nickserv = find_module("nickserv/main"); |
302 |
if (!module_nickserv) { |
303 |
module_log("Main NickServ module not loaded"); |
304 |
return 0; |
305 |
} |
306 |
use_module(module_nickserv); |
307 |
|
308 |
if (!register_commands(module_nickserv, cmds)) { |
309 |
module_log("Unable to register commands"); |
310 |
exit_module(0); |
311 |
return 0; |
312 |
} |
313 |
|
314 |
if (!add_callback(module_nickserv, "check recognized", check_on_access) |
315 |
|| !add_callback(module_nickserv, "registered", do_registered) |
316 |
) { |
317 |
module_log("Unable to add callbacks"); |
318 |
exit_module(0); |
319 |
return 0; |
320 |
} |
321 |
|
322 |
if (!register_dbtable(&access_dbtable)) { |
323 |
module_log("Unable to register database table"); |
324 |
exit_module(0); |
325 |
return 0; |
326 |
} |
327 |
|
328 |
return 1; |
329 |
} |
330 |
|
331 |
/*************************************************************************/ |
332 |
|
333 |
int exit_module(int shutdown_unused) |
334 |
{ |
335 |
unregister_dbtable(&access_dbtable); |
336 |
|
337 |
if (module_nickserv) { |
338 |
remove_callback(module_nickserv, "registered", do_registered); |
339 |
remove_callback(module_nickserv, "check recognized", check_on_access); |
340 |
unregister_commands(module_nickserv, cmds); |
341 |
unuse_module(module_nickserv); |
342 |
module_nickserv = NULL; |
343 |
} |
344 |
|
345 |
return 1; |
346 |
} |
347 |
|
348 |
/*************************************************************************/ |
349 |
|
350 |
/* |
351 |
* Local variables: |
352 |
* c-file-style: "stroustrup" |
353 |
* c-file-offsets: ((case-label . *) (statement-case-intro . *)) |
354 |
* indent-tabs-mode: nil |
355 |
* End: |
356 |
* |
357 |
* vim: expandtab shiftwidth=4: |
358 |
*/ |