1 /*
2  * atheme-services: A collection of minimalist IRC services
3  * commandtree.c: Management of services commands.
4  *
5  * Copyright (c) 2005-2010 Atheme Project (http://www.atheme.org)
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21  * POSSIBILITY OF SUCH DAMAGE.
22  */
23 
24 #include "atheme.h"
25 #include "privs.h"
26 
27 static int text_to_parv(char *text, int maxparc, char **parv);
28 
command_add(command_t * cmd,mowgli_patricia_t * commandtree)29 void command_add(command_t *cmd, mowgli_patricia_t *commandtree)
30 {
31 	return_if_fail(cmd != NULL);
32 	return_if_fail(commandtree != NULL);
33 
34 	mowgli_patricia_add(commandtree, cmd->name, cmd);
35 }
36 
command_delete(command_t * cmd,mowgli_patricia_t * commandtree)37 void command_delete(command_t *cmd, mowgli_patricia_t *commandtree)
38 {
39 	return_if_fail(cmd != NULL);
40 	return_if_fail(commandtree != NULL);
41 
42 	mowgli_patricia_delete(commandtree, cmd->name);
43 }
44 
command_find(mowgli_patricia_t * commandtree,const char * command)45 command_t *command_find(mowgli_patricia_t *commandtree, const char *command)
46 {
47 	return_val_if_fail(commandtree != NULL, NULL);
48 	return_val_if_fail(command != NULL, NULL);
49 
50 	return mowgli_patricia_retrieve(commandtree, command);
51 }
52 
53 static bool permissive_mode_fallback = false;
default_command_authorize(service_t * svs,sourceinfo_t * si,command_t * c,const char * userlevel)54 static bool default_command_authorize(service_t *svs, sourceinfo_t *si, command_t *c, const char *userlevel)
55 {
56 	if (!(has_priv(si, c->access) && has_priv(si, userlevel)))
57 	{
58 		if (!permissive_mode_fallback)
59 			logaudit_denycmd(si, c, userlevel);
60 		return false;
61 	}
62 
63 	return true;
64 }
65 bool (*command_authorize)(service_t *svs, sourceinfo_t *si, command_t *c, const char *userlevel) = default_command_authorize;
66 
command_verify(service_t * svs,sourceinfo_t * si,command_t * c,const char * userlevel)67 static inline bool command_verify(service_t *svs, sourceinfo_t *si, command_t *c, const char *userlevel)
68 {
69 	if (command_authorize(svs, si, c, userlevel))
70 		return true;
71 
72 	if (permissive_mode)
73 	{
74 		bool ret;
75 
76 		permissive_mode_fallback = true;
77 		ret = default_command_authorize(svs, si, c, userlevel);
78 		permissive_mode_fallback = false;
79 
80 		return ret;
81 	}
82 
83 	return false;
84 }
85 
command_exec(service_t * svs,sourceinfo_t * si,command_t * c,int parc,char * parv[])86 void command_exec(service_t *svs, sourceinfo_t *si, command_t *c, int parc, char *parv[])
87 {
88 	const char *cmdaccess;
89 
90 	if (si->smu != NULL)
91 		language_set_active(si->smu->language);
92 
93 	/* Make this look a bit more expected for normal users */
94 	if (si->smu == NULL && c->access != NULL && !strcasecmp(c->access, AC_AUTHENTICATED))
95 	{
96 		command_fail(si, fault_noprivs, _("You are not logged in."));
97 		language_set_active(NULL);
98 		return;
99 	}
100 
101 	cmdaccess = service_set_access(svs, c->name, c->access);
102 
103 	if (command_verify(svs, si, c, cmdaccess))
104 	{
105 		if (si->force_language != NULL)
106 			language_set_active(si->force_language);
107 
108 		si->command = c;
109 		c->cmd(si, parc, parv);
110 		language_set_active(NULL);
111 		return;
112 	}
113 
114 	if (has_any_privs(si))
115 	{
116 		char accessbuf[BUFSIZE];
117 
118 		*accessbuf = '\0';
119 
120 		if (c->access && (!cmdaccess || strcmp(c->access, cmdaccess)))
121 		{
122 			mowgli_strlcat(accessbuf, c->access, BUFSIZE);
123 			mowgli_strlcat(accessbuf, " ", BUFSIZE);
124 		}
125 
126 		mowgli_strlcat(accessbuf, cmdaccess, BUFSIZE);
127 
128 		command_fail(si, fault_noprivs, STR_NO_PRIVILEGE, accessbuf);
129 	}
130 	else
131 		command_fail(si, fault_noprivs, _("You are not authorized to perform this operation."));
132 	/* logcommand(si, CMDLOG_ADMIN, "DENIED COMMAND: \2%s\2 used \2%s\2 \2%s\2", origin, svs->name, cmd); */
133 	if (si->smu != NULL)
134 		language_set_active(NULL);
135 }
136 
command_exec_split(service_t * svs,sourceinfo_t * si,const char * cmd,char * text,mowgli_patricia_t * commandtree)137 void command_exec_split(service_t *svs, sourceinfo_t *si, const char *cmd, char *text, mowgli_patricia_t *commandtree)
138 {
139 	int parc, i;
140 	char *parv[20];
141         command_t *c;
142 
143 	cmd = service_resolve_alias(svs, commandtree == svs->commands ? NULL : "unknown", cmd);
144 	if ((c = command_find(commandtree, cmd)))
145 	{
146 		parc = text_to_parv(text, c->maxparc, parv);
147 		for (i = parc; i < (int)(sizeof(parv) / sizeof(parv[0])); i++)
148 			parv[i] = NULL;
149 		command_exec(svs, si, c, parc, parv);
150 	}
151 	else
152 	{
153 		if (si->smu != NULL)
154 			language_set_active(si->smu->language);
155 
156 		notice(svs->nick, si->su->nick, _("Invalid command. Use \2/%s%s help\2 for a command listing."), (ircd->uses_rcommand == false) ? "msg " : "", svs->disp);
157 
158 		if (si->smu != NULL)
159 			language_set_active(NULL);
160 	}
161 }
162 
163 /*
164  * command_help
165  *     Iterates the command tree and lists available commands.
166  *
167  * inputs -
168  *     si:          The origin of the request.
169  *     commandtree: The command tree being listed.
170  *
171  * outputs -
172  *     A list of available commands.
173  */
command_help(sourceinfo_t * si,mowgli_patricia_t * commandtree)174 void command_help(sourceinfo_t *si, mowgli_patricia_t *commandtree)
175 {
176 	mowgli_patricia_iteration_state_t state;
177 	command_t *c;
178 
179 	if (si->service == NULL || si->service->commands == commandtree)
180 		command_success_nodata(si, _("The following commands are available:"));
181 	else
182 		command_success_nodata(si, _("The following subcommands are available:"));
183 
184 	MOWGLI_PATRICIA_FOREACH(c, &state, commandtree)
185 	{
186 		/* show only the commands we have access to
187 		 * (taken from command_exec())
188 		 */
189 		if (has_priv(si, c->access) || (c->access != NULL && !strcasecmp(c->access, AC_AUTHENTICATED) && si->smu != NULL))
190 			command_success_nodata(si, "\2%-15s\2 %s", c->name, translation_get(_(c->desc)));
191 	}
192 }
193 
194 /* name1 name2 name3... */
string_in_list(const char * str,const char * name)195 static bool string_in_list(const char *str, const char *name)
196 {
197 	char *p;
198 	int l;
199 
200 	if (str == NULL)
201 		return false;
202 	l = strlen(name);
203 	while (*str != '\0')
204 	{
205 		p = strchr(str, ' ');
206 		if (p != NULL ? p - str == l && !strncasecmp(str, name, p - str) : !strcasecmp(str, name))
207 			return true;
208 		if (p == NULL)
209 			return false;
210 		str = p;
211 		while (*str == ' ')
212 			str++;
213 	}
214 	return false;
215 }
216 
217 /*
218  * command_help_short
219  *     Iterates over the command tree and lists available commands.
220  *
221  * inputs -
222  *     mynick:      The nick of the services bot sending out the notices.
223  *     origin:      The origin of the request.
224  *     commandtree: The command tree being listed.
225  *     maincmds:    The commands to list verbosely.
226  *
227  * outputs -
228  *     A list of available commands.
229  */
command_help_short(sourceinfo_t * si,mowgli_patricia_t * commandtree,const char * maincmds)230 void command_help_short(sourceinfo_t *si, mowgli_patricia_t *commandtree, const char *maincmds)
231 {
232 	mowgli_patricia_iteration_state_t state;
233 	unsigned int l, lv;
234 	char buf[256], *p;
235 	command_t *c;
236 
237 	if (si->service == NULL || si->service->commands == commandtree)
238 		command_success_nodata(si, _("The following commands are available:"));
239 	else
240 		command_success_nodata(si, _("The following subcommands are available:"));
241 
242 	MOWGLI_PATRICIA_FOREACH(c, &state, commandtree)
243 	{
244 		/* show only the commands we have access to
245 		 * (taken from command_exec())
246 		 */
247 		if (string_in_list(maincmds, c->name) && (has_priv(si, c->access) || (c->access != NULL && !strcasecmp(c->access, AC_AUTHENTICATED) && si->smu != NULL)))
248 			command_success_nodata(si, "\2%-15s\2 %s", c->name, translation_get(_(c->desc)));
249 	}
250 
251 	command_success_nodata(si, " ");
252 	mowgli_strlcpy(buf, translation_get(_("\2Other commands:\2 ")), sizeof buf);
253 	l = strlen(buf);
254 	lv = 0;
255 	for (p = buf; *p != '\0'; p++)
256 	{
257 		if (!(*p >= '\1' && *p < ' '))
258 			lv++;
259 	}
260 
261 	MOWGLI_PATRICIA_FOREACH(c, &state, commandtree)
262 	{
263 		/* show only the commands we have access to
264 		 * (taken from command_exec())
265 		 */
266 		if (!string_in_list(maincmds, c->name) && (has_priv(si, c->access) || (c->access != NULL && !strcasecmp(c->access, AC_AUTHENTICATED) && si->smu != NULL)))
267 		{
268 			if (strlen(buf) > l)
269 				mowgli_strlcat(buf, ", ", sizeof buf);
270 			if (strlen(buf) > 55)
271 			{
272 				command_success_nodata(si, "%s", buf);
273 				l = lv;
274 				buf[lv] = '\0';
275 				while (--lv > 0)
276 					buf[lv] = ' ';
277 				buf[0] = ' ';
278 				lv = l;
279 			}
280 			mowgli_strlcat(buf, c->name, sizeof buf);
281 		}
282 	}
283 	if (strlen(buf) > l)
284 		command_success_nodata(si, "%s", buf);
285 }
286 
text_to_parv(char * text,int maxparc,char ** parv)287 static int text_to_parv(char *text, int maxparc, char **parv)
288 {
289 	int count = 0;
290 	char *p;
291 
292         if (maxparc == 0)
293         	return 0;
294 
295 	if (!text)
296 		return 0;
297 
298 	p = text;
299 	while (count < maxparc - 1 && (parv[count] = strtok(p, " ")) != NULL)
300 		count++, p = NULL;
301 
302 	if ((parv[count] = strtok(p, "")) != NULL)
303 	{
304 		p = parv[count];
305 		while (*p == ' ')
306 			p++;
307 		parv[count] = p;
308 		if (*p != '\0')
309 		{
310 			p += strlen(p) - 1;
311 			while (*p == ' ' && p > parv[count])
312 				p--;
313 			p[1] = '\0';
314 			count++;
315 		}
316 	}
317 	return count;
318 }
319 
320 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
321  * vim:ts=8
322  * vim:sw=8
323  * vim:noexpandtab
324  */
325