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