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