1 /********************************************************************\
2   * BitlBee -- An IRC to other IM-networks gateway                     *
3   *                                                                    *
4   * Copyright 2002-2012 Wilmer van der Gaast and others                *
5   \********************************************************************/
6 
7 /* Some stuff to fetch, save and handle nicknames for your buddies      */
8 
9 /*
10   This program is free software; you can redistribute it and/or modify
11   it under the terms of the GNU General Public License as published by
12   the Free Software Foundation; either version 2 of the License, or
13   (at your option) any later version.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License with
21   the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22   if not, write to the Free Software Foundation, Inc., 51 Franklin St.,
23   Fifth Floor, Boston, MA  02110-1301  USA
24 */
25 
26 #define BITLBEE_CORE
27 #include "bitlbee.h"
28 
29 /* Character maps, _lc_[x] == _uc_[x] (but uppercase), according to the RFC's.
30    With one difference, we allow dashes. These are used to do uc/lc conversions
31    and strip invalid chars. */
32 static char *nick_lc_chars = "0123456789abcdefghijklmnopqrstuvwxyz{}^`-_|";
33 static char *nick_uc_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]~`-_\\";
34 
35 /* Store handles in lower case and strip spaces, because AIM is braindead. */
clean_handle(const char * orig)36 static char *clean_handle(const char *orig)
37 {
38 	char *new = g_malloc(strlen(orig) + 1);
39 	int i = 0;
40 
41 	do {
42 		if (*orig != ' ') {
43 			new[i++] = g_ascii_tolower(*orig);
44 		}
45 	} while (*(orig++));
46 
47 	return new;
48 }
49 
nick_set_raw(account_t * acc,const char * handle,const char * nick)50 void nick_set_raw(account_t *acc, const char *handle, const char *nick)
51 {
52 	char *store_handle, *store_nick = g_malloc(MAX_NICK_LENGTH + 1);
53 	irc_t *irc = (irc_t *) acc->bee->ui_data;
54 
55 	store_handle = clean_handle(handle);
56 	store_nick[MAX_NICK_LENGTH] = '\0';
57 	strncpy(store_nick, nick, MAX_NICK_LENGTH);
58 	nick_strip(irc, store_nick);
59 
60 	g_hash_table_replace(acc->nicks, store_handle, store_nick);
61 }
62 
nick_set(bee_user_t * bu,const char * nick)63 void nick_set(bee_user_t *bu, const char *nick)
64 {
65 	nick_set_raw(bu->ic->acc, bu->handle, nick);
66 }
67 
nick_get(bee_user_t * bu)68 char *nick_get(bee_user_t *bu)
69 {
70 	static char nick[MAX_NICK_LENGTH + 1];
71 	char *store_handle, *found_nick;
72 	irc_t *irc = (irc_t *) bu->bee->ui_data;
73 
74 	memset(nick, 0, MAX_NICK_LENGTH + 1);
75 
76 	store_handle = clean_handle(bu->handle);
77 	/* Find out if we stored a nick for this person already. If not, try
78 	   to generate a sane nick automatically. */
79 	if ((found_nick = g_hash_table_lookup(bu->ic->acc->nicks, store_handle))) {
80 		strncpy(nick, found_nick, MAX_NICK_LENGTH);
81 	} else if ((found_nick = nick_gen(bu))) {
82 		strncpy(nick, found_nick, MAX_NICK_LENGTH);
83 		g_free(found_nick);
84 	} else {
85 		/* Keep this fallback since nick_gen() can return NULL in some cases. */
86 		char *s;
87 
88 		g_snprintf(nick, MAX_NICK_LENGTH, "%s", bu->handle);
89 		if ((s = strchr(nick, '@'))) {
90 			while (*s) {
91 				*(s++) = 0;
92 			}
93 		}
94 
95 		nick_strip(irc, nick);
96 		if (set_getbool(&bu->bee->set, "nick_lowercase")) {
97 			nick_lc(irc, nick);
98 		}
99 	}
100 	g_free(store_handle);
101 
102 	/* Make sure the nick doesn't collide with an existing one by adding
103 	   underscores and that kind of stuff, if necessary. */
104 	nick_dedupe(bu, nick);
105 
106 	return nick;
107 }
108 
nick_gen(bee_user_t * bu)109 char *nick_gen(bee_user_t *bu)
110 {
111 	gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
112 	GString *ret = g_string_sized_new(MAX_NICK_LENGTH + 1);
113 	char *rets;
114 	irc_t *irc = (irc_t *) bu->bee->ui_data;
115 	char *fmt = set_getstr(&bu->ic->acc->set, "nick_format") ? :
116 	            set_getstr(&bu->bee->set, "nick_format");
117 
118 	while (fmt && *fmt && ret->len < MAX_NICK_LENGTH) {
119 		char *part = NULL, chop = '\0', *asc = NULL, *s;
120 		int len = INT_MAX;
121 
122 		if (*fmt != '%') {
123 			g_string_append_c(ret, *fmt);
124 			fmt++;
125 			continue;
126 		}
127 
128 		fmt++;
129 		while (*fmt) {
130 			/* -char means chop off everything from char */
131 			if (*fmt == '-') {
132 				chop = fmt[1];
133 				if (chop == '\0') {
134 					g_string_free(ret, TRUE);
135 					return NULL;
136 				}
137 				fmt += 2;
138 			} else if (g_ascii_isdigit(*fmt)) {
139 				len = 0;
140 				/* Grab a number. */
141 				while (g_ascii_isdigit(*fmt)) {
142 					len = len * 10 + (*(fmt++) - '0');
143 				}
144 			} else if (g_strncasecmp(fmt, "nick", 4) == 0) {
145 				part = bu->nick ? : bu->handle;
146 				fmt += 4;
147 				ok |= TRUE;
148 				break;
149 			} else if (g_strncasecmp(fmt, "handle", 6) == 0) {
150 				part = bu->handle;
151 				fmt += 6;
152 				ok |= TRUE;
153 				break;
154 			} else if (g_strncasecmp(fmt, "full_name", 9) == 0) {
155 				part = bu->fullname;
156 				fmt += 9;
157 				ok |= part && *part;
158 				break;
159 			} else if (g_strncasecmp(fmt, "first_name", 10) == 0) {
160 				part = bu->fullname;
161 				fmt += 10;
162 				ok |= part && *part;
163 				chop = ' ';
164 				break;
165 			} else if (g_strncasecmp(fmt, "group", 5) == 0) {
166 				part = bu->group ? bu->group->name : NULL;
167 				fmt += 5;
168 				break;
169 			} else if (g_strncasecmp(fmt, "account", 7) == 0) {
170 				part = bu->ic->acc->tag;
171 				fmt += 7;
172 				break;
173 			} else {
174 				g_string_free(ret, TRUE);
175 				return NULL;
176 			}
177 		}
178 
179 		if (!part) {
180 			continue;
181 		}
182 
183 		/* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
184 		   should do lossy/approximate conversions, so letters with
185 		   accents don't just get stripped. Note that it depends on
186 		   LC_CTYPE being set to something other than C/POSIX. */
187 		if (!(irc && irc->status & IRC_UTF8_NICKS)) {
188 			asc = g_convert_with_fallback(part, -1, "ASCII//TRANSLIT", "UTF-8", "", NULL, NULL, NULL);
189 
190 			if (!asc) {
191 				/* If above failed, try again without //TRANSLIT.
192 				   //TRANSLIT is a GNU iconv special and is not POSIX.
193 				   Other platforms may not support it. */
194 				asc = g_convert_with_fallback(part, -1, "ASCII", "UTF-8", "", NULL, NULL, NULL);
195 			}
196 
197 			part = asc;
198 		}
199 
200 		if (part && chop && (s = strchr(part, chop))) {
201 			len = MIN(len, s - part);
202 		}
203 
204 		if (part) {
205 			if (len < INT_MAX) {
206 				g_string_append_len(ret, part, len);
207 			} else {
208 				g_string_append(ret, part);
209 			}
210 		}
211 		g_free(asc);
212 	}
213 
214 	rets = g_string_free(ret, FALSE);
215 	if (ok && rets && *rets) {
216 		nick_strip(irc, rets);
217 
218 		if (set_getbool(&bu->bee->set, "nick_lowercase")) {
219 			nick_lc(irc, rets);
220 		}
221 
222 		truncate_utf8(rets, MAX_NICK_LENGTH);
223 		return rets;
224 	}
225 	g_free(rets);
226 	return NULL;
227 }
228 
229 /* Used for nicks and channel names too! */
underscore_dedupe(char nick[MAX_NICK_LENGTH+1])230 void underscore_dedupe(char nick[MAX_NICK_LENGTH + 1])
231 {
232 	if (strlen(nick) < (MAX_NICK_LENGTH - 1)) {
233 		nick[strlen(nick) + 1] = 0;
234 		nick[strlen(nick)] = '_';
235 	} else {
236 		/* We've got no more space for underscores,
237 		   so truncate it and replace the last three
238 		   chars with a random "_XX" suffix */
239 		int len = truncate_utf8(nick, MAX_NICK_LENGTH - 3);
240 		nick[len] = '_';
241 		g_snprintf(nick + len + 1, 3, "%2x", rand());
242 	}
243 }
244 
nick_dedupe(bee_user_t * bu,char nick[MAX_NICK_LENGTH+1])245 void nick_dedupe(bee_user_t *bu, char nick[MAX_NICK_LENGTH + 1])
246 {
247 	irc_t *irc = (irc_t *) bu->bee->ui_data;
248 	int inf_protection = 256;
249 	irc_user_t *iu;
250 
251 	/* Now, find out if the nick is already in use at the moment, and make
252 	   subtle changes to make it unique. */
253 	while (!nick_ok(irc, nick) ||
254 	       ((iu = irc_user_by_name(irc, nick)) && iu->bu != bu)) {
255 
256 		underscore_dedupe(nick);
257 
258 		if (inf_protection-- == 0) {
259 			g_snprintf(nick, MAX_NICK_LENGTH + 1, "xx%x", rand());
260 
261 			irc_rootmsg(irc, "Warning: Something went wrong while trying "
262 			            "to generate a nickname for contact %s on %s.",
263 			            bu->handle, bu->ic->acc->tag);
264 			irc_rootmsg(irc, "This might be a bug in BitlBee, or the result "
265 			            "of a faulty nick_format setting. Will use %s "
266 			            "instead.", nick);
267 
268 			break;
269 		}
270 	}
271 }
272 
273 /* Just check if there is a nickname set for this buddy or if we'd have to
274    generate one. */
nick_saved(bee_user_t * bu)275 int nick_saved(bee_user_t *bu)
276 {
277 	char *store_handle, *found;
278 
279 	store_handle = clean_handle(bu->handle);
280 	found = g_hash_table_lookup(bu->ic->acc->nicks, store_handle);
281 	g_free(store_handle);
282 
283 	return found != NULL;
284 }
285 
nick_del(bee_user_t * bu)286 void nick_del(bee_user_t *bu)
287 {
288 	g_hash_table_remove(bu->ic->acc->nicks, bu->handle);
289 }
290 
291 
nick_strip(irc_t * irc,char * nick)292 void nick_strip(irc_t *irc, char *nick)
293 {
294 	int len = 0;
295 	gboolean nick_underscores = irc ? set_getbool(&irc->b->set, "nick_underscores") : FALSE;
296 
297 	if (irc && (irc->status & IRC_UTF8_NICKS)) {
298 		gunichar c;
299 		char *p = nick, *n, tmp[strlen(nick) + 1];
300 
301 		while (p && *p) {
302 			c = g_utf8_get_char_validated(p, -1);
303 			n = g_utf8_find_next_char(p, NULL);
304 
305 			if (nick_underscores && c == ' ') {
306 				*p = '_';
307 				p = n;
308 			} else if ((c < 0x7f && !(strchr(nick_lc_chars, c) ||
309 			                          strchr(nick_uc_chars, c))) ||
310 			    !g_unichar_isgraph(c)) {
311 				strcpy(tmp, n);
312 				strcpy(p, tmp);
313 			} else {
314 				p = n;
315 			}
316 		}
317 		if (p) {
318 			len = p - nick;
319 		}
320 	} else {
321 		int i;
322 
323 		for (i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++) {
324 			if (nick_underscores && nick[i] == ' ') {
325 				nick[len] = '_';
326 				len++;
327 			} else if (strchr(nick_lc_chars, nick[i]) ||
328 			    strchr(nick_uc_chars, nick[i])) {
329 				nick[len] = nick[i];
330 				len++;
331 			}
332 		}
333 	}
334 	if (g_ascii_isdigit(nick[0])) {
335 		char *orig;
336 
337 		/* First character of a nick can't be a digit, so insert an
338 		   underscore if necessary. */
339 		orig = g_strdup(nick);
340 		g_snprintf(nick, MAX_NICK_LENGTH, "_%s", orig);
341 		g_free(orig);
342 		len++;
343 	}
344 	while (len <= MAX_NICK_LENGTH) {
345 		nick[len++] = '\0';
346 	}
347 }
348 
nick_ok(irc_t * irc,const char * nick)349 gboolean nick_ok(irc_t *irc, const char *nick)
350 {
351 	const char *s;
352 
353 	/* Empty/long nicks are not allowed, nor numbers at [0] */
354 	if (!*nick || g_ascii_isdigit(nick[0]) || strlen(nick) > MAX_NICK_LENGTH) {
355 		return 0;
356 	}
357 
358 	if (irc && (irc->status & IRC_UTF8_NICKS)) {
359 		gunichar c;
360 		const char *p = nick, *n;
361 
362 		while (p && *p) {
363 			c = g_utf8_get_char_validated(p, -1);
364 			n = g_utf8_find_next_char(p, NULL);
365 
366 			if ((c < 0x7f && !(strchr(nick_lc_chars, c) ||
367 			                   strchr(nick_uc_chars, c))) ||
368 			    !g_unichar_isgraph(c)) {
369 				return FALSE;
370 			}
371 			p = n;
372 		}
373 	} else {
374 		for (s = nick; *s; s++) {
375 			if (!strchr(nick_lc_chars, *s) && !strchr(nick_uc_chars, *s)) {
376 				return FALSE;
377 			}
378 		}
379 	}
380 
381 	return TRUE;
382 }
383 
nick_lc(irc_t * irc,char * nick)384 int nick_lc(irc_t *irc, char *nick)
385 {
386 	static char tab[128] = { 0 };
387 	int i;
388 
389 	if (tab['A'] == 0) {
390 		/* initialize table so nonchars are mapped to themselves */
391 		for (i = 0; i < sizeof(tab); i++) {
392 			tab[i] = i;
393 		}
394 		/* replace uppercase chars with lowercase chars */
395 		for (i = 0; nick_lc_chars[i]; i++) {
396 			tab[(int) nick_uc_chars[i]] = nick_lc_chars[i];
397 		}
398 	}
399 
400 	if (irc && (irc->status & IRC_UTF8_NICKS)) {
401 		gchar *down = g_utf8_strdown(nick, -1);
402 		if (strlen(down) > strlen(nick)) {
403 			truncate_utf8(down, strlen(nick));
404 		}
405 		strcpy(nick, down);
406 		g_free(down);
407 	}
408 
409 	for (i = 0; nick[i]; i++) {
410 		if (((guchar) nick[i]) < 0x7f) {
411 			nick[i] = tab[(guchar) nick[i]];
412 		}
413 	}
414 
415 	return nick_ok(irc, nick);
416 }
417 
nick_cmp(irc_t * irc,const char * a,const char * b)418 int nick_cmp(irc_t *irc, const char *a, const char *b)
419 {
420 	char aa[1024] = "", bb[1024] = "";
421 
422 	strncpy(aa, a, sizeof(aa) - 1);
423 	strncpy(bb, b, sizeof(bb) - 1);
424 	if (nick_lc(irc, aa) && nick_lc(irc, bb)) {
425 		return(strcmp(aa, bb));
426 	} else {
427 		return(-1);     /* Hmm... Not a clear answer.. :-/ */
428 	}
429 }
430