1 /* X-Chat
2  * Copyright (C) 1998 Peter Zelezny.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #include "hexchat.h"
24 #include "modes.h"
25 #include "fe.h"
26 #include "notify.h"
27 #include "tree.h"
28 #include "hexchatc.h"
29 #include "util.h"
30 
31 
32 int
nick_cmp_az_ops(server * serv,struct User * user1,struct User * user2)33 nick_cmp_az_ops (server *serv, struct User *user1, struct User *user2)
34 {
35 	unsigned int access1 = user1->access;
36 	unsigned int access2 = user2->access;
37 	int pos;
38 
39 	if (access1 != access2)
40 	{
41 		for (pos = 0; pos < USERACCESS_SIZE; pos++)
42 		{
43 			if ((access1&(1<<pos)) && (access2&(1<<pos)))
44 				break;
45 			if ((access1&(1<<pos)) && !(access2&(1<<pos)))
46 				return -1;
47 			if (!(access1&(1<<pos)) && (access2&(1<<pos)))
48 				return 1;
49 		}
50 	}
51 
52 	return serv->p_cmp (user1->nick, user2->nick);
53 }
54 
55 int
nick_cmp_alpha(struct User * user1,struct User * user2,server * serv)56 nick_cmp_alpha (struct User *user1, struct User *user2, server *serv)
57 {
58 	return serv->p_cmp (user1->nick, user2->nick);
59 }
60 
61 /*
62  insert name in appropriate place in linked list. Returns row number or:
63   -1: duplicate
64 */
65 
66 static int
userlist_insertname(session * sess,struct User * newuser)67 userlist_insertname (session *sess, struct User *newuser)
68 {
69 	if (!sess->usertree)
70 	{
71 		sess->usertree = tree_new ((tree_cmp_func *)nick_cmp_alpha, sess->server);
72 	}
73 
74 	return tree_insert (sess->usertree, newuser);
75 }
76 
77 void
userlist_set_away(struct session * sess,char * nick,unsigned int away)78 userlist_set_away (struct session *sess, char *nick, unsigned int away)
79 {
80 	struct User *user;
81 
82 	user = userlist_find (sess, nick);
83 	if (user)
84 	{
85 		if (user->away != away)
86 		{
87 			user->away = away;
88 			/* rehash GUI */
89 			fe_userlist_rehash (sess, user);
90 			if (away)
91 				fe_userlist_update (sess, user);
92 		}
93 	}
94 }
95 
96 void
userlist_set_account(struct session * sess,char * nick,char * account)97 userlist_set_account (struct session *sess, char *nick, char *account)
98 {
99 	struct User *user;
100 
101 	user = userlist_find (sess, nick);
102 	if (user)
103 	{
104 		if (strcmp (account, "*") == 0)
105 		{
106 			g_clear_pointer (&user->account, g_free);
107 		} else if (g_strcmp0 (user->account, account))
108 		{
109 			g_free (user->account);
110 			user->account = g_strdup (account);
111 		}
112 
113 		/* gui doesnt currently reflect login status, maybe later
114 		fe_userlist_rehash (sess, user); */
115 	}
116 }
117 
118 int
userlist_add_hostname(struct session * sess,char * nick,char * hostname,char * realname,char * servername,char * account,unsigned int away)119 userlist_add_hostname (struct session *sess, char *nick, char *hostname,
120 							  char *realname, char *servername, char *account, unsigned int away)
121 {
122 	struct User *user;
123 	gboolean do_rehash = FALSE;
124 
125 	user = userlist_find (sess, nick);
126 	if (user)
127 	{
128 		if (hostname && (!user->hostname || strcmp(user->hostname, hostname)))
129 		{
130 			if (prefs.hex_gui_ulist_show_hosts)
131 				do_rehash = TRUE;
132 			g_free (user->hostname);
133 			user->hostname = g_strdup (hostname);
134 		}
135 		if (realname && *realname && g_strcmp0 (user->realname, realname) != 0)
136 		{
137 			g_free (user->realname);
138 			user->realname = g_strdup (realname);
139 		}
140 		if (!user->servername && servername)
141 			user->servername = g_strdup (servername);
142 		if (!user->account && account && strcmp (account, "0") != 0)
143 			user->account = g_strdup (account);
144 		if (away != 0xff)
145 		{
146 			if (user->away != away)
147 				do_rehash = TRUE;
148 			user->away = away;
149 		}
150 
151 		fe_userlist_update (sess, user);
152 		if (do_rehash)
153 			fe_userlist_rehash (sess, user);
154 
155 		return 1;
156 	}
157 	return 0;
158 }
159 
160 static int
free_user(struct User * user,gpointer data)161 free_user (struct User *user, gpointer data)
162 {
163 	g_free (user->realname);
164 	g_free (user->hostname);
165 	g_free (user->servername);
166 	g_free (user->account);
167 	g_free (user);
168 
169 	return TRUE;
170 }
171 
172 void
userlist_free(session * sess)173 userlist_free (session *sess)
174 {
175 	tree_foreach (sess->usertree, (tree_traverse_func *)free_user, NULL);
176 	tree_destroy (sess->usertree);
177 
178 	sess->usertree = NULL;
179 	sess->me = NULL;
180 
181 	sess->ops = 0;
182 	sess->hops = 0;
183 	sess->voices = 0;
184 	sess->total = 0;
185 }
186 
187 void
userlist_clear(session * sess)188 userlist_clear (session *sess)
189 {
190 	fe_userlist_clear (sess);
191 	userlist_free (sess);
192 	fe_userlist_numbers (sess);
193 }
194 
195 static int
find_cmp(const char * name,struct User * user,server * serv)196 find_cmp (const char *name, struct User *user, server *serv)
197 {
198 	return serv->p_cmp ((char *)name, user->nick);
199 }
200 
201 struct User *
userlist_find(struct session * sess,const char * name)202 userlist_find (struct session *sess, const char *name)
203 {
204 	int pos;
205 
206 	if (sess->usertree)
207 		return tree_find (sess->usertree, name,
208 								(tree_cmp_func *)find_cmp, sess->server, &pos);
209 
210 	return NULL;
211 }
212 
213 struct User *
userlist_find_global(struct server * serv,char * name)214 userlist_find_global (struct server *serv, char *name)
215 {
216 	struct User *user;
217 	session *sess;
218 	GSList *list = sess_list;
219 	while (list)
220 	{
221 		sess = (session *) list->data;
222 		if (sess->server == serv)
223 		{
224 			user = userlist_find (sess, name);
225 			if (user)
226 				return user;
227 		}
228 		list = list->next;
229 	}
230 	return NULL;
231 }
232 
233 static void
update_counts(session * sess,struct User * user,char prefix,int level,int offset)234 update_counts (session *sess, struct User *user, char prefix,
235 					int level, int offset)
236 {
237 	switch (prefix)
238 	{
239 	case '@':
240 		user->op = level;
241 		sess->ops += offset;
242 		break;
243 	case '%':
244 		user->hop = level;
245 		sess->hops += offset;
246 		break;
247 	case '+':
248 		user->voice = level;
249 		sess->voices += offset;
250 		break;
251 	}
252 }
253 
254 void
userlist_update_mode(session * sess,char * name,char mode,char sign)255 userlist_update_mode (session *sess, char *name, char mode, char sign)
256 {
257 	int access;
258 	int offset = 0;
259 	int level;
260 	int pos;
261 	char prefix;
262 	struct User *user;
263 
264 	user = userlist_find (sess, name);
265 	if (!user)
266 		return;
267 
268 	/* remove from binary trees, before we loose track of it */
269 	tree_remove (sess->usertree, user, &pos);
270 	fe_userlist_remove (sess, user);
271 
272 	/* which bit number is affected? */
273 	access = mode_access (sess->server, mode, &prefix);
274 
275 	if (sign == '+')
276 	{
277 		level = TRUE;
278 		if (!(user->access & (1 << access)))
279 		{
280 			offset = 1;
281 			user->access |= (1 << access);
282 		}
283 	} else
284 	{
285 		level = FALSE;
286 		if (user->access & (1 << access))
287 		{
288 			offset = -1;
289 			user->access &= ~(1 << access);
290 		}
291 	}
292 
293 	/* now what is this users highest prefix? e.g. @ for ops */
294 	user->prefix[0] = get_nick_prefix (sess->server, user->access);
295 
296 	/* update the various counts using the CHANGED prefix only */
297 	update_counts (sess, user, prefix, level, offset);
298 
299 	/* insert it back into its new place */
300 	tree_insert (sess->usertree, user);
301 	fe_userlist_insert (sess, user, FALSE);
302 	fe_userlist_numbers (sess);
303 }
304 
305 int
userlist_change(struct session * sess,char * oldname,char * newname)306 userlist_change (struct session *sess, char *oldname, char *newname)
307 {
308 	struct User *user = userlist_find (sess, oldname);
309 	int pos;
310 
311 	if (user)
312 	{
313 		tree_remove (sess->usertree, user, &pos);
314 		fe_userlist_remove (sess, user);
315 
316 		safe_strcpy (user->nick, newname, NICKLEN);
317 
318 		tree_insert (sess->usertree, user);
319 		fe_userlist_insert (sess, user, FALSE);
320 
321 		return 1;
322 	}
323 
324 	return 0;
325 }
326 
327 int
userlist_remove(struct session * sess,char * name)328 userlist_remove (struct session *sess, char *name)
329 {
330 	struct User *user;
331 
332 	user = userlist_find (sess, name);
333 	if (!user)
334 		return FALSE;
335 
336 	userlist_remove_user (sess, user);
337 	return TRUE;
338 }
339 
340 void
userlist_remove_user(struct session * sess,struct User * user)341 userlist_remove_user (struct session *sess, struct User *user)
342 {
343 	int pos;
344 	if (user->voice)
345 		sess->voices--;
346 	if (user->op)
347 		sess->ops--;
348 	if (user->hop)
349 		sess->hops--;
350 	sess->total--;
351 	fe_userlist_numbers (sess);
352 	fe_userlist_remove (sess, user);
353 
354 	if (user == sess->me)
355 		sess->me = NULL;
356 
357 	tree_remove (sess->usertree, user, &pos);
358 	free_user (user, NULL);
359 }
360 
361 void
userlist_add(struct session * sess,char * name,char * hostname,char * account,char * realname,const message_tags_data * tags_data)362 userlist_add (struct session *sess, char *name, char *hostname,
363 				  char *account, char *realname, const message_tags_data *tags_data)
364 {
365 	struct User *user;
366 	int row, prefix_chars;
367 	unsigned int acc;
368 
369 	acc = nick_access (sess->server, name, &prefix_chars);
370 
371 	notify_set_online (sess->server, name + prefix_chars, tags_data);
372 
373 	user = g_new0 (struct User, 1);
374 
375 	user->access = acc;
376 
377 	/* assume first char is the highest level nick prefix */
378 	if (prefix_chars)
379 		user->prefix[0] = name[0];
380 
381 	/* add it to our linked list */
382 	if (hostname)
383 		user->hostname = g_strdup (hostname);
384 	safe_strcpy (user->nick, name + prefix_chars, NICKLEN);
385 	/* is it me? */
386 	if (!sess->server->p_cmp (user->nick, sess->server->nick))
387 		user->me = TRUE;
388 	/* extended join info */
389 	if (sess->server->have_extjoin)
390 	{
391 		if (account && *account)
392 			user->account = g_strdup (account);
393 		if (realname && *realname)
394 			user->realname = g_strdup (realname);
395 	}
396 
397 	row = userlist_insertname (sess, user);
398 
399 	/* duplicate? some broken servers trigger this */
400 	if (row == -1)
401 	{
402 		g_free (user->hostname);
403 		g_free (user->account);
404 		g_free (user->realname);
405 		g_free (user);
406 		return;
407 	}
408 
409 	sess->total++;
410 
411 	/* most ircds don't support multiple modechars in front of the nickname
412       for /NAMES - though they should. */
413 	while (prefix_chars)
414 	{
415 		update_counts (sess, user, name[0], TRUE, 1);
416 		name++;
417 		prefix_chars--;
418 	}
419 
420 	if (user->me)
421 		sess->me = user;
422 
423 	fe_userlist_insert (sess, user, FALSE);
424 	if(sess->end_of_names)
425 		fe_userlist_numbers (sess);
426 }
427 
428 static int
rehash_cb(struct User * user,session * sess)429 rehash_cb (struct User *user, session *sess)
430 {
431 	fe_userlist_rehash (sess, user);
432 	return TRUE;
433 }
434 
435 void
userlist_rehash(session * sess)436 userlist_rehash (session *sess)
437 {
438 	tree_foreach (sess->usertree, (tree_traverse_func *)rehash_cb, sess);
439 }
440 
441 static int
flat_cb(struct User * user,GSList ** list)442 flat_cb (struct User *user, GSList **list)
443 {
444 	*list = g_slist_prepend (*list, user);
445 	return TRUE;
446 }
447 
448 GSList *
userlist_flat_list(session * sess)449 userlist_flat_list (session *sess)
450 {
451 	GSList *list = NULL;
452 
453 	tree_foreach (sess->usertree, (tree_traverse_func *)flat_cb, &list);
454 	return g_slist_reverse (list);
455 }
456 
457 static int
double_cb(struct User * user,GList ** list)458 double_cb (struct User *user, GList **list)
459 {
460 	*list = g_list_prepend(*list, user);
461 	return TRUE;
462 }
463 
464 GList *
userlist_double_list(session * sess)465 userlist_double_list(session *sess)
466 {
467 	GList *list = NULL;
468 
469 	tree_foreach (sess->usertree, (tree_traverse_func *)double_cb, &list);
470 	return list;
471 }
472