Mercurial > pidgin
comparison libpurple/protocols/silc10/buddy.c @ 17567:ba1b50f114f6
Duplicate the current SILC prpl as silc10 for backwards compatibility with
SILC Toolkit 1.0
author | Stu Tomlinson <stu@nosnilmot.com> |
---|---|
date | Sat, 09 Jun 2007 16:39:00 +0000 |
parents | |
children | ab6d2763b8d8 |
comparison
equal
deleted
inserted
replaced
17566:016eee704a96 | 17567:ba1b50f114f6 |
---|---|
1 /* | |
2 | |
3 silcpurple_buddy.c | |
4 | |
5 Author: Pekka Riikonen <priikone@silcnet.org> | |
6 | |
7 Copyright (C) 2004 Pekka Riikonen | |
8 | |
9 This program is free software; you can redistribute it and/or modify | |
10 it under the terms of the GNU General Public License as published by | |
11 the Free Software Foundation; version 2 of the License. | |
12 | |
13 This program is distributed in the hope that it will be useful, | |
14 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 GNU General Public License for more details. | |
17 | |
18 */ | |
19 | |
20 #include "silcincludes.h" | |
21 #include "silcclient.h" | |
22 #include "silcpurple.h" | |
23 #include "wb.h" | |
24 | |
25 /***************************** Key Agreement *********************************/ | |
26 | |
27 static void | |
28 silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data); | |
29 | |
30 static void | |
31 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name, | |
32 gboolean force_local); | |
33 | |
34 typedef struct { | |
35 char *nick; | |
36 PurpleConnection *gc; | |
37 } *SilcPurpleResolve; | |
38 | |
39 static void | |
40 silcpurple_buddy_keyagr_resolved(SilcClient client, | |
41 SilcClientConnection conn, | |
42 SilcClientEntry *clients, | |
43 SilcUInt32 clients_count, | |
44 void *context) | |
45 { | |
46 PurpleConnection *gc = client->application; | |
47 SilcPurpleResolve r = context; | |
48 char tmp[256]; | |
49 | |
50 if (!clients) { | |
51 g_snprintf(tmp, sizeof(tmp), | |
52 _("User %s is not present in the network"), r->nick); | |
53 purple_notify_error(gc, _("Key Agreement"), | |
54 _("Cannot perform the key agreement"), tmp); | |
55 silc_free(r->nick); | |
56 silc_free(r); | |
57 return; | |
58 } | |
59 | |
60 silcpurple_buddy_keyagr_do(gc, r->nick, FALSE); | |
61 silc_free(r->nick); | |
62 silc_free(r); | |
63 } | |
64 | |
65 typedef struct { | |
66 gboolean responder; | |
67 } *SilcPurpleKeyAgr; | |
68 | |
69 static void | |
70 silcpurple_buddy_keyagr_cb(SilcClient client, | |
71 SilcClientConnection conn, | |
72 SilcClientEntry client_entry, | |
73 SilcKeyAgreementStatus status, | |
74 SilcSKEKeyMaterial *key, | |
75 void *context) | |
76 { | |
77 PurpleConnection *gc = client->application; | |
78 SilcPurple sg = gc->proto_data; | |
79 SilcPurpleKeyAgr a = context; | |
80 | |
81 if (!sg->conn) | |
82 return; | |
83 | |
84 switch (status) { | |
85 case SILC_KEY_AGREEMENT_OK: | |
86 { | |
87 PurpleConversation *convo; | |
88 char tmp[128]; | |
89 | |
90 /* Set the private key for this client */ | |
91 silc_client_del_private_message_key(client, conn, client_entry); | |
92 silc_client_add_private_message_key_ske(client, conn, client_entry, | |
93 NULL, NULL, key, a->responder); | |
94 silc_ske_free_key_material(key); | |
95 | |
96 | |
97 /* Open IM window */ | |
98 convo = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, | |
99 client_entry->nickname, sg->account); | |
100 if (convo) { | |
101 /* we don't have windows in the core anymore...but we may want to | |
102 * provide some method for asking the UI to show the window | |
103 purple_conv_window_show(purple_conversation_get_window(convo)); | |
104 */ | |
105 } else { | |
106 convo = purple_conversation_new(PURPLE_CONV_TYPE_IM, sg->account, | |
107 client_entry->nickname); | |
108 } | |
109 g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname); | |
110 purple_conversation_set_title(convo, tmp); | |
111 } | |
112 break; | |
113 | |
114 case SILC_KEY_AGREEMENT_ERROR: | |
115 purple_notify_error(gc, _("Key Agreement"), | |
116 _("Error occurred during key agreement"), NULL); | |
117 break; | |
118 | |
119 case SILC_KEY_AGREEMENT_FAILURE: | |
120 purple_notify_error(gc, _("Key Agreement"), _("Key Agreement failed"), NULL); | |
121 break; | |
122 | |
123 case SILC_KEY_AGREEMENT_TIMEOUT: | |
124 purple_notify_error(gc, _("Key Agreement"), | |
125 _("Timeout during key agreement"), NULL); | |
126 break; | |
127 | |
128 case SILC_KEY_AGREEMENT_ABORTED: | |
129 purple_notify_error(gc, _("Key Agreement"), | |
130 _("Key agreement was aborted"), NULL); | |
131 break; | |
132 | |
133 case SILC_KEY_AGREEMENT_ALREADY_STARTED: | |
134 purple_notify_error(gc, _("Key Agreement"), | |
135 _("Key agreement is already started"), NULL); | |
136 break; | |
137 | |
138 case SILC_KEY_AGREEMENT_SELF_DENIED: | |
139 purple_notify_error(gc, _("Key Agreement"), | |
140 _("Key agreement cannot be started with yourself"), | |
141 NULL); | |
142 break; | |
143 | |
144 default: | |
145 break; | |
146 } | |
147 | |
148 silc_free(a); | |
149 } | |
150 | |
151 static void | |
152 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name, | |
153 gboolean force_local) | |
154 { | |
155 SilcPurple sg = gc->proto_data; | |
156 SilcClientEntry *clients; | |
157 SilcUInt32 clients_count; | |
158 char *local_ip = NULL, *remote_ip = NULL; | |
159 gboolean local = TRUE; | |
160 char *nickname; | |
161 SilcPurpleKeyAgr a; | |
162 | |
163 if (!sg->conn || !name) | |
164 return; | |
165 | |
166 if (!silc_parse_userfqdn(name, &nickname, NULL)) | |
167 return; | |
168 | |
169 /* Find client entry */ | |
170 clients = silc_client_get_clients_local(sg->client, sg->conn, nickname, name, | |
171 &clients_count); | |
172 if (!clients) { | |
173 /* Resolve unknown user */ | |
174 SilcPurpleResolve r = silc_calloc(1, sizeof(*r)); | |
175 if (!r) | |
176 return; | |
177 r->nick = g_strdup(name); | |
178 r->gc = gc; | |
179 silc_client_get_clients(sg->client, sg->conn, nickname, NULL, | |
180 silcpurple_buddy_keyagr_resolved, r); | |
181 silc_free(nickname); | |
182 return; | |
183 } | |
184 | |
185 /* Resolve the local IP from the outgoing socket connection. We resolve | |
186 it to check whether we have a private range IP address or public IP | |
187 address. If we have public then we will assume that we are not behind | |
188 NAT and will provide automatically the point of connection to the | |
189 agreement. If we have private range address we assume that we are | |
190 behind NAT and we let the responder provide the point of connection. | |
191 | |
192 The algorithm also checks the remote IP address of server connection. | |
193 If it is private range address and we have private range address we | |
194 assume that we are chatting in LAN and will provide the point of | |
195 connection. | |
196 | |
197 Naturally this algorithm does not always get things right. */ | |
198 | |
199 if (silc_net_check_local_by_sock(sg->conn->sock->sock, NULL, &local_ip)) { | |
200 /* Check if the IP is private */ | |
201 if (!force_local && silcpurple_ip_is_private(local_ip)) { | |
202 local = FALSE; | |
203 | |
204 /* Local IP is private, resolve the remote server IP to see whether | |
205 we are talking to Internet or just on LAN. */ | |
206 if (silc_net_check_host_by_sock(sg->conn->sock->sock, NULL, | |
207 &remote_ip)) | |
208 if (silcpurple_ip_is_private(remote_ip)) | |
209 /* We assume we are in LAN. Let's provide | |
210 the connection point. */ | |
211 local = TRUE; | |
212 } | |
213 } | |
214 | |
215 if (force_local) | |
216 local = TRUE; | |
217 | |
218 if (local && !local_ip) | |
219 local_ip = silc_net_localip(); | |
220 | |
221 a = silc_calloc(1, sizeof(*a)); | |
222 if (!a) | |
223 return; | |
224 a->responder = local; | |
225 | |
226 /* Send the key agreement request */ | |
227 silc_client_send_key_agreement(sg->client, sg->conn, clients[0], | |
228 local ? local_ip : NULL, NULL, 0, 60, | |
229 silcpurple_buddy_keyagr_cb, a); | |
230 | |
231 silc_free(local_ip); | |
232 silc_free(remote_ip); | |
233 silc_free(clients); | |
234 } | |
235 | |
236 typedef struct { | |
237 SilcClient client; | |
238 SilcClientConnection conn; | |
239 SilcClientID client_id; | |
240 char *hostname; | |
241 SilcUInt16 port; | |
242 } *SilcPurpleKeyAgrAsk; | |
243 | |
244 static void | |
245 silcpurple_buddy_keyagr_request_cb(SilcPurpleKeyAgrAsk a, gint id) | |
246 { | |
247 SilcPurpleKeyAgr ai; | |
248 SilcClientEntry client_entry; | |
249 | |
250 if (id != 1) | |
251 goto out; | |
252 | |
253 /* Get the client entry. */ | |
254 client_entry = silc_client_get_client_by_id(a->client, a->conn, | |
255 &a->client_id); | |
256 if (!client_entry) { | |
257 purple_notify_error(a->client->application, _("Key Agreement"), | |
258 _("The remote user is not present in the network any more"), | |
259 NULL); | |
260 goto out; | |
261 } | |
262 | |
263 /* If the hostname was provided by the requestor perform the key agreement | |
264 now. Otherwise, we will send him a request to connect to us. */ | |
265 if (a->hostname) { | |
266 ai = silc_calloc(1, sizeof(*ai)); | |
267 if (!ai) | |
268 goto out; | |
269 ai->responder = FALSE; | |
270 silc_client_perform_key_agreement(a->client, a->conn, client_entry, | |
271 a->hostname, a->port, | |
272 silcpurple_buddy_keyagr_cb, ai); | |
273 } else { | |
274 /* Send request. Force us as the point of connection since requestor | |
275 did not provide the point of connection. */ | |
276 silcpurple_buddy_keyagr_do(a->client->application, | |
277 client_entry->nickname, TRUE); | |
278 } | |
279 | |
280 out: | |
281 silc_free(a->hostname); | |
282 silc_free(a); | |
283 } | |
284 | |
285 void silcpurple_buddy_keyagr_request(SilcClient client, | |
286 SilcClientConnection conn, | |
287 SilcClientEntry client_entry, | |
288 const char *hostname, SilcUInt16 port) | |
289 { | |
290 char tmp[128], tmp2[128]; | |
291 SilcPurpleKeyAgrAsk a; | |
292 PurpleConnection *gc = client->application; | |
293 | |
294 g_snprintf(tmp, sizeof(tmp), | |
295 _("Key agreement request received from %s. Would you like to " | |
296 "perform the key agreement?"), client_entry->nickname); | |
297 if (hostname) | |
298 g_snprintf(tmp2, sizeof(tmp2), | |
299 _("The remote user is waiting key agreement on:\n" | |
300 "Remote host: %s\nRemote port: %d"), hostname, port); | |
301 | |
302 a = silc_calloc(1, sizeof(*a)); | |
303 if (!a) | |
304 return; | |
305 a->client = client; | |
306 a->conn = conn; | |
307 a->client_id = *client_entry->id; | |
308 if (hostname) | |
309 a->hostname = strdup(hostname); | |
310 a->port = port; | |
311 | |
312 purple_request_action(client->application, _("Key Agreement Request"), tmp, | |
313 hostname ? tmp2 : NULL, 1, gc->account, client_entry->nickname, | |
314 NULL, a, 2, _("Yes"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb), | |
315 _("No"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb)); | |
316 } | |
317 | |
318 static void | |
319 silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data) | |
320 { | |
321 PurpleBuddy *buddy; | |
322 | |
323 buddy = (PurpleBuddy *)node; | |
324 silcpurple_buddy_keyagr_do(buddy->account->gc, buddy->name, FALSE); | |
325 } | |
326 | |
327 | |
328 /**************************** Static IM Key **********************************/ | |
329 | |
330 static void | |
331 silcpurple_buddy_resetkey(PurpleBlistNode *node, gpointer data) | |
332 { | |
333 PurpleBuddy *b; | |
334 PurpleConnection *gc; | |
335 SilcPurple sg; | |
336 char *nickname; | |
337 SilcClientEntry *clients; | |
338 SilcUInt32 clients_count; | |
339 | |
340 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); | |
341 | |
342 b = (PurpleBuddy *) node; | |
343 gc = purple_account_get_connection(b->account); | |
344 sg = gc->proto_data; | |
345 | |
346 if (!silc_parse_userfqdn(b->name, &nickname, NULL)) | |
347 return; | |
348 | |
349 /* Find client entry */ | |
350 clients = silc_client_get_clients_local(sg->client, sg->conn, | |
351 nickname, b->name, | |
352 &clients_count); | |
353 if (!clients) { | |
354 silc_free(nickname); | |
355 return; | |
356 } | |
357 | |
358 clients[0]->prv_resp = FALSE; | |
359 silc_client_del_private_message_key(sg->client, sg->conn, | |
360 clients[0]); | |
361 silc_free(clients); | |
362 silc_free(nickname); | |
363 } | |
364 | |
365 typedef struct { | |
366 SilcClient client; | |
367 SilcClientConnection conn; | |
368 SilcClientID client_id; | |
369 } *SilcPurplePrivkey; | |
370 | |
371 static void | |
372 silcpurple_buddy_privkey(PurpleConnection *gc, const char *name); | |
373 | |
374 static void | |
375 silcpurple_buddy_privkey_cb(SilcPurplePrivkey p, const char *passphrase) | |
376 { | |
377 SilcClientEntry client_entry; | |
378 | |
379 if (!passphrase || !(*passphrase)) { | |
380 silc_free(p); | |
381 return; | |
382 } | |
383 | |
384 /* Get the client entry. */ | |
385 client_entry = silc_client_get_client_by_id(p->client, p->conn, | |
386 &p->client_id); | |
387 if (!client_entry) { | |
388 purple_notify_error(p->client->application, _("IM With Password"), | |
389 _("The remote user is not present in the network any more"), | |
390 NULL); | |
391 silc_free(p); | |
392 return; | |
393 } | |
394 | |
395 /* Set the private message key */ | |
396 silc_client_del_private_message_key(p->client, p->conn, | |
397 client_entry); | |
398 silc_client_add_private_message_key(p->client, p->conn, | |
399 client_entry, NULL, NULL, | |
400 (unsigned char *)passphrase, | |
401 strlen(passphrase), FALSE, | |
402 client_entry->prv_resp); | |
403 if (!client_entry->prv_resp) | |
404 silc_client_send_private_message_key_request(p->client, | |
405 p->conn, | |
406 client_entry); | |
407 silc_free(p); | |
408 } | |
409 | |
410 static void | |
411 silcpurple_buddy_privkey_resolved(SilcClient client, | |
412 SilcClientConnection conn, | |
413 SilcClientEntry *clients, | |
414 SilcUInt32 clients_count, | |
415 void *context) | |
416 { | |
417 char tmp[256]; | |
418 | |
419 if (!clients) { | |
420 g_snprintf(tmp, sizeof(tmp), | |
421 _("User %s is not present in the network"), | |
422 (const char *)context); | |
423 purple_notify_error(client->application, _("IM With Password"), | |
424 _("Cannot set IM key"), tmp); | |
425 g_free(context); | |
426 return; | |
427 } | |
428 | |
429 silcpurple_buddy_privkey(client->application, context); | |
430 silc_free(context); | |
431 } | |
432 | |
433 static void | |
434 silcpurple_buddy_privkey(PurpleConnection *gc, const char *name) | |
435 { | |
436 SilcPurple sg = gc->proto_data; | |
437 char *nickname; | |
438 SilcPurplePrivkey p; | |
439 SilcClientEntry *clients; | |
440 SilcUInt32 clients_count; | |
441 | |
442 if (!name) | |
443 return; | |
444 if (!silc_parse_userfqdn(name, &nickname, NULL)) | |
445 return; | |
446 | |
447 /* Find client entry */ | |
448 clients = silc_client_get_clients_local(sg->client, sg->conn, | |
449 nickname, name, | |
450 &clients_count); | |
451 if (!clients) { | |
452 silc_client_get_clients(sg->client, sg->conn, nickname, NULL, | |
453 silcpurple_buddy_privkey_resolved, | |
454 g_strdup(name)); | |
455 silc_free(nickname); | |
456 return; | |
457 } | |
458 | |
459 p = silc_calloc(1, sizeof(*p)); | |
460 if (!p) | |
461 return; | |
462 p->client = sg->client; | |
463 p->conn = sg->conn; | |
464 p->client_id = *clients[0]->id; | |
465 purple_request_input(gc, _("IM With Password"), NULL, | |
466 _("Set IM Password"), NULL, FALSE, TRUE, NULL, | |
467 _("OK"), G_CALLBACK(silcpurple_buddy_privkey_cb), | |
468 _("Cancel"), G_CALLBACK(silcpurple_buddy_privkey_cb), | |
469 gc->account, NULL, NULL, p); | |
470 | |
471 silc_free(clients); | |
472 silc_free(nickname); | |
473 } | |
474 | |
475 static void | |
476 silcpurple_buddy_privkey_menu(PurpleBlistNode *node, gpointer data) | |
477 { | |
478 PurpleBuddy *buddy; | |
479 PurpleConnection *gc; | |
480 | |
481 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); | |
482 | |
483 buddy = (PurpleBuddy *) node; | |
484 gc = purple_account_get_connection(buddy->account); | |
485 | |
486 silcpurple_buddy_privkey(gc, buddy->name); | |
487 } | |
488 | |
489 | |
490 /**************************** Get Public Key *********************************/ | |
491 | |
492 typedef struct { | |
493 SilcClient client; | |
494 SilcClientConnection conn; | |
495 SilcClientID client_id; | |
496 } *SilcPurpleBuddyGetkey; | |
497 | |
498 static void | |
499 silcpurple_buddy_getkey(PurpleConnection *gc, const char *name); | |
500 | |
501 static void | |
502 silcpurple_buddy_getkey_cb(SilcPurpleBuddyGetkey g, | |
503 SilcClientCommandReplyContext cmd) | |
504 { | |
505 SilcClientEntry client_entry; | |
506 unsigned char *pk; | |
507 SilcUInt32 pk_len; | |
508 | |
509 /* Get the client entry. */ | |
510 client_entry = silc_client_get_client_by_id(g->client, g->conn, | |
511 &g->client_id); | |
512 if (!client_entry) { | |
513 purple_notify_error(g->client->application, _("Get Public Key"), | |
514 _("The remote user is not present in the network any more"), | |
515 NULL); | |
516 silc_free(g); | |
517 return; | |
518 } | |
519 | |
520 if (!client_entry->public_key) { | |
521 silc_free(g); | |
522 return; | |
523 } | |
524 | |
525 /* Now verify the public key */ | |
526 pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len); | |
527 silcpurple_verify_public_key(g->client, g->conn, client_entry->nickname, | |
528 SILC_SOCKET_TYPE_CLIENT, | |
529 pk, pk_len, SILC_SKE_PK_TYPE_SILC, | |
530 NULL, NULL); | |
531 silc_free(pk); | |
532 silc_free(g); | |
533 } | |
534 | |
535 static void | |
536 silcpurple_buddy_getkey_resolved(SilcClient client, | |
537 SilcClientConnection conn, | |
538 SilcClientEntry *clients, | |
539 SilcUInt32 clients_count, | |
540 void *context) | |
541 { | |
542 char tmp[256]; | |
543 | |
544 if (!clients) { | |
545 g_snprintf(tmp, sizeof(tmp), | |
546 _("User %s is not present in the network"), | |
547 (const char *)context); | |
548 purple_notify_error(client->application, _("Get Public Key"), | |
549 _("Cannot fetch the public key"), tmp); | |
550 g_free(context); | |
551 return; | |
552 } | |
553 | |
554 silcpurple_buddy_getkey(client->application, context); | |
555 silc_free(context); | |
556 } | |
557 | |
558 static void | |
559 silcpurple_buddy_getkey(PurpleConnection *gc, const char *name) | |
560 { | |
561 SilcPurple sg = gc->proto_data; | |
562 SilcClient client = sg->client; | |
563 SilcClientConnection conn = sg->conn; | |
564 SilcClientEntry *clients; | |
565 SilcUInt32 clients_count; | |
566 SilcPurpleBuddyGetkey g; | |
567 char *nickname; | |
568 | |
569 if (!name) | |
570 return; | |
571 | |
572 if (!silc_parse_userfqdn(name, &nickname, NULL)) | |
573 return; | |
574 | |
575 /* Find client entry */ | |
576 clients = silc_client_get_clients_local(client, conn, nickname, name, | |
577 &clients_count); | |
578 if (!clients) { | |
579 silc_client_get_clients(client, conn, nickname, NULL, | |
580 silcpurple_buddy_getkey_resolved, | |
581 g_strdup(name)); | |
582 silc_free(nickname); | |
583 return; | |
584 } | |
585 | |
586 /* Call GETKEY */ | |
587 g = silc_calloc(1, sizeof(*g)); | |
588 if (!g) | |
589 return; | |
590 g->client = client; | |
591 g->conn = conn; | |
592 g->client_id = *clients[0]->id; | |
593 silc_client_command_call(client, conn, NULL, "GETKEY", | |
594 clients[0]->nickname, NULL); | |
595 silc_client_command_pending(conn, SILC_COMMAND_GETKEY, | |
596 conn->cmd_ident, | |
597 (SilcCommandCb)silcpurple_buddy_getkey_cb, g); | |
598 silc_free(clients); | |
599 silc_free(nickname); | |
600 } | |
601 | |
602 static void | |
603 silcpurple_buddy_getkey_menu(PurpleBlistNode *node, gpointer data) | |
604 { | |
605 PurpleBuddy *buddy; | |
606 PurpleConnection *gc; | |
607 | |
608 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); | |
609 | |
610 buddy = (PurpleBuddy *) node; | |
611 gc = purple_account_get_connection(buddy->account); | |
612 | |
613 silcpurple_buddy_getkey(gc, buddy->name); | |
614 } | |
615 | |
616 static void | |
617 silcpurple_buddy_showkey(PurpleBlistNode *node, gpointer data) | |
618 { | |
619 PurpleBuddy *b; | |
620 PurpleConnection *gc; | |
621 SilcPurple sg; | |
622 SilcPublicKey public_key; | |
623 const char *pkfile; | |
624 | |
625 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); | |
626 | |
627 b = (PurpleBuddy *) node; | |
628 gc = purple_account_get_connection(b->account); | |
629 sg = gc->proto_data; | |
630 | |
631 pkfile = purple_blist_node_get_string(node, "public-key"); | |
632 if (!silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_PEM) && | |
633 !silc_pkcs_load_public_key(pkfile, &public_key, SILC_PKCS_FILE_BIN)) { | |
634 purple_notify_error(gc, | |
635 _("Show Public Key"), | |
636 _("Could not load public key"), NULL); | |
637 return; | |
638 } | |
639 | |
640 silcpurple_show_public_key(sg, b->name, public_key, NULL, NULL); | |
641 silc_pkcs_public_key_free(public_key); | |
642 } | |
643 | |
644 | |
645 /**************************** Buddy routines *********************************/ | |
646 | |
647 /* The buddies are implemented by using the WHOIS and WATCH commands that | |
648 can be used to search users by their public key. Since nicknames aren't | |
649 unique in SILC we cannot trust the buddy list using their nickname. We | |
650 associate public keys to buddies and use those to search and watch | |
651 in the network. | |
652 | |
653 The problem is that Purple does not return PurpleBuddy contexts to the | |
654 callbacks but the buddy names. Naturally, this is not going to work | |
655 with SILC. But, for now, we have to do what we can... */ | |
656 | |
657 typedef struct { | |
658 SilcClient client; | |
659 SilcClientConnection conn; | |
660 SilcClientID client_id; | |
661 PurpleBuddy *b; | |
662 unsigned char *offline_pk; | |
663 SilcUInt32 offline_pk_len; | |
664 unsigned int offline : 1; | |
665 unsigned int pubkey_search : 1; | |
666 unsigned int init : 1; | |
667 } *SilcPurpleBuddyRes; | |
668 | |
669 static void | |
670 silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id); | |
671 static void | |
672 silcpurple_add_buddy_resolved(SilcClient client, | |
673 SilcClientConnection conn, | |
674 SilcClientEntry *clients, | |
675 SilcUInt32 clients_count, | |
676 void *context); | |
677 | |
678 void silcpurple_get_info(PurpleConnection *gc, const char *who) | |
679 { | |
680 SilcPurple sg = gc->proto_data; | |
681 SilcClient client = sg->client; | |
682 SilcClientConnection conn = sg->conn; | |
683 SilcClientEntry client_entry; | |
684 PurpleBuddy *b; | |
685 const char *filename, *nick = who; | |
686 char tmp[256]; | |
687 | |
688 if (!who) | |
689 return; | |
690 if (strlen(who) > 1 && who[0] == '@') | |
691 nick = who + 1; | |
692 if (strlen(who) > 1 && who[0] == '*') | |
693 nick = who + 1; | |
694 if (strlen(who) > 2 && who[0] == '*' && who[1] == '@') | |
695 nick = who + 2; | |
696 | |
697 b = purple_find_buddy(gc->account, nick); | |
698 if (b) { | |
699 /* See if we have this buddy's public key. If we do use that | |
700 to search the details. */ | |
701 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key"); | |
702 if (filename) { | |
703 /* Call WHOIS. The user info is displayed in the WHOIS | |
704 command reply. */ | |
705 silc_client_command_call(client, conn, NULL, "WHOIS", | |
706 "-details", "-pubkey", filename, NULL); | |
707 return; | |
708 } | |
709 | |
710 if (!b->proto_data) { | |
711 g_snprintf(tmp, sizeof(tmp), | |
712 _("User %s is not present in the network"), b->name); | |
713 purple_notify_error(gc, _("User Information"), | |
714 _("Cannot get user information"), tmp); | |
715 return; | |
716 } | |
717 | |
718 client_entry = silc_client_get_client_by_id(client, conn, b->proto_data); | |
719 if (client_entry) { | |
720 /* Call WHOIS. The user info is displayed in the WHOIS | |
721 command reply. */ | |
722 silc_client_command_call(client, conn, NULL, "WHOIS", | |
723 client_entry->nickname, "-details", NULL); | |
724 } | |
725 } else { | |
726 /* Call WHOIS just with nickname. */ | |
727 silc_client_command_call(client, conn, NULL, "WHOIS", nick, NULL); | |
728 } | |
729 } | |
730 | |
731 static void | |
732 silcpurple_add_buddy_pk_no(SilcPurpleBuddyRes r) | |
733 { | |
734 char tmp[512]; | |
735 g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"), | |
736 r->b->name); | |
737 purple_notify_error(r->client->application, _("Add Buddy"), tmp, | |
738 _("You cannot receive buddy notifications until you " | |
739 "import his/her public key. You can use the Get Public Key " | |
740 "command to get the public key.")); | |
741 purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL); | |
742 } | |
743 | |
744 static void | |
745 silcpurple_add_buddy_save(bool success, void *context) | |
746 { | |
747 SilcPurpleBuddyRes r = context; | |
748 PurpleBuddy *b = r->b; | |
749 SilcClient client = r->client; | |
750 SilcClientEntry client_entry; | |
751 SilcAttributePayload attr; | |
752 SilcAttribute attribute; | |
753 SilcVCardStruct vcard; | |
754 SilcAttributeObjMime message, extension; | |
755 #ifdef SILC_ATTRIBUTE_USER_ICON | |
756 SilcAttributeObjMime usericon; | |
757 #endif | |
758 SilcAttributeObjPk serverpk, usersign, serversign; | |
759 gboolean usign_success = TRUE, ssign_success = TRUE; | |
760 char filename[512], filename2[512], *fingerprint = NULL, *tmp; | |
761 SilcUInt32 len; | |
762 int i; | |
763 | |
764 if (!success) { | |
765 /* The user did not trust the public key. */ | |
766 silcpurple_add_buddy_pk_no(r); | |
767 silc_free(r); | |
768 return; | |
769 } | |
770 | |
771 if (r->offline) { | |
772 /* User is offline. Associate the imported public key with | |
773 this user. */ | |
774 fingerprint = silc_hash_fingerprint(NULL, r->offline_pk, | |
775 r->offline_pk_len); | |
776 for (i = 0; i < strlen(fingerprint); i++) | |
777 if (fingerprint[i] == ' ') | |
778 fingerprint[i] = '_'; | |
779 g_snprintf(filename, sizeof(filename) - 1, | |
780 "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub", | |
781 silcpurple_silcdir(), fingerprint); | |
782 purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename); | |
783 purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL); | |
784 silc_free(fingerprint); | |
785 silc_free(r->offline_pk); | |
786 silc_free(r); | |
787 return; | |
788 } | |
789 | |
790 /* Get the client entry. */ | |
791 client_entry = silc_client_get_client_by_id(r->client, r->conn, | |
792 &r->client_id); | |
793 if (!client_entry) { | |
794 silc_free(r); | |
795 return; | |
796 } | |
797 | |
798 memset(&vcard, 0, sizeof(vcard)); | |
799 memset(&message, 0, sizeof(message)); | |
800 memset(&extension, 0, sizeof(extension)); | |
801 #ifdef SILC_ATTRIBUTE_USER_ICON | |
802 memset(&usericon, 0, sizeof(usericon)); | |
803 #endif | |
804 memset(&serverpk, 0, sizeof(serverpk)); | |
805 memset(&usersign, 0, sizeof(usersign)); | |
806 memset(&serversign, 0, sizeof(serversign)); | |
807 | |
808 /* Now that we have the public key and we trust it now we | |
809 save the attributes of the buddy and update its status. */ | |
810 | |
811 if (client_entry->attrs) { | |
812 silc_dlist_start(client_entry->attrs); | |
813 while ((attr = silc_dlist_get(client_entry->attrs)) | |
814 != SILC_LIST_END) { | |
815 attribute = silc_attribute_get_attribute(attr); | |
816 | |
817 switch (attribute) { | |
818 case SILC_ATTRIBUTE_USER_INFO: | |
819 if (!silc_attribute_get_object(attr, (void *)&vcard, | |
820 sizeof(vcard))) | |
821 continue; | |
822 break; | |
823 | |
824 case SILC_ATTRIBUTE_STATUS_MESSAGE: | |
825 if (!silc_attribute_get_object(attr, (void *)&message, | |
826 sizeof(message))) | |
827 continue; | |
828 break; | |
829 | |
830 case SILC_ATTRIBUTE_EXTENSION: | |
831 if (!silc_attribute_get_object(attr, (void *)&extension, | |
832 sizeof(extension))) | |
833 continue; | |
834 break; | |
835 | |
836 #ifdef SILC_ATTRIBUTE_USER_ICON | |
837 case SILC_ATTRIBUTE_USER_ICON: | |
838 if (!silc_attribute_get_object(attr, (void *)&usericon, | |
839 sizeof(usericon))) | |
840 continue; | |
841 break; | |
842 #endif | |
843 | |
844 case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY: | |
845 if (serverpk.type) | |
846 continue; | |
847 if (!silc_attribute_get_object(attr, (void *)&serverpk, | |
848 sizeof(serverpk))) | |
849 continue; | |
850 break; | |
851 | |
852 case SILC_ATTRIBUTE_USER_DIGITAL_SIGNATURE: | |
853 if (usersign.data) | |
854 continue; | |
855 if (!silc_attribute_get_object(attr, (void *)&usersign, | |
856 sizeof(usersign))) | |
857 continue; | |
858 break; | |
859 | |
860 case SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE: | |
861 if (serversign.data) | |
862 continue; | |
863 if (!silc_attribute_get_object(attr, (void *)&serversign, | |
864 sizeof(serversign))) | |
865 continue; | |
866 break; | |
867 | |
868 default: | |
869 break; | |
870 } | |
871 } | |
872 } | |
873 | |
874 /* Verify the attribute signatures */ | |
875 | |
876 if (usersign.data) { | |
877 SilcPKCS pkcs; | |
878 unsigned char *verifyd; | |
879 SilcUInt32 verify_len; | |
880 | |
881 silc_pkcs_alloc((unsigned char*)"rsa", &pkcs); | |
882 verifyd = silc_attribute_get_verify_data(client_entry->attrs, | |
883 FALSE, &verify_len); | |
884 if (verifyd && silc_pkcs_public_key_set(pkcs, client_entry->public_key)){ | |
885 if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash, | |
886 usersign.data, | |
887 usersign.data_len, | |
888 verifyd, verify_len)) | |
889 usign_success = FALSE; | |
890 } | |
891 silc_free(verifyd); | |
892 } | |
893 | |
894 if (serversign.data && !strcmp(serverpk.type, "silc-rsa")) { | |
895 SilcPublicKey public_key; | |
896 SilcPKCS pkcs; | |
897 unsigned char *verifyd; | |
898 SilcUInt32 verify_len; | |
899 | |
900 if (silc_pkcs_public_key_decode(serverpk.data, serverpk.data_len, | |
901 &public_key)) { | |
902 silc_pkcs_alloc((unsigned char *)"rsa", &pkcs); | |
903 verifyd = silc_attribute_get_verify_data(client_entry->attrs, | |
904 TRUE, &verify_len); | |
905 if (verifyd && silc_pkcs_public_key_set(pkcs, public_key)) { | |
906 if (!silc_pkcs_verify_with_hash(pkcs, client->sha1hash, | |
907 serversign.data, | |
908 serversign.data_len, | |
909 verifyd, verify_len)) | |
910 ssign_success = FALSE; | |
911 } | |
912 silc_pkcs_public_key_free(public_key); | |
913 silc_free(verifyd); | |
914 } | |
915 } | |
916 | |
917 fingerprint = silc_fingerprint(client_entry->fingerprint, | |
918 client_entry->fingerprint_len); | |
919 for (i = 0; i < strlen(fingerprint); i++) | |
920 if (fingerprint[i] == ' ') | |
921 fingerprint[i] = '_'; | |
922 | |
923 if (usign_success || ssign_success) { | |
924 struct passwd *pw; | |
925 struct stat st; | |
926 | |
927 memset(filename2, 0, sizeof(filename2)); | |
928 | |
929 /* Filename for dir */ | |
930 tmp = fingerprint + strlen(fingerprint) - 9; | |
931 g_snprintf(filename, sizeof(filename) - 1, | |
932 "%s" G_DIR_SEPARATOR_S "friends" G_DIR_SEPARATOR_S "%s", | |
933 silcpurple_silcdir(), tmp); | |
934 | |
935 pw = getpwuid(getuid()); | |
936 if (!pw) | |
937 return; | |
938 | |
939 /* Create dir if it doesn't exist */ | |
940 if ((g_stat(filename, &st)) == -1) { | |
941 if (errno == ENOENT) { | |
942 if (pw->pw_uid == geteuid()) | |
943 g_mkdir(filename, 0755); | |
944 } | |
945 } | |
946 | |
947 /* Save VCard */ | |
948 g_snprintf(filename2, sizeof(filename2) - 1, | |
949 "%s" G_DIR_SEPARATOR_S "vcard", filename); | |
950 if (vcard.full_name) { | |
951 tmp = (char *)silc_vcard_encode(&vcard, &len); | |
952 silc_file_writefile(filename2, tmp, len); | |
953 silc_free(tmp); | |
954 } | |
955 | |
956 /* Save status message */ | |
957 if (message.mime) { | |
958 memset(filename2, 0, sizeof(filename2)); | |
959 g_snprintf(filename2, sizeof(filename2) - 1, | |
960 "%s" G_DIR_SEPARATOR_S "status_message.mime", | |
961 filename); | |
962 silc_file_writefile(filename2, (char *)message.mime, | |
963 message.mime_len); | |
964 } | |
965 | |
966 /* Save extension data */ | |
967 if (extension.mime) { | |
968 memset(filename2, 0, sizeof(filename2)); | |
969 g_snprintf(filename2, sizeof(filename2) - 1, | |
970 "%s" G_DIR_SEPARATOR_S "extension.mime", | |
971 filename); | |
972 silc_file_writefile(filename2, (char *)extension.mime, | |
973 extension.mime_len); | |
974 } | |
975 | |
976 #ifdef SILC_ATTRIBUTE_USER_ICON | |
977 /* Save user icon */ | |
978 if (usericon.mime) { | |
979 SilcMime m = silc_mime_decode(usericon.mime, | |
980 usericon.mime_len); | |
981 if (m) { | |
982 const char *type = silc_mime_get_field(m, "Content-Type"); | |
983 if (!strcmp(type, "image/jpeg") || | |
984 !strcmp(type, "image/gif") || | |
985 !strcmp(type, "image/bmp") || | |
986 !strcmp(type, "image/png")) { | |
987 const unsigned char *data; | |
988 SilcUInt32 data_len; | |
989 data = silc_mime_get_data(m, &data_len); | |
990 if (data) { | |
991 /* TODO: Check if SILC gives us something to use as the checksum instead */ | |
992 purple_buddy_icons_set_for_user(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), g_memdup(data, data_len), data_len, NULL); | |
993 } | |
994 } | |
995 silc_mime_free(m); | |
996 } | |
997 } | |
998 #endif | |
999 } | |
1000 | |
1001 /* Save the public key path to buddy properties, as it is used | |
1002 to identify the buddy in the network (and not the nickname). */ | |
1003 memset(filename, 0, sizeof(filename)); | |
1004 g_snprintf(filename, sizeof(filename) - 1, | |
1005 "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub", | |
1006 silcpurple_silcdir(), fingerprint); | |
1007 purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename); | |
1008 | |
1009 /* Update online status */ | |
1010 purple_prpl_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_AVAILABLE, NULL); | |
1011 | |
1012 /* Finally, start watching this user so we receive its status | |
1013 changes from the server */ | |
1014 g_snprintf(filename2, sizeof(filename2) - 1, "+%s", filename); | |
1015 silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey", | |
1016 filename2, NULL); | |
1017 | |
1018 silc_free(fingerprint); | |
1019 silc_free(r); | |
1020 } | |
1021 | |
1022 static void | |
1023 silcpurple_add_buddy_ask_import(void *user_data, const char *name) | |
1024 { | |
1025 SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data; | |
1026 SilcPublicKey public_key; | |
1027 | |
1028 /* Load the public key */ | |
1029 if (!silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_PEM) && | |
1030 !silc_pkcs_load_public_key(name, &public_key, SILC_PKCS_FILE_BIN)) { | |
1031 silcpurple_add_buddy_ask_pk_cb(r, 0); | |
1032 purple_notify_error(r->client->application, | |
1033 _("Add Buddy"), _("Could not load public key"), NULL); | |
1034 return; | |
1035 } | |
1036 | |
1037 /* Now verify the public key */ | |
1038 r->offline_pk = silc_pkcs_public_key_encode(public_key, &r->offline_pk_len); | |
1039 silcpurple_verify_public_key(r->client, r->conn, r->b->name, | |
1040 SILC_SOCKET_TYPE_CLIENT, | |
1041 r->offline_pk, r->offline_pk_len, | |
1042 SILC_SKE_PK_TYPE_SILC, | |
1043 silcpurple_add_buddy_save, r); | |
1044 } | |
1045 | |
1046 static void | |
1047 silcpurple_add_buddy_ask_pk_cancel(void *user_data, const char *name) | |
1048 { | |
1049 SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data; | |
1050 | |
1051 /* The user did not import public key. The buddy is unusable. */ | |
1052 silcpurple_add_buddy_pk_no(r); | |
1053 silc_free(r); | |
1054 } | |
1055 | |
1056 static void | |
1057 silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id) | |
1058 { | |
1059 if (id != 0) { | |
1060 /* The user did not import public key. The buddy is unusable. */ | |
1061 silcpurple_add_buddy_pk_no(r); | |
1062 silc_free(r); | |
1063 return; | |
1064 } | |
1065 | |
1066 /* Open file selector to select the public key. */ | |
1067 purple_request_file(r->client->application, _("Open..."), NULL, FALSE, | |
1068 G_CALLBACK(silcpurple_add_buddy_ask_import), | |
1069 G_CALLBACK(silcpurple_add_buddy_ask_pk_cancel), | |
1070 purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r); | |
1071 | |
1072 } | |
1073 | |
1074 static void | |
1075 silcpurple_add_buddy_ask_pk(SilcPurpleBuddyRes r) | |
1076 { | |
1077 char tmp[512]; | |
1078 g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"), | |
1079 r->b->name); | |
1080 purple_request_action(r->client->application, _("Add Buddy"), tmp, | |
1081 _("To add the buddy you must import his/her public key. " | |
1082 "Press Import to import a public key."), 0, | |
1083 purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r, 2, | |
1084 _("Cancel"), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb), | |
1085 _("_Import..."), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb)); | |
1086 } | |
1087 | |
1088 static void | |
1089 silcpurple_add_buddy_getkey_cb(SilcPurpleBuddyRes r, | |
1090 SilcClientCommandReplyContext cmd) | |
1091 { | |
1092 SilcClientEntry client_entry; | |
1093 unsigned char *pk; | |
1094 SilcUInt32 pk_len; | |
1095 | |
1096 /* Get the client entry. */ | |
1097 client_entry = silc_client_get_client_by_id(r->client, r->conn, | |
1098 &r->client_id); | |
1099 if (!client_entry || !client_entry->public_key) { | |
1100 /* The buddy is offline/nonexistent. We will require user | |
1101 to associate a public key with the buddy or the buddy | |
1102 cannot be added. */ | |
1103 r->offline = TRUE; | |
1104 silcpurple_add_buddy_ask_pk(r); | |
1105 return; | |
1106 } | |
1107 | |
1108 /* Now verify the public key */ | |
1109 pk = silc_pkcs_public_key_encode(client_entry->public_key, &pk_len); | |
1110 silcpurple_verify_public_key(r->client, r->conn, client_entry->nickname, | |
1111 SILC_SOCKET_TYPE_CLIENT, | |
1112 pk, pk_len, SILC_SKE_PK_TYPE_SILC, | |
1113 silcpurple_add_buddy_save, r); | |
1114 silc_free(pk); | |
1115 } | |
1116 | |
1117 static void | |
1118 silcpurple_add_buddy_select_cb(SilcPurpleBuddyRes r, PurpleRequestFields *fields) | |
1119 { | |
1120 PurpleRequestField *f; | |
1121 const GList *list; | |
1122 SilcClientEntry client_entry; | |
1123 | |
1124 f = purple_request_fields_get_field(fields, "list"); | |
1125 list = purple_request_field_list_get_selected(f); | |
1126 if (!list) { | |
1127 /* The user did not select any user. */ | |
1128 silcpurple_add_buddy_pk_no(r); | |
1129 silc_free(r); | |
1130 return; | |
1131 } | |
1132 | |
1133 client_entry = purple_request_field_list_get_data(f, list->data); | |
1134 silcpurple_add_buddy_resolved(r->client, r->conn, &client_entry, 1, r); | |
1135 } | |
1136 | |
1137 static void | |
1138 silcpurple_add_buddy_select_cancel(SilcPurpleBuddyRes r, PurpleRequestFields *fields) | |
1139 { | |
1140 /* The user did not select any user. */ | |
1141 silcpurple_add_buddy_pk_no(r); | |
1142 silc_free(r); | |
1143 } | |
1144 | |
1145 static void | |
1146 silcpurple_add_buddy_select(SilcPurpleBuddyRes r, | |
1147 SilcClientEntry *clients, | |
1148 SilcUInt32 clients_count) | |
1149 { | |
1150 PurpleRequestFields *fields; | |
1151 PurpleRequestFieldGroup *g; | |
1152 PurpleRequestField *f; | |
1153 char tmp[512], tmp2[128]; | |
1154 int i; | |
1155 char *fingerprint; | |
1156 | |
1157 fields = purple_request_fields_new(); | |
1158 g = purple_request_field_group_new(NULL); | |
1159 f = purple_request_field_list_new("list", NULL); | |
1160 purple_request_field_group_add_field(g, f); | |
1161 purple_request_field_list_set_multi_select(f, FALSE); | |
1162 purple_request_fields_add_group(fields, g); | |
1163 | |
1164 for (i = 0; i < clients_count; i++) { | |
1165 fingerprint = NULL; | |
1166 if (clients[i]->fingerprint) { | |
1167 fingerprint = silc_fingerprint(clients[i]->fingerprint, | |
1168 clients[i]->fingerprint_len); | |
1169 g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint); | |
1170 } | |
1171 g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s", | |
1172 clients[i]->realname, clients[i]->nickname, | |
1173 clients[i]->username, clients[i]->hostname ? | |
1174 clients[i]->hostname : "", | |
1175 fingerprint ? tmp2 : ""); | |
1176 purple_request_field_list_add(f, tmp, clients[i]); | |
1177 silc_free(fingerprint); | |
1178 } | |
1179 | |
1180 purple_request_fields(r->client->application, _("Add Buddy"), | |
1181 _("Select correct user"), | |
1182 r->pubkey_search | |
1183 ? _("More than one user was found with the same public key. Select " | |
1184 "the correct user from the list to add to the buddy list.") | |
1185 : _("More than one user was found with the same name. Select " | |
1186 "the correct user from the list to add to the buddy list."), | |
1187 fields, | |
1188 _("OK"), G_CALLBACK(silcpurple_add_buddy_select_cb), | |
1189 _("Cancel"), G_CALLBACK(silcpurple_add_buddy_select_cancel), | |
1190 purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), NULL, r); | |
1191 } | |
1192 | |
1193 static void | |
1194 silcpurple_add_buddy_resolved(SilcClient client, | |
1195 SilcClientConnection conn, | |
1196 SilcClientEntry *clients, | |
1197 SilcUInt32 clients_count, | |
1198 void *context) | |
1199 { | |
1200 SilcPurpleBuddyRes r = context; | |
1201 PurpleBuddy *b = r->b; | |
1202 SilcAttributePayload pub; | |
1203 SilcAttributeObjPk userpk; | |
1204 unsigned char *pk; | |
1205 SilcUInt32 pk_len; | |
1206 const char *filename; | |
1207 | |
1208 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key"); | |
1209 | |
1210 /* If the buddy is offline/nonexistent, we will require user | |
1211 to associate a public key with the buddy or the buddy | |
1212 cannot be added. */ | |
1213 if (!clients_count) { | |
1214 if (r->init) { | |
1215 silc_free(r); | |
1216 return; | |
1217 } | |
1218 | |
1219 r->offline = TRUE; | |
1220 /* If the user has already associated a public key, try loading it | |
1221 * before prompting the user to load it again */ | |
1222 if (filename != NULL) | |
1223 silcpurple_add_buddy_ask_import(r, filename); | |
1224 else | |
1225 silcpurple_add_buddy_ask_pk(r); | |
1226 return; | |
1227 } | |
1228 | |
1229 /* If more than one client was found with nickname, we need to verify | |
1230 from user which one is the correct. */ | |
1231 if (clients_count > 1 && !r->pubkey_search) { | |
1232 if (r->init) { | |
1233 silc_free(r); | |
1234 return; | |
1235 } | |
1236 | |
1237 silcpurple_add_buddy_select(r, clients, clients_count); | |
1238 return; | |
1239 } | |
1240 | |
1241 /* If we searched using public keys and more than one entry was found | |
1242 the same person is logged on multiple times. */ | |
1243 if (clients_count > 1 && r->pubkey_search && b->name) { | |
1244 if (r->init) { | |
1245 /* Find the entry that closest matches to the | |
1246 buddy nickname. */ | |
1247 int i; | |
1248 for (i = 0; i < clients_count; i++) { | |
1249 if (!strncasecmp(b->name, clients[i]->nickname, | |
1250 strlen(b->name))) { | |
1251 clients[0] = clients[i]; | |
1252 break; | |
1253 } | |
1254 } | |
1255 } else { | |
1256 /* Verify from user which one is correct */ | |
1257 silcpurple_add_buddy_select(r, clients, clients_count); | |
1258 return; | |
1259 } | |
1260 } | |
1261 | |
1262 /* The client was found. Now get its public key and verify | |
1263 that before adding the buddy. */ | |
1264 memset(&userpk, 0, sizeof(userpk)); | |
1265 b->proto_data = silc_memdup(clients[0]->id, sizeof(*clients[0]->id)); | |
1266 r->client_id = *clients[0]->id; | |
1267 | |
1268 /* Get the public key from attributes, if not present then | |
1269 resolve it with GETKEY unless we have it cached already. */ | |
1270 if (clients[0]->attrs && !clients[0]->public_key) { | |
1271 pub = silcpurple_get_attr(clients[0]->attrs, | |
1272 SILC_ATTRIBUTE_USER_PUBLIC_KEY); | |
1273 if (!pub || !silc_attribute_get_object(pub, (void *)&userpk, | |
1274 sizeof(userpk))) { | |
1275 /* Get public key with GETKEY */ | |
1276 silc_client_command_call(client, conn, NULL, | |
1277 "GETKEY", clients[0]->nickname, NULL); | |
1278 silc_client_command_pending(conn, SILC_COMMAND_GETKEY, | |
1279 conn->cmd_ident, | |
1280 (SilcCommandCb)silcpurple_add_buddy_getkey_cb, | |
1281 r); | |
1282 return; | |
1283 } | |
1284 if (!silc_pkcs_public_key_decode(userpk.data, userpk.data_len, | |
1285 &clients[0]->public_key)) | |
1286 return; | |
1287 silc_free(userpk.data); | |
1288 } else if (filename && !clients[0]->public_key) { | |
1289 if (!silc_pkcs_load_public_key(filename, &clients[0]->public_key, | |
1290 SILC_PKCS_FILE_PEM) && | |
1291 !silc_pkcs_load_public_key(filename, &clients[0]->public_key, | |
1292 SILC_PKCS_FILE_BIN)) { | |
1293 /* Get public key with GETKEY */ | |
1294 silc_client_command_call(client, conn, NULL, | |
1295 "GETKEY", clients[0]->nickname, NULL); | |
1296 silc_client_command_pending(conn, SILC_COMMAND_GETKEY, | |
1297 conn->cmd_ident, | |
1298 (SilcCommandCb)silcpurple_add_buddy_getkey_cb, | |
1299 r); | |
1300 return; | |
1301 } | |
1302 } else if (!clients[0]->public_key) { | |
1303 /* Get public key with GETKEY */ | |
1304 silc_client_command_call(client, conn, NULL, | |
1305 "GETKEY", clients[0]->nickname, NULL); | |
1306 silc_client_command_pending(conn, SILC_COMMAND_GETKEY, | |
1307 conn->cmd_ident, | |
1308 (SilcCommandCb)silcpurple_add_buddy_getkey_cb, | |
1309 r); | |
1310 return; | |
1311 } | |
1312 | |
1313 /* We have the public key, verify it. */ | |
1314 pk = silc_pkcs_public_key_encode(clients[0]->public_key, &pk_len); | |
1315 silcpurple_verify_public_key(client, conn, clients[0]->nickname, | |
1316 SILC_SOCKET_TYPE_CLIENT, | |
1317 pk, pk_len, SILC_SKE_PK_TYPE_SILC, | |
1318 silcpurple_add_buddy_save, r); | |
1319 silc_free(pk); | |
1320 } | |
1321 | |
1322 static void | |
1323 silcpurple_add_buddy_i(PurpleConnection *gc, PurpleBuddy *b, gboolean init) | |
1324 { | |
1325 SilcPurple sg = gc->proto_data; | |
1326 SilcClient client = sg->client; | |
1327 SilcClientConnection conn = sg->conn; | |
1328 SilcPurpleBuddyRes r; | |
1329 SilcBuffer attrs; | |
1330 const char *filename, *name = b->name; | |
1331 | |
1332 r = silc_calloc(1, sizeof(*r)); | |
1333 if (!r) | |
1334 return; | |
1335 r->client = client; | |
1336 r->conn = conn; | |
1337 r->b = b; | |
1338 r->init = init; | |
1339 | |
1340 /* See if we have this buddy's public key. If we do use that | |
1341 to search the details. */ | |
1342 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key"); | |
1343 if (filename) { | |
1344 SilcPublicKey public_key; | |
1345 SilcAttributeObjPk userpk; | |
1346 | |
1347 if (!silc_pkcs_load_public_key(filename, &public_key, | |
1348 SILC_PKCS_FILE_PEM) && | |
1349 !silc_pkcs_load_public_key(filename, &public_key, | |
1350 SILC_PKCS_FILE_BIN)) | |
1351 return; | |
1352 | |
1353 /* Get all attributes, and use the public key to search user */ | |
1354 name = NULL; | |
1355 attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO, | |
1356 SILC_ATTRIBUTE_SERVICE, | |
1357 SILC_ATTRIBUTE_STATUS_MOOD, | |
1358 SILC_ATTRIBUTE_STATUS_FREETEXT, | |
1359 SILC_ATTRIBUTE_STATUS_MESSAGE, | |
1360 SILC_ATTRIBUTE_PREFERRED_LANGUAGE, | |
1361 SILC_ATTRIBUTE_PREFERRED_CONTACT, | |
1362 SILC_ATTRIBUTE_TIMEZONE, | |
1363 SILC_ATTRIBUTE_GEOLOCATION, | |
1364 #ifdef SILC_ATTRIBUTE_USER_ICON | |
1365 SILC_ATTRIBUTE_USER_ICON, | |
1366 #endif | |
1367 SILC_ATTRIBUTE_DEVICE_INFO, 0); | |
1368 userpk.type = "silc-rsa"; | |
1369 userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len); | |
1370 attrs = silc_attribute_payload_encode(attrs, | |
1371 SILC_ATTRIBUTE_USER_PUBLIC_KEY, | |
1372 SILC_ATTRIBUTE_FLAG_VALID, | |
1373 &userpk, sizeof(userpk)); | |
1374 silc_free(userpk.data); | |
1375 silc_pkcs_public_key_free(public_key); | |
1376 r->pubkey_search = TRUE; | |
1377 } else { | |
1378 /* Get all attributes */ | |
1379 attrs = silc_client_attributes_request(0); | |
1380 } | |
1381 | |
1382 /* Resolve */ | |
1383 silc_client_get_clients_whois(client, conn, name, NULL, attrs, | |
1384 silcpurple_add_buddy_resolved, r); | |
1385 silc_buffer_free(attrs); | |
1386 } | |
1387 | |
1388 void silcpurple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) | |
1389 { | |
1390 silcpurple_add_buddy_i(gc, buddy, FALSE); | |
1391 } | |
1392 | |
1393 void silcpurple_send_buddylist(PurpleConnection *gc) | |
1394 { | |
1395 PurpleBuddyList *blist; | |
1396 PurpleBlistNode *gnode, *cnode, *bnode; | |
1397 PurpleBuddy *buddy; | |
1398 PurpleAccount *account; | |
1399 | |
1400 account = purple_connection_get_account(gc); | |
1401 | |
1402 if ((blist = purple_get_blist()) != NULL) | |
1403 { | |
1404 for (gnode = blist->root; gnode != NULL; gnode = gnode->next) | |
1405 { | |
1406 if (!PURPLE_BLIST_NODE_IS_GROUP(gnode)) | |
1407 continue; | |
1408 for (cnode = gnode->child; cnode != NULL; cnode = cnode->next) | |
1409 { | |
1410 if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode)) | |
1411 continue; | |
1412 for (bnode = cnode->child; bnode != NULL; bnode = bnode->next) | |
1413 { | |
1414 if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode)) | |
1415 continue; | |
1416 buddy = (PurpleBuddy *)bnode; | |
1417 if (purple_buddy_get_account(buddy) == account) | |
1418 silcpurple_add_buddy_i(gc, buddy, TRUE); | |
1419 } | |
1420 } | |
1421 } | |
1422 } | |
1423 } | |
1424 | |
1425 void silcpurple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, | |
1426 PurpleGroup *group) | |
1427 { | |
1428 silc_free(buddy->proto_data); | |
1429 } | |
1430 | |
1431 void silcpurple_idle_set(PurpleConnection *gc, int idle) | |
1432 | |
1433 { | |
1434 SilcPurple sg = gc->proto_data; | |
1435 SilcClient client = sg->client; | |
1436 SilcClientConnection conn = sg->conn; | |
1437 SilcAttributeObjService service; | |
1438 const char *server; | |
1439 int port; | |
1440 | |
1441 server = purple_account_get_string(sg->account, "server", | |
1442 "silc.silcnet.org"); | |
1443 port = purple_account_get_int(sg->account, "port", 706), | |
1444 | |
1445 memset(&service, 0, sizeof(service)); | |
1446 silc_client_attribute_del(client, conn, | |
1447 SILC_ATTRIBUTE_SERVICE, NULL); | |
1448 service.port = port; | |
1449 g_snprintf(service.address, sizeof(service.address), "%s", server); | |
1450 service.idle = idle; | |
1451 silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_SERVICE, | |
1452 &service, sizeof(service)); | |
1453 } | |
1454 | |
1455 char *silcpurple_status_text(PurpleBuddy *b) | |
1456 { | |
1457 SilcPurple sg = b->account->gc->proto_data; | |
1458 SilcClient client = sg->client; | |
1459 SilcClientConnection conn = sg->conn; | |
1460 SilcClientID *client_id = b->proto_data; | |
1461 SilcClientEntry client_entry; | |
1462 SilcAttributePayload attr; | |
1463 SilcAttributeMood mood = 0; | |
1464 | |
1465 /* Get the client entry. */ | |
1466 client_entry = silc_client_get_client_by_id(client, conn, client_id); | |
1467 if (!client_entry) | |
1468 return NULL; | |
1469 | |
1470 /* If user is online, we show the mood status, if available. | |
1471 If user is offline or away that status is indicated. */ | |
1472 | |
1473 if (client_entry->mode & SILC_UMODE_DETACHED) | |
1474 return g_strdup(_("Detached")); | |
1475 if (client_entry->mode & SILC_UMODE_GONE) | |
1476 return g_strdup(_("Away")); | |
1477 if (client_entry->mode & SILC_UMODE_INDISPOSED) | |
1478 return g_strdup(_("Indisposed")); | |
1479 if (client_entry->mode & SILC_UMODE_BUSY) | |
1480 return g_strdup(_("Busy")); | |
1481 if (client_entry->mode & SILC_UMODE_PAGE) | |
1482 return g_strdup(_("Wake Me Up")); | |
1483 if (client_entry->mode & SILC_UMODE_HYPER) | |
1484 return g_strdup(_("Hyper Active")); | |
1485 if (client_entry->mode & SILC_UMODE_ROBOT) | |
1486 return g_strdup(_("Robot")); | |
1487 | |
1488 attr = silcpurple_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD); | |
1489 if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) { | |
1490 /* The mood is a bit mask, so we could show multiple moods, | |
1491 but let's show only one for now. */ | |
1492 if (mood & SILC_ATTRIBUTE_MOOD_HAPPY) | |
1493 return g_strdup(_("Happy")); | |
1494 if (mood & SILC_ATTRIBUTE_MOOD_SAD) | |
1495 return g_strdup(_("Sad")); | |
1496 if (mood & SILC_ATTRIBUTE_MOOD_ANGRY) | |
1497 return g_strdup(_("Angry")); | |
1498 if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS) | |
1499 return g_strdup(_("Jealous")); | |
1500 if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED) | |
1501 return g_strdup(_("Ashamed")); | |
1502 if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE) | |
1503 return g_strdup(_("Invincible")); | |
1504 if (mood & SILC_ATTRIBUTE_MOOD_INLOVE) | |
1505 return g_strdup(_("In Love")); | |
1506 if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY) | |
1507 return g_strdup(_("Sleepy")); | |
1508 if (mood & SILC_ATTRIBUTE_MOOD_BORED) | |
1509 return g_strdup(_("Bored")); | |
1510 if (mood & SILC_ATTRIBUTE_MOOD_EXCITED) | |
1511 return g_strdup(_("Excited")); | |
1512 if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS) | |
1513 return g_strdup(_("Anxious")); | |
1514 } | |
1515 | |
1516 return NULL; | |
1517 } | |
1518 | |
1519 void silcpurple_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full) | |
1520 { | |
1521 SilcPurple sg = b->account->gc->proto_data; | |
1522 SilcClient client = sg->client; | |
1523 SilcClientConnection conn = sg->conn; | |
1524 SilcClientID *client_id = b->proto_data; | |
1525 SilcClientEntry client_entry; | |
1526 char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr; | |
1527 char tmp[256]; | |
1528 | |
1529 /* Get the client entry. */ | |
1530 client_entry = silc_client_get_client_by_id(client, conn, client_id); | |
1531 if (!client_entry) | |
1532 return; | |
1533 | |
1534 if (client_entry->nickname) | |
1535 purple_notify_user_info_add_pair(user_info, _("Nickname"), | |
1536 client_entry->nickname); | |
1537 if (client_entry->username && client_entry->hostname) { | |
1538 g_snprintf(tmp, sizeof(tmp), "%s@%s", client_entry->username, client_entry->hostname); | |
1539 purple_notify_user_info_add_pair(user_info, _("Username"), tmp); | |
1540 } | |
1541 if (client_entry->mode) { | |
1542 memset(tmp, 0, sizeof(tmp)); | |
1543 silcpurple_get_umode_string(client_entry->mode, | |
1544 tmp, sizeof(tmp) - strlen(tmp)); | |
1545 purple_notify_user_info_add_pair(user_info, _("User Modes"), tmp); | |
1546 } | |
1547 | |
1548 silcpurple_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr); | |
1549 | |
1550 if (statusstr) { | |
1551 purple_notify_user_info_add_pair(user_info, _("Message"), statusstr); | |
1552 g_free(statusstr); | |
1553 } | |
1554 | |
1555 if (full) { | |
1556 if (moodstr) { | |
1557 purple_notify_user_info_add_pair(user_info, _("Mood"), moodstr); | |
1558 g_free(moodstr); | |
1559 } | |
1560 | |
1561 if (contactstr) { | |
1562 purple_notify_user_info_add_pair(user_info, _("Preferred Contact"), contactstr); | |
1563 g_free(contactstr); | |
1564 } | |
1565 | |
1566 if (langstr) { | |
1567 purple_notify_user_info_add_pair(user_info, _("Preferred Language"), langstr); | |
1568 g_free(langstr); | |
1569 } | |
1570 | |
1571 if (devicestr) { | |
1572 purple_notify_user_info_add_pair(user_info, _("Device"), devicestr); | |
1573 g_free(devicestr); | |
1574 } | |
1575 | |
1576 if (tzstr) { | |
1577 purple_notify_user_info_add_pair(user_info, _("Timezone"), tzstr); | |
1578 g_free(tzstr); | |
1579 } | |
1580 | |
1581 if (geostr) { | |
1582 purple_notify_user_info_add_pair(user_info, _("Geolocation"), geostr); | |
1583 g_free(geostr); | |
1584 } | |
1585 } | |
1586 } | |
1587 | |
1588 static void | |
1589 silcpurple_buddy_kill(PurpleBlistNode *node, gpointer data) | |
1590 { | |
1591 PurpleBuddy *b; | |
1592 PurpleConnection *gc; | |
1593 SilcPurple sg; | |
1594 | |
1595 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); | |
1596 | |
1597 b = (PurpleBuddy *) node; | |
1598 gc = purple_account_get_connection(b->account); | |
1599 sg = gc->proto_data; | |
1600 | |
1601 /* Call KILL */ | |
1602 silc_client_command_call(sg->client, sg->conn, NULL, "KILL", | |
1603 b->name, "Killed by operator", NULL); | |
1604 } | |
1605 | |
1606 typedef struct { | |
1607 SilcPurple sg; | |
1608 SilcClientEntry client_entry; | |
1609 } *SilcPurpleBuddyWb; | |
1610 | |
1611 static void | |
1612 silcpurple_buddy_wb(PurpleBlistNode *node, gpointer data) | |
1613 { | |
1614 SilcPurpleBuddyWb wb = data; | |
1615 silcpurple_wb_init(wb->sg, wb->client_entry); | |
1616 silc_free(wb); | |
1617 } | |
1618 | |
1619 GList *silcpurple_buddy_menu(PurpleBuddy *buddy) | |
1620 { | |
1621 PurpleConnection *gc = purple_account_get_connection(buddy->account); | |
1622 SilcPurple sg = gc->proto_data; | |
1623 SilcClientConnection conn = sg->conn; | |
1624 const char *pkfile = NULL; | |
1625 SilcClientEntry client_entry = NULL; | |
1626 PurpleMenuAction *act; | |
1627 GList *m = NULL; | |
1628 SilcPurpleBuddyWb wb; | |
1629 | |
1630 pkfile = purple_blist_node_get_string((PurpleBlistNode *) buddy, "public-key"); | |
1631 client_entry = silc_client_get_client_by_id(sg->client, | |
1632 sg->conn, | |
1633 buddy->proto_data); | |
1634 | |
1635 if (client_entry && client_entry->send_key) { | |
1636 act = purple_menu_action_new(_("Reset IM Key"), | |
1637 PURPLE_CALLBACK(silcpurple_buddy_resetkey), | |
1638 NULL, NULL); | |
1639 m = g_list_append(m, act); | |
1640 | |
1641 } else { | |
1642 act = purple_menu_action_new(_("IM with Key Exchange"), | |
1643 PURPLE_CALLBACK(silcpurple_buddy_keyagr), | |
1644 NULL, NULL); | |
1645 m = g_list_append(m, act); | |
1646 | |
1647 act = purple_menu_action_new(_("IM with Password"), | |
1648 PURPLE_CALLBACK(silcpurple_buddy_privkey_menu), | |
1649 NULL, NULL); | |
1650 m = g_list_append(m, act); | |
1651 } | |
1652 | |
1653 if (pkfile) { | |
1654 act = purple_menu_action_new(_("Show Public Key"), | |
1655 PURPLE_CALLBACK(silcpurple_buddy_showkey), | |
1656 NULL, NULL); | |
1657 m = g_list_append(m, act); | |
1658 | |
1659 } else { | |
1660 act = purple_menu_action_new(_("Get Public Key..."), | |
1661 PURPLE_CALLBACK(silcpurple_buddy_getkey_menu), | |
1662 NULL, NULL); | |
1663 m = g_list_append(m, act); | |
1664 } | |
1665 | |
1666 if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) { | |
1667 act = purple_menu_action_new(_("Kill User"), | |
1668 PURPLE_CALLBACK(silcpurple_buddy_kill), | |
1669 NULL, NULL); | |
1670 m = g_list_append(m, act); | |
1671 } | |
1672 | |
1673 if (client_entry) { | |
1674 wb = silc_calloc(1, sizeof(*wb)); | |
1675 wb->sg = sg; | |
1676 wb->client_entry = client_entry; | |
1677 act = purple_menu_action_new(_("Draw On Whiteboard"), | |
1678 PURPLE_CALLBACK(silcpurple_buddy_wb), | |
1679 (void *)wb, NULL); | |
1680 m = g_list_append(m, act); | |
1681 } | |
1682 return m; | |
1683 } | |
1684 | |
1685 #ifdef SILC_ATTRIBUTE_USER_ICON | |
1686 void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleStoredImage *img) | |
1687 { | |
1688 SilcPurple sg = gc->proto_data; | |
1689 SilcClient client = sg->client; | |
1690 SilcClientConnection conn = sg->conn; | |
1691 SilcMime mime; | |
1692 char type[32]; | |
1693 unsigned char *icon; | |
1694 const char *t; | |
1695 SilcAttributeObjMime obj; | |
1696 | |
1697 /* Remove */ | |
1698 if (!img) { | |
1699 silc_client_attribute_del(client, conn, | |
1700 SILC_ATTRIBUTE_USER_ICON, NULL); | |
1701 return; | |
1702 } | |
1703 | |
1704 /* Add */ | |
1705 mime = silc_mime_alloc(); | |
1706 if (!mime) | |
1707 return; | |
1708 | |
1709 t = purple_imgstore_get_extension(img); | |
1710 if (!t || !strcmp(t, "icon")) { | |
1711 silc_mime_free(mime); | |
1712 return; | |
1713 } | |
1714 if (!strcmp(t, "jpg")) | |
1715 t = "jpeg"; | |
1716 g_snprintf(type, sizeof(type), "image/%s", t); | |
1717 silc_mime_add_field(mime, "Content-Type", type); | |
1718 silc_mime_add_data(mime, purple_imgstore_get_data(img), purple_imgstore_get_size(img)); | |
1719 | |
1720 obj.mime = icon = silc_mime_encode(mime, &obj.mime_len); | |
1721 if (obj.mime) | |
1722 silc_client_attribute_add(client, conn, | |
1723 SILC_ATTRIBUTE_USER_ICON, &obj, sizeof(obj)); | |
1724 | |
1725 silc_free(icon); | |
1726 silc_mime_free(mime); | |
1727 } | |
1728 #endif |