1 /*
2 * Copyright (c) 2006-2007 Atheme Development Group
3 * Rights to this code are as documented in doc/LICENSE.
4 *
5 * Changes and shows nickname access lists.
6 *
7 */
8
9 #include "atheme.h"
10
11 DECLARE_MODULE_V1
12 (
13 "nickserv/access", false, _modinit, _moddeinit,
14 PACKAGE_STRING,
15 VENDOR_STRING
16 );
17
18 static void ns_cmd_access(sourceinfo_t *si, int parc, char *parv[]);
19
20 command_t ns_access = { "ACCESS", N_("Changes and shows your nickname access list."), AC_NONE, 2, ns_cmd_access, { .path = "nickserv/access" } };
21
_modinit(module_t * m)22 void _modinit(module_t *m)
23 {
24 service_named_bind_command("nickserv", &ns_access);
25
26 use_myuser_access++;
27 }
28
_moddeinit(module_unload_intent_t intent)29 void _moddeinit(module_unload_intent_t intent)
30 {
31 service_named_unbind_command("nickserv", &ns_access);
32
33 use_myuser_access--;
34 }
35
username_is_random(const char * name)36 static bool username_is_random(const char *name)
37 {
38 const char *p;
39 int lower = 0, upper = 0, digit = 0;
40
41 if (*name == '~')
42 name++;
43 if (strlen(name) < 9)
44 return false;
45 p = name;
46 while (*p != '\0')
47 {
48 if (isdigit((unsigned char)*p))
49 digit++;
50 else if (isupper((unsigned char)*p))
51 upper++;
52 else if (islower((unsigned char)*p))
53 lower++;
54 p++;
55 }
56 if (digit >= 4 && lower + upper > 1)
57 return true;
58 if (lower == 0 || upper == 0 || (upper <= 2 && isupper(*name)))
59 return false;
60 return true;
61 }
62
construct_mask(user_t * u)63 static char *construct_mask(user_t *u)
64 {
65 static char mask[USERLEN+HOSTLEN];
66 const char *dynhosts[] = { "*dyn*.*", "*dial*.*.*", "*dhcp*.*.*",
67 "*.t-online.??", "*.t-online.???",
68 "*.t-dialin.??", "*.t-dialin.???",
69 "*.t-ipconnect.??", "*.t-ipconnect.???",
70 "*.ipt.aol.com", NULL };
71 int i;
72 bool hostisdyn = false, havedigits;
73 const char *p, *prevdot, *lastdot;
74
75 for (i = 0; dynhosts[i] != NULL; i++)
76 if (!match(dynhosts[i], u->host))
77 hostisdyn = true;
78 if (hostisdyn)
79 {
80 /* note that all dyn patterns contain a dot */
81 p = u->host;
82 prevdot = u->host;
83 lastdot = strrchr(u->host, '.');
84 havedigits = true;
85 while (*p)
86 {
87 if (*p == '.')
88 {
89 if (!havedigits || p == lastdot || !strcasecmp(p, ".Level3.net"))
90 break;
91 prevdot = p;
92 havedigits = false;
93 }
94 else if (isdigit((unsigned char)*p))
95 havedigits = true;
96 p++;
97 }
98 snprintf(mask, sizeof mask, "%s@*%s", u->user, prevdot);
99 }
100 else if (username_is_random(u->user))
101 snprintf(mask, sizeof mask, "*@%s", u->host);
102 else if (u->ip != NULL && !strcmp(u->host, u->ip) &&
103 (p = strrchr(u->ip, '.')) != NULL)
104 snprintf(mask, sizeof mask, "%s@%.*s.0/24", u->user, (int)(p - u->ip), u->ip);
105 else
106 snprintf(mask, sizeof mask, "%s@%s", u->user, u->host);
107 return mask;
108 }
109
mangle_wildcard_to_cidr(const char * host,char * dest,size_t destlen)110 static bool mangle_wildcard_to_cidr(const char *host, char *dest, size_t destlen)
111 {
112 int i;
113 const char *p;
114
115 p = host;
116
117 if ((p[0] != '0' || p[1] != '.') && ((i = atoi(p)) < 1 || i > 255))
118 return false;
119 while (isdigit((unsigned char)*p))
120 p++;
121 if (*p++ != '.')
122 return false;
123 if (p[0] == '*' && p[1] == '\0')
124 {
125 snprintf(dest, destlen, "%.*s0.0.0/8", (int)(p - host), host);
126 return true;
127 }
128
129 if ((p[0] != '0' || p[1] != '.') && ((i = atoi(p)) < 1 || i > 255))
130 return false;
131 while (isdigit((unsigned char)*p))
132 p++;
133 if (*p++ != '.')
134 return false;
135 if (p[0] == '*' && (p[1] == '\0' || (p[1] == '.' && p[2] == '*' && p[3] == '\0')))
136 {
137 snprintf(dest, destlen, "%.*s0.0/16", (int)(p - host), host);
138 return true;
139 }
140
141 if ((p[0] != '0' || p[1] != '.') && ((i = atoi(p)) < 1 || i > 255))
142 return false;
143 while (isdigit((unsigned char)*p))
144 p++;
145 if (*p++ != '.')
146 return false;
147 if (p[0] == '*' && p[1] == '\0')
148 {
149 snprintf(dest, destlen, "%.*s0/24", (int)(p - host), host);
150 return true;
151 }
152
153 return false;
154 }
155
myuser_access_delete_enforce(myuser_t * mu,char * mask)156 static void myuser_access_delete_enforce(myuser_t *mu, char *mask)
157 {
158 mowgli_list_t l = {NULL, NULL, 0};
159 mowgli_node_t *n, *tn;
160 mynick_t *mn;
161 user_t *u;
162 hook_nick_enforce_t hdata;
163
164 /* find users who get access via the access list */
165 MOWGLI_ITER_FOREACH(n, mu->nicks.head)
166 {
167 mn = n->data;
168 u = user_find_named(mn->nick);
169 if (u != NULL && u->myuser != mu && myuser_access_verify(u, mu))
170 mowgli_node_add(u, mowgli_node_create(), &l);
171 }
172 /* remove mask */
173 myuser_access_delete(mu, mask);
174 /* check if those users still have access */
175 MOWGLI_ITER_FOREACH_SAFE(n, tn, l.head)
176 {
177 u = n->data;
178 mowgli_node_delete(n, &l);
179 mowgli_node_free(n);
180 if (!myuser_access_verify(u, mu))
181 {
182 mn = mynick_find(u->nick);
183 if (mn != NULL)
184 {
185 hdata.u = u;
186 hdata.mn = mn;
187 hook_call_nick_enforce(&hdata);
188 }
189 }
190 }
191 }
192
ns_cmd_access(sourceinfo_t * si,int parc,char * parv[])193 static void ns_cmd_access(sourceinfo_t *si, int parc, char *parv[])
194 {
195 myuser_t *mu;
196 mowgli_node_t *n;
197 char *mask;
198 char *host;
199 char *p;
200 char mangledmask[NICKLEN+HOSTLEN+10];
201
202 if (parc < 1)
203 {
204 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACCESS");
205 command_fail(si, fault_needmoreparams, _("Syntax: ACCESS ADD|DEL|LIST [mask]"));
206 return;
207 }
208
209 if (!strcasecmp(parv[0], "LIST"))
210 {
211 if (parc < 2)
212 {
213 mu = si->smu;
214 if (mu == NULL)
215 {
216 command_fail(si, fault_noprivs, _("You are not logged in."));
217 return;
218 }
219 }
220 else
221 {
222 if (!has_priv(si, PRIV_USER_AUSPEX))
223 {
224 command_fail(si, fault_noprivs, _("You are not authorized to use the target argument."));
225 return;
226 }
227
228 if (!(mu = myuser_find_ext(parv[1])))
229 {
230 command_fail(si, fault_badparams, _("\2%s\2 is not registered."), parv[1]);
231 return;
232 }
233 }
234
235 if (mu != si->smu)
236 logcommand(si, CMDLOG_ADMIN, "ACCESS:LIST: \2%s\2", entity(mu)->name);
237 else
238 logcommand(si, CMDLOG_GET, "ACCESS:LIST");
239
240 command_success_nodata(si, _("Access list for \2%s\2:"), entity(mu)->name);
241
242 MOWGLI_ITER_FOREACH(n, mu->access_list.head)
243 {
244 mask = n->data;
245 command_success_nodata(si, "- %s", mask);
246 }
247
248 command_success_nodata(si, _("End of \2%s\2 access list."), entity(mu)->name);
249 }
250 else if (!strcasecmp(parv[0], "ADD"))
251 {
252 mu = si->smu;
253 if (parc < 2)
254 {
255 if (si->su == NULL)
256 {
257 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACCESS ADD");
258 command_fail(si, fault_needmoreparams, _("Syntax: ACCESS ADD <mask>"));
259 return;
260 }
261 else
262 mask = construct_mask(si->su);
263 }
264 else
265 mask = parv[1];
266 if (mu == NULL)
267 {
268 command_fail(si, fault_noprivs, _("You are not logged in."));
269 return;
270 }
271 if (mask[0] == '*' && mask[1] == '!')
272 mask += 2;
273 if (strlen(mask) >= USERLEN + HOSTLEN)
274 {
275 command_fail(si, fault_badparams, _("Invalid mask \2%s\2."), parv[1]);
276 return;
277 }
278 p = mask;
279 while (*p != '\0')
280 {
281 if (!isprint((unsigned char)*p) || *p == ' ' || *p == '!')
282 {
283 command_fail(si, fault_badparams, _("Invalid mask \2%s\2."), parv[1]);
284 return;
285 }
286 p++;
287 }
288 host = strchr(mask, '@');
289 if (host == NULL) /* account name access masks? */
290 {
291 command_fail(si, fault_badparams, _("Invalid mask \2%s\2."), parv[1]);
292 return;
293 }
294 host++;
295 /* try mangling to cidr */
296 mowgli_strlcpy(mangledmask, mask, sizeof mangledmask);
297 if (mangle_wildcard_to_cidr(host, mangledmask + (host - mask), sizeof mangledmask - (host - mask)))
298 host = mangledmask + (host - mask), mask = mangledmask;
299 /* more checks */
300 if (si->su != NULL && (!strcasecmp(host, si->su->host) || !strcasecmp(host, si->su->vhost)))
301 ; /* it's their host, allow it */
302 else if (host[0] == '.' || host[0] == ':' || host[0] == '\0' || host[1] == '\0' || host == mask + 1 || strchr(host, '@') || strstr(host, ".."))
303 {
304 command_fail(si, fault_badparams, _("Invalid mask \2%s\2."), parv[1]);
305 return;
306 }
307 else if ((strchr(host, '*') || strchr(host, '?')) && (mask[0] == '*' && mask[1] == '@'))
308 {
309 /* can't use * username and wildcarded host */
310 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
311 return;
312 }
313 else if ((p = strrchr(host, '/')) != NULL)
314 {
315 if (isdigit((unsigned char)p[1]) && (atoi(p + 1) < 16 || (mask[0] == '*' && mask[1] == '@')))
316 {
317 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
318 return;
319 }
320 if (host[0] == '*')
321 {
322 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
323 return;
324 }
325 }
326 else
327 {
328 if (strchr(host, ':'))
329 {
330 /* No wildcarded IPs */
331 if (strchr(host, '?') || strchr(host, '*'))
332 {
333 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
334 return;
335 }
336 }
337 else
338 {
339 p = strrchr(host, '.');
340 if (p == NULL)
341 p = host;
342
343 /* No wildcarded IPs */
344 if (isdigit((unsigned char)p[1]) && (strchr(host, '*') || strchr(host, '?')))
345 {
346 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
347 return;
348 }
349 /* Require non-wildcard top and second level
350 * domain */
351 if (strchr(p, '?') || strchr(p, '*'))
352 {
353 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
354 return;
355 }
356
357 if (p != host)
358 {
359 p--;
360 while (p >= host && *p != '.')
361 {
362 if (*p == '?' || *p == '*')
363 {
364 command_fail(si, fault_badparams, _("Too wide mask \2%s\2."), parv[1]);
365 return;
366 }
367 p--;
368 }
369 }
370 }
371 }
372 if (myuser_access_find(mu, mask))
373 {
374 command_fail(si, fault_nochange, _("Mask \2%s\2 is already on your access list."), mask);
375 return;
376 }
377 if (myuser_access_add(mu, mask))
378 {
379 command_success_nodata(si, _("Added mask \2%s\2 to your access list."), mask);
380 logcommand(si, CMDLOG_SET, "ACCESS:ADD: \2%s\2", mask);
381 }
382 else
383 command_fail(si, fault_toomany, _("Your access list is full."));
384 }
385 else if (!strcasecmp(parv[0], "DEL"))
386 {
387 if (parc < 2)
388 {
389 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ACCESS DEL");
390 command_fail(si, fault_needmoreparams, _("Syntax: ACCESS DEL <mask>"));
391 return;
392 }
393 mu = si->smu;
394 if (mu == NULL)
395 {
396 command_fail(si, fault_noprivs, _("You are not logged in."));
397 return;
398 }
399 if ((mask = myuser_access_find(mu, parv[1])) == NULL)
400 {
401 command_fail(si, fault_nochange, _("Mask \2%s\2 is not on your access list."), parv[1]);
402 return;
403 }
404 command_success_nodata(si, _("Deleted mask \2%s\2 from your access list."), mask);
405 logcommand(si, CMDLOG_SET, "ACCESS:DEL: \2%s\2", mask);
406 myuser_access_delete_enforce(mu, mask);
407 }
408 else
409 {
410 command_fail(si, fault_needmoreparams, STR_INVALID_PARAMS, "ACCESS");
411 command_fail(si, fault_needmoreparams, _("Syntax: ACCESS ADD|DEL|LIST [mask]"));
412 return;
413 }
414 }
415
416 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
417 * vim:ts=8
418 * vim:sw=8
419 * vim:noexpandtab
420 */
421