1 /*
2  * ngIRCd -- The Next Generation IRC Daemon
3  * Copyright (c)2001-2014 Alexander Barton (alex@barton.de) and Contributors.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  * Please read the file COPYING, README and AUTHORS for more information.
10  */
11 
12 #include "portab.h"
13 
14 /**
15  * @file
16  * Handler for IRC capability ("CAP") commands
17  */
18 
19 #include <assert.h>
20 #include <string.h>
21 #include <strings.h>
22 
23 #include "conn.h"
24 #include "channel.h"
25 #include "client-cap.h"
26 #include "irc-write.h"
27 #include "log.h"
28 #include "login.h"
29 #include "messages.h"
30 #include "parse.h"
31 
32 #include "irc-cap.h"
33 
34 /* Local functions */
35 
36 /**
37  * Set CAP negotiation status and mark client as "supports capabilities".
38  *
39  * @param Client The client to handle.
40  */
41 static void
Set_CAP_Negotiation(CLIENT * Client)42 Set_CAP_Negotiation(CLIENT *Client)
43 {
44 	assert(Client != NULL);
45 
46 	if (Client_Type(Client) != CLIENT_USER)
47 		Client_CapAdd(Client, CLIENT_CAP_PENDING);
48 	Client_CapAdd(Client, CLIENT_CAP_SUPPORTED);
49 }
50 
51 /**
52  * Parse capability string and return numeric flag value.
53  *
54  * @param Args The string containing space-separated capability names.
55  * @return Changed capability flags or 0 on error.
56  */
57 static int
Parse_CAP(int Capabilities,char * Args)58 Parse_CAP(int Capabilities, char *Args)
59 {
60 	static char tmp[COMMAND_LEN];
61 	char *ptr;
62 
63 	assert(Args != NULL);
64 
65 	strlcpy(tmp, Args, sizeof(tmp));
66 
67 	ptr = strtok(tmp, " ");
68 	while (ptr) {
69 		if (*ptr == '-') {
70 			/* drop capabilities */
71 			ptr++;
72 			if (strcmp(ptr, "multi-prefix") == 0)
73 				Capabilities &= ~CLIENT_CAP_MULTI_PREFIX;
74 			else
75 				return -1;
76 		} else {
77 			/* request capabilities */
78 			if (strcmp(ptr, "multi-prefix") == 0)
79 				Capabilities |= CLIENT_CAP_MULTI_PREFIX;
80 			else
81 				return -1;
82 		}
83 		ptr = strtok(NULL, " ");
84 	}
85 
86 	return Capabilities;
87 }
88 
89 /**
90  * Return textual representation of capability flags.
91  *
92  * Please note: this function returns a pointer to a global buffer and
93  * therefore isn't thread safe!
94  *
95  * @param Capabilities Capability flags (bitmask).
96  * @return Pointer to textual representation.
97  */
98 static char *
Get_CAP_String(int Capabilities)99 Get_CAP_String(int Capabilities)
100 {
101 	static char txt[COMMAND_LEN];
102 
103 	txt[0] = '\0';
104 
105 	if (Capabilities & CLIENT_CAP_MULTI_PREFIX)
106 		strlcat(txt, "multi-prefix ", sizeof(txt));
107 
108 	return txt;
109 }
110 
111 /**
112  * Handler for the IRCv3 sub-command "CAP LS".
113  *
114  * @param Client The client from which this command has been received.
115  * @param Arg Command argument or NULL.
116  * @return CONNECTED or DISCONNECTED.
117  */
118 static bool
Handle_CAP_LS(CLIENT * Client,UNUSED char * Arg)119 Handle_CAP_LS(CLIENT *Client, UNUSED char *Arg)
120 {
121 	assert(Client != NULL);
122 
123 	Set_CAP_Negotiation(Client);
124 
125 	return IRC_WriteStrClient(Client,
126 				  "CAP %s LS :multi-prefix",
127 				  Client_ID(Client));
128 }
129 
130 /**
131  * Handler for the IRCv3 sub-command "CAP LIST".
132  *
133  * @param Client The client from which this command has been received.
134  * @param Arg Command argument or NULL.
135  * @return CONNECTED or DISCONNECTED.
136  */
137 static bool
Handle_CAP_LIST(CLIENT * Client,UNUSED char * Arg)138 Handle_CAP_LIST(CLIENT *Client, UNUSED char *Arg)
139 {
140 	assert(Client != NULL);
141 
142 	return IRC_WriteStrClient(Client, "CAP %s LIST :%s", Client_ID(Client),
143 				  Get_CAP_String(Client_Cap(Client)));
144 }
145 
146 /**
147  * Handler for the IRCv3 sub-command "CAP REQ".
148  *
149  * @param Client The client from which this command has been received.
150  * @param Arg Command argument.
151  * @return CONNECTED or DISCONNECTED.
152  */
153 static bool
Handle_CAP_REQ(CLIENT * Client,char * Arg)154 Handle_CAP_REQ(CLIENT *Client, char *Arg)
155 {
156 	int new_cap;
157 
158 	assert(Client != NULL);
159 	assert(Arg != NULL);
160 
161 	Set_CAP_Negotiation(Client);
162 
163 	new_cap = Parse_CAP(Client_Cap(Client), Arg);
164 
165 	if (new_cap < 0)
166 		return IRC_WriteStrClient(Client, "CAP %s NAK :%s",
167 					  Client_ID(Client), Arg);
168 
169 	Client_CapSet(Client, new_cap);
170 	return IRC_WriteStrClient(Client, "CAP %s ACK :%s",
171 				  Client_ID(Client), Arg);
172 }
173 
174 /**
175  * Handler for the IRCv3 sub-command "CAP ACK".
176  *
177  * @param Client The client from which this command has been received.
178  * @param Arg Command argument.
179  * @return CONNECTED or DISCONNECTED.
180  */
181 static bool
Handle_CAP_ACK(UNUSED CLIENT * Client,UNUSED char * Arg)182 Handle_CAP_ACK(UNUSED CLIENT *Client, UNUSED char *Arg)
183 {
184 	assert(Client != NULL);
185 	assert(Arg != NULL);
186 
187 	return CONNECTED;
188 }
189 
190 /**
191  * Handler for the IRCv3 sub-command "CAP CLEAR".
192  *
193  * @param Client The client from which this command has been received.
194  * @return CONNECTED or DISCONNECTED.
195  */
196 static bool
Handle_CAP_CLEAR(CLIENT * Client)197 Handle_CAP_CLEAR(CLIENT *Client)
198 {
199 	int cap_old;
200 
201 	assert(Client != NULL);
202 
203 	cap_old = Client_Cap(Client);
204 	if (cap_old & CLIENT_CAP_MULTI_PREFIX)
205 		Client_CapDel(Client, CLIENT_CAP_MULTI_PREFIX);
206 
207 	return IRC_WriteStrClient(Client, "CAP %s ACK :%s", Client_ID(Client),
208 				  Get_CAP_String(cap_old));
209 }
210 
211 /**
212  * Handler for the IRCv3 sub-command "CAP END".
213  *
214  * @param Client The client from which this command has been received.
215  * @return CONNECTED or DISCONNECTED.
216  */
217 static bool
Handle_CAP_END(CLIENT * Client)218 Handle_CAP_END(CLIENT *Client)
219 {
220 	assert(Client != NULL);
221 
222 	if (Client_Type(Client) != CLIENT_USER) {
223 		/* User is still logging in ... */
224 		Client_CapDel(Client, CLIENT_CAP_PENDING);
225 
226 		if (Client_Type(Client) == CLIENT_WAITCAPEND) {
227 			/* Only "CAP END" was missing: log in! */
228 			return Login_User(Client);
229 		}
230 	}
231 
232 	return CONNECTED;
233 }
234 
235 /* Global functions */
236 
237 /**
238  * Handler for the IRCv3 command "CAP".
239  *
240  * @param Client The client from which this command has been received.
241  * @param Req Request structure with prefix and all parameters.
242  * @return CONNECTED or DISCONNECTED.
243  */
244 GLOBAL bool
IRC_CAP(CLIENT * Client,REQUEST * Req)245 IRC_CAP(CLIENT *Client, REQUEST *Req)
246 {
247 	assert(Client != NULL);
248 	assert(Req != NULL);
249 
250 	LogDebug("Got \"%s %s\" command from \"%s\" ...",
251 		 Req->command, Req->argv[0], Client_ID(Client));
252 
253 	if (Req->argc == 1) {
254 		if (strcasecmp(Req->argv[0], "CLEAR") == 0)
255 			return Handle_CAP_CLEAR(Client);
256 		if (strcasecmp(Req->argv[0], "END") == 0)
257 			return Handle_CAP_END(Client);
258 	}
259 	if (Req->argc >= 1 && Req->argc <= 2) {
260 		if (strcasecmp(Req->argv[0], "LS") == 0)
261 			return Handle_CAP_LS(Client, Req->argv[1]);
262 		if (strcasecmp(Req->argv[0], "LIST") == 0)
263 			return Handle_CAP_LIST(Client, Req->argv[1]);
264 	}
265 	if (Req->argc == 2) {
266 		if (strcasecmp(Req->argv[0], "REQ") == 0)
267 			return Handle_CAP_REQ(Client, Req->argv[1]);
268 		if (strcasecmp(Req->argv[0], "ACK") == 0)
269 			return Handle_CAP_ACK(Client, Req->argv[1]);
270 	}
271 
272 	return IRC_WriteErrClient(Client, ERR_INVALIDCAP_MSG,
273 				  Client_ID(Client), Req->argv[0]);
274 }
275 
276 /* -eof- */
277