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. */ |
---|
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 | |
---|
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 | |
---|
63 | void nick_set(bee_user_t *bu, const char *nick) |
---|
64 | { |
---|
65 | nick_set_raw(bu->ic->acc, bu->handle, nick); |
---|
66 | } |
---|
67 | |
---|
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 | |
---|
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! */ |
---|
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 | |
---|
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. */ |
---|
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 | |
---|
286 | void nick_del(bee_user_t *bu) |
---|
287 | { |
---|
288 | g_hash_table_remove(bu->ic->acc->nicks, bu->handle); |
---|
289 | } |
---|
290 | |
---|
291 | |
---|
292 | void nick_strip(irc_t *irc, char *nick) |
---|
293 | { |
---|
294 | int len = 0; |
---|
295 | gboolean nick_underscores = set_getbool(&irc->b->set, "nick_underscores"); |
---|
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 | |
---|
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 | |
---|
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 | |
---|
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 | } |
---|