Merged pidgin/main into default
[pidgin-git.git] / libpurple / protocols / silc / buddy.c
blob1b0d14617973342a0300cffb3ffa9616ab31dc75
1 /*
3 silcpurple_buddy.c
5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 2004 - 2007 Pekka Riikonen
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.
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.
20 #include "internal.h"
21 #include "glibcompat.h"
22 PURPLE_BEGIN_IGNORE_CAST_ALIGN
23 #include "silc.h"
24 PURPLE_END_IGNORE_CAST_ALIGN
25 #include "silcclient.h"
26 #include "silcpurple.h"
27 #include "wb.h"
29 /***************************** Key Agreement *********************************/
31 static void
32 silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data);
34 static void
35 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
36 gboolean force_local);
38 typedef struct {
39 char *nick;
40 PurpleConnection *gc;
41 } *SilcPurpleResolve;
43 static void
44 silcpurple_buddy_keyagr_resolved(SilcClient client,
45 SilcClientConnection conn,
46 SilcStatus status,
47 SilcDList clients,
48 void *context)
50 PurpleConnection *gc = client->application;
51 SilcPurpleResolve r = context;
52 char tmp[256];
54 if (!clients) {
55 g_snprintf(tmp, sizeof(tmp),
56 _("User %s is not present in the network"), r->nick);
57 purple_notify_error(gc, _("Key Agreement"),
58 _("Cannot perform the key agreement"), tmp,
59 purple_request_cpar_from_connection(gc));
60 g_free(r->nick);
61 silc_free(r);
62 return;
65 silcpurple_buddy_keyagr_do(gc, r->nick, FALSE);
66 g_free(r->nick);
67 silc_free(r);
70 static void
71 silcpurple_buddy_keyagr_cb(SilcClient client,
72 SilcClientConnection conn,
73 SilcClientEntry client_entry,
74 SilcKeyAgreementStatus status,
75 SilcSKEKeyMaterial key,
76 void *context)
78 PurpleConnection *gc = client->application;
79 SilcPurple sg = purple_connection_get_protocol_data(gc);
81 if (!sg->conn)
82 return;
84 switch (status) {
85 case SILC_KEY_AGREEMENT_OK:
87 PurpleIMConversation *im;
88 char tmp[128];
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);
94 silc_ske_free_key_material(key);
97 /* Open IM window */
98 im = purple_conversations_find_im_with_account(
99 client_entry->nickname, sg->account);
100 if (im) {
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_conversation_window_show(purple_conversation_get_window(im));
105 } else {
106 im = purple_im_conversation_new(sg->account,
107 client_entry->nickname);
109 g_snprintf(tmp, sizeof(tmp), "%s [private key]", client_entry->nickname);
110 purple_conversation_set_title(PURPLE_CONVERSATION(im), tmp);
112 break;
114 case SILC_KEY_AGREEMENT_ERROR:
115 purple_notify_error(gc, _("Key Agreement"),
116 _("Error occurred during key agreement"), NULL,
117 purple_request_cpar_from_connection(gc));
118 break;
120 case SILC_KEY_AGREEMENT_FAILURE:
121 purple_notify_error(gc, _("Key Agreement"),
122 _("Key Agreement failed"), NULL,
123 purple_request_cpar_from_connection(gc));
124 break;
126 case SILC_KEY_AGREEMENT_TIMEOUT:
127 purple_notify_error(gc, _("Key Agreement"),
128 _("Timeout during key agreement"), NULL,
129 purple_request_cpar_from_connection(gc));
130 break;
132 case SILC_KEY_AGREEMENT_ABORTED:
133 purple_notify_error(gc, _("Key Agreement"),
134 _("Key agreement was aborted"), NULL,
135 purple_request_cpar_from_connection(gc));
136 break;
138 case SILC_KEY_AGREEMENT_ALREADY_STARTED:
139 purple_notify_error(gc, _("Key Agreement"), _("Key agreement is"
140 " already started"), NULL,
141 purple_request_cpar_from_connection(gc));
142 break;
144 case SILC_KEY_AGREEMENT_SELF_DENIED:
145 purple_notify_error(gc, _("Key Agreement"), _("Key agreement "
146 "cannot be started with yourself"), NULL,
147 purple_request_cpar_from_connection(gc));
148 break;
150 default:
151 break;
155 static void
156 silcpurple_buddy_keyagr_do(PurpleConnection *gc, const char *name,
157 gboolean force_local)
159 SilcPurple sg = purple_connection_get_protocol_data(gc);
160 SilcDList clients;
161 SilcClientEntry client_entry;
162 SilcClientConnectionParams params;
163 char *local_ip = NULL, *remote_ip = NULL;
164 gboolean local = TRUE;
165 SilcSocket sock;
167 if (!sg->conn || !name)
168 return;
170 /* Find client entry */
171 clients = silc_client_get_clients_local(sg->client, sg->conn, name,
172 FALSE);
173 if (!clients) {
174 /* Resolve unknown user */
175 SilcPurpleResolve r = silc_calloc(1, sizeof(*r));
176 if (!r)
177 return;
178 r->nick = g_strdup(name);
179 r->gc = gc;
180 silc_client_get_clients(sg->client, sg->conn, name, NULL,
181 silcpurple_buddy_keyagr_resolved, r);
182 return;
185 silc_socket_stream_get_info(silc_packet_stream_get_stream(sg->conn->stream),
186 &sock, NULL, NULL, NULL);
188 /* Resolve the local IP from the outgoing socket connection. We resolve
189 it to check whether we have a private range IP address or public IP
190 address. If we have public then we will assume that we are not behind
191 NAT and will provide automatically the point of connection to the
192 agreement. If we have private range address we assume that we are
193 behind NAT and we let the responder provide the point of connection.
195 The algorithm also checks the remote IP address of server connection.
196 If it is private range address and we have private range address we
197 assume that we are chatting in LAN and will provide the point of
198 connection.
200 Naturally this algorithm does not always get things right. */
202 if (silc_net_check_local_by_sock(sock, NULL, &local_ip)) {
203 /* Check if the IP is private */
204 if (!force_local && silcpurple_ip_is_private(local_ip)) {
205 local = FALSE;
207 /* Local IP is private, resolve the remote server IP to see whether
208 we are talking to Internet or just on LAN. */
209 if (silc_net_check_host_by_sock(sock, NULL,
210 &remote_ip))
211 if (silcpurple_ip_is_private(remote_ip))
212 /* We assume we are in LAN. Let's provide
213 the connection point. */
214 local = TRUE;
218 if (force_local)
219 local = TRUE;
221 if (local && !local_ip)
222 local_ip = silc_net_localip();
224 silc_dlist_start(clients);
225 client_entry = silc_dlist_get(clients);
227 memset(&params, 0, sizeof(params));
228 params.timeout_secs = 60;
229 if (local)
230 /* Provide connection point */
231 params.local_ip = local_ip;
233 /* Send the key agreement request */
234 silc_client_send_key_agreement(sg->client, sg->conn, client_entry,
235 &params, sg->public_key,
236 sg->private_key,
237 silcpurple_buddy_keyagr_cb, NULL);
239 silc_free(local_ip);
240 silc_free(remote_ip);
241 silc_client_list_free(sg->client, sg->conn, clients);
244 typedef struct {
245 SilcClient client;
246 SilcClientConnection conn;
247 SilcClientID client_id;
248 char *hostname;
249 SilcUInt16 port;
250 } *SilcPurpleKeyAgrAsk;
252 static void
253 silcpurple_buddy_keyagr_request_cb(SilcPurpleKeyAgrAsk a, gint id)
255 SilcClientEntry client_entry;
256 SilcClientConnectionParams params;
258 if (id != 1)
259 goto out;
261 /* Get the client entry. */
262 client_entry = silc_client_get_client_by_id(a->client, a->conn,
263 &a->client_id);
264 if (!client_entry) {
265 purple_notify_error(a->client->application, _("Key Agreement"),
266 _("The remote user is not present in the network any more"),
267 NULL, NULL);
268 goto out;
271 /* If the hostname was provided by the requestor perform the key agreement
272 now. Otherwise, we will send him a request to connect to us. */
273 if (a->hostname) {
274 memset(&params, 0, sizeof(params));
275 params.timeout_secs = 60;
276 silc_client_perform_key_agreement(a->client, a->conn,
277 client_entry, &params,
278 a->conn->public_key,
279 a->conn->private_key,
280 a->hostname, a->port,
281 silcpurple_buddy_keyagr_cb, NULL);
282 } else {
283 /* Send request. Force us as the point of connection since requestor
284 did not provide the point of connection. */
285 silcpurple_buddy_keyagr_do(a->client->application,
286 client_entry->nickname, TRUE);
289 out:
290 g_free(a->hostname);
291 silc_free(a);
294 void silcpurple_buddy_keyagr_request(SilcClient client,
295 SilcClientConnection conn,
296 SilcClientEntry client_entry,
297 const char *hostname, SilcUInt16 port,
298 SilcUInt16 protocol)
300 char tmp[128], tmp2[128];
301 SilcPurpleKeyAgrAsk a;
302 PurpleConnection *gc = client->application;
304 /* For now Pidgin don't support UDP key agreement */
305 if (protocol == 1)
306 return;
308 g_snprintf(tmp, sizeof(tmp),
309 _("Key agreement request received from %s. Would you like to "
310 "perform the key agreement?"), client_entry->nickname);
311 if (hostname)
312 g_snprintf(tmp2, sizeof(tmp2),
313 _("The remote user is waiting key agreement on:\n"
314 "Remote host: %s\nRemote port: %d"), hostname, port);
316 a = silc_calloc(1, sizeof(*a));
317 if (!a)
318 return;
319 a->client = client;
320 a->conn = conn;
321 a->client_id = client_entry->id;
322 if (hostname)
323 a->hostname = g_strdup(hostname);
324 a->port = port;
326 purple_request_action(client->application, _("Key Agreement Request"), tmp,
327 hostname ? tmp2 : NULL, 1, purple_request_cpar_from_connection(gc),
328 a, 2, _("Yes"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb),
329 _("No"), G_CALLBACK(silcpurple_buddy_keyagr_request_cb));
332 static void
333 silcpurple_buddy_keyagr(PurpleBlistNode *node, gpointer data)
335 PurpleBuddy *buddy;
336 PurpleAccount *account;
338 buddy = (PurpleBuddy *)node;
339 account = purple_buddy_get_account(buddy);
340 silcpurple_buddy_keyagr_do(purple_account_get_connection(account),
341 purple_buddy_get_name(buddy), FALSE);
345 /**************************** Static IM Key **********************************/
347 static void
348 silcpurple_buddy_resetkey(PurpleBlistNode *node, gpointer data)
350 PurpleBuddy *b;
351 PurpleConnection *gc;
352 SilcPurple sg;
353 SilcDList clients;
355 g_return_if_fail(PURPLE_IS_BUDDY(node));
357 b = (PurpleBuddy *) node;
358 gc = purple_account_get_connection(purple_buddy_get_account(b));
359 sg = purple_connection_get_protocol_data(gc);
361 /* Find client entry */
362 clients = silc_client_get_clients_local(sg->client, sg->conn,
363 purple_buddy_get_name(b), FALSE);
364 if (!clients)
365 return;
367 silc_dlist_start(clients);
368 silc_client_del_private_message_key(sg->client, sg->conn,
369 silc_dlist_get(clients));
370 silc_client_list_free(sg->client, sg->conn, clients);
373 typedef struct {
374 SilcClient client;
375 SilcClientConnection conn;
376 SilcClientID client_id;
377 } *SilcPurplePrivkey;
379 static void
380 silcpurple_buddy_privkey(PurpleConnection *gc, const char *name);
382 static void
383 silcpurple_buddy_privkey_cb(SilcPurplePrivkey p, const char *passphrase)
385 SilcClientEntry client_entry;
387 if (!passphrase || !(*passphrase)) {
388 silc_free(p);
389 return;
392 /* Get the client entry. */
393 client_entry = silc_client_get_client_by_id(p->client, p->conn,
394 &p->client_id);
395 if (!client_entry) {
396 purple_notify_error(p->client->application, _("IM With Password"),
397 _("The remote user is not present in the network any more"),
398 NULL, NULL);
399 silc_free(p);
400 return;
403 /* Set the private message key */
404 silc_client_del_private_message_key(p->client, p->conn,
405 client_entry);
406 silc_client_add_private_message_key(p->client, p->conn,
407 client_entry, NULL, NULL,
408 (unsigned char *)passphrase,
409 strlen(passphrase));
410 silc_free(p);
413 static void
414 silcpurple_buddy_privkey_resolved(SilcClient client,
415 SilcClientConnection conn,
416 SilcStatus status,
417 SilcDList clients,
418 void *context)
420 char tmp[256];
422 if (!clients) {
423 g_snprintf(tmp, sizeof(tmp),
424 _("User %s is not present in the network"),
425 (const char *)context);
426 purple_notify_error(client->application, _("IM With Password"),
427 _("Cannot set IM key"), tmp, NULL);
428 g_free(context);
429 return;
432 silcpurple_buddy_privkey(client->application, context);
433 g_free(context);
436 static void
437 silcpurple_buddy_privkey(PurpleConnection *gc, const char *name)
439 SilcPurple sg = purple_connection_get_protocol_data(gc);
440 SilcPurplePrivkey p;
441 SilcDList clients;
442 SilcClientEntry client_entry;
444 if (!name)
445 return;
447 /* Find client entry */
448 clients = silc_client_get_clients_local(sg->client, sg->conn,
449 name, FALSE);
450 if (!clients) {
451 silc_client_get_clients(sg->client, sg->conn, name, NULL,
452 silcpurple_buddy_privkey_resolved,
453 g_strdup(name));
454 return;
457 silc_dlist_start(clients);
458 client_entry = silc_dlist_get(clients);
460 p = silc_calloc(1, sizeof(*p));
461 if (!p)
462 return;
463 p->client = sg->client;
464 p->conn = sg->conn;
465 p->client_id = client_entry->id;
466 purple_request_input(gc, _("IM With Password"), NULL,
467 _("Set IM Password"), NULL, FALSE, TRUE, NULL,
468 _("OK"), G_CALLBACK(silcpurple_buddy_privkey_cb),
469 _("Cancel"), G_CALLBACK(silcpurple_buddy_privkey_cb),
470 purple_request_cpar_from_connection(gc), p);
472 silc_client_list_free(sg->client, sg->conn, clients);
475 static void
476 silcpurple_buddy_privkey_menu(PurpleBlistNode *node, gpointer data)
478 PurpleBuddy *buddy;
479 PurpleConnection *gc;
481 g_return_if_fail(PURPLE_IS_BUDDY(node));
483 buddy = (PurpleBuddy *) node;
484 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
486 silcpurple_buddy_privkey(gc, purple_buddy_get_name(buddy));
490 /**************************** Get Public Key *********************************/
492 typedef struct {
493 SilcClient client;
494 SilcClientConnection conn;
495 SilcClientID client_id;
496 } *SilcPurpleBuddyGetkey;
498 static void
499 silcpurple_buddy_getkey(PurpleConnection *gc, const char *name);
501 static SilcBool
502 silcpurple_buddy_getkey_cb(SilcClient client, SilcClientConnection conn,
503 SilcCommand command, SilcStatus status,
504 SilcStatus error, void *context, va_list ap)
506 SilcClientEntry client_entry;
507 SilcPurpleBuddyGetkey g = context;
509 if (status != SILC_STATUS_OK) {
510 purple_notify_error(g->client->application, _("Get Public Key"),
511 _("The remote user is not present in the network any more"),
512 NULL, NULL);
513 silc_free(g);
514 return FALSE;
517 /* Get the client entry. */
518 client_entry = silc_client_get_client_by_id(g->client, g->conn,
519 &g->client_id);
520 if (!client_entry) {
521 purple_notify_error(g->client->application, _("Get Public Key"),
522 _("The remote user is not present in the network any more"),
523 NULL, NULL);
524 silc_free(g);
525 return FALSE;
528 if (!client_entry->public_key) {
529 silc_free(g);
530 return FALSE;
533 /* Now verify the public key */
534 silcpurple_verify_public_key(g->client, g->conn, client_entry->nickname,
535 SILC_CONN_CLIENT, client_entry->public_key,
536 NULL, NULL);
537 silc_free(g);
538 return TRUE;
541 static void
542 silcpurple_buddy_getkey_resolved(SilcClient client,
543 SilcClientConnection conn,
544 SilcStatus status,
545 SilcDList clients,
546 void *context)
548 char tmp[256];
550 if (!clients) {
551 g_snprintf(tmp, sizeof(tmp),
552 _("User %s is not present in the network"),
553 (const char *)context);
554 purple_notify_error(client->application, _("Get Public Key"),
555 _("Cannot fetch the public key"), tmp, NULL);
556 g_free(context);
557 return;
560 silcpurple_buddy_getkey(client->application, context);
561 g_free(context);
564 static void
565 silcpurple_buddy_getkey(PurpleConnection *gc, const char *name)
567 SilcPurple sg = purple_connection_get_protocol_data(gc);
568 SilcClient client = sg->client;
569 SilcClientConnection conn = sg->conn;
570 SilcClientEntry client_entry;
571 SilcDList clients;
572 SilcPurpleBuddyGetkey g;
573 SilcUInt16 cmd_ident;
575 if (!name)
576 return;
578 /* Find client entry */
579 clients = silc_client_get_clients_local(client, conn, name, FALSE);
580 if (!clients) {
581 silc_client_get_clients(client, conn, name, NULL,
582 silcpurple_buddy_getkey_resolved,
583 g_strdup(name));
584 return;
587 silc_dlist_start(clients);
588 client_entry = silc_dlist_get(clients);
590 /* Call GETKEY */
591 g = silc_calloc(1, sizeof(*g));
592 if (!g)
593 return;
594 g->client = client;
595 g->conn = conn;
596 g->client_id = client_entry->id;
597 cmd_ident = silc_client_command_call(client, conn, NULL, "GETKEY",
598 client_entry->nickname, NULL);
599 silc_client_command_pending(conn, SILC_COMMAND_GETKEY, cmd_ident,
600 silcpurple_buddy_getkey_cb, g);
601 silc_client_list_free(client, conn, clients);
604 static void
605 silcpurple_buddy_getkey_menu(PurpleBlistNode *node, gpointer data)
607 PurpleBuddy *buddy;
608 PurpleConnection *gc;
610 g_return_if_fail(PURPLE_IS_BUDDY(node));
612 buddy = (PurpleBuddy *) node;
613 gc = purple_account_get_connection(purple_buddy_get_account(buddy));
615 silcpurple_buddy_getkey(gc, purple_buddy_get_name(buddy));
618 static void
619 silcpurple_buddy_showkey(PurpleBlistNode *node, gpointer data)
621 PurpleBuddy *b;
622 PurpleConnection *gc;
623 SilcPurple sg;
624 SilcPublicKey public_key;
625 const char *pkfile;
627 g_return_if_fail(PURPLE_IS_BUDDY(node));
629 b = (PurpleBuddy *) node;
630 gc = purple_account_get_connection(purple_buddy_get_account(b));
631 sg = purple_connection_get_protocol_data(gc);
633 pkfile = purple_blist_node_get_string(node, "public-key");
634 if (!silc_pkcs_load_public_key(pkfile, &public_key)) {
635 purple_notify_error(gc, _("Show Public Key"),
636 _("Could not load public key"), NULL,
637 purple_request_cpar_from_connection(gc));
638 return;
641 silcpurple_show_public_key(sg, purple_buddy_get_name(b), public_key, NULL, NULL);
642 silc_pkcs_public_key_free(public_key);
646 /**************************** Buddy routines *********************************/
648 /* The buddies are implemented by using the WHOIS and WATCH commands that
649 can be used to search users by their public key. Since nicknames aren't
650 unique in SILC we cannot trust the buddy list using their nickname. We
651 associate public keys to buddies and use those to search and watch
652 in the network.
654 The problem is that Purple does not return PurpleBuddy contexts to the
655 callbacks but the buddy names. Naturally, this is not going to work
656 with SILC. But, for now, we have to do what we can... */
658 typedef struct {
659 SilcClient client;
660 SilcClientConnection conn;
661 SilcClientID client_id;
662 PurpleBuddy *b;
663 unsigned char *offline_pk;
664 SilcUInt32 offline_pk_len;
665 SilcPublicKey public_key;
666 unsigned int offline : 1;
667 unsigned int pubkey_search : 1;
668 unsigned int init : 1;
669 } *SilcPurpleBuddyRes;
671 static void
672 silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id);
673 static void
674 silcpurple_add_buddy_resolved(SilcClient client,
675 SilcClientConnection conn,
676 SilcStatus status,
677 SilcDList clients,
678 void *context);
680 void silcpurple_get_info(PurpleConnection *gc, const char *who)
682 SilcPurple sg = purple_connection_get_protocol_data(gc);
683 SilcClient client = sg->client;
684 SilcClientConnection conn = sg->conn;
685 SilcClientEntry client_entry;
686 PurpleBuddy *b;
687 const char *filename, *nick = who;
688 char tmp[256];
690 if (!who)
691 return;
692 if (strlen(who) > 1 && who[0] == '@')
693 nick = who + 1;
694 if (strlen(who) > 1 && who[0] == '*')
695 nick = who + 1;
696 if (strlen(who) > 2 && who[0] == '*' && who[1] == '@')
697 nick = who + 2;
699 b = purple_blist_find_buddy(purple_connection_get_account(gc), nick);
700 if (b) {
701 /* See if we have this buddy's public key. If we do use that
702 to search the details. */
703 gpointer proto_data;
704 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
705 if (filename) {
706 /* Call WHOIS. The user info is displayed in the WHOIS
707 command reply. */
708 silc_client_command_call(client, conn, NULL, "WHOIS",
709 "-details", "-pubkey", filename, NULL);
710 return;
713 if (!(proto_data = purple_buddy_get_protocol_data(b))) {
714 g_snprintf(tmp, sizeof(tmp),
715 _("User %s is not present in the network"), purple_buddy_get_name(b));
716 purple_notify_error(gc, _("User Information"),
717 _("Cannot get user information"), tmp,
718 purple_request_cpar_from_connection(gc));
719 return;
722 client_entry = silc_client_get_client_by_id(client, conn, proto_data);
723 if (client_entry) {
724 /* Call WHOIS. The user info is displayed in the WHOIS
725 command reply. */
726 silc_client_command_call(client, conn, NULL, "WHOIS",
727 client_entry->nickname, "-details", NULL);
729 } else {
730 /* Call WHOIS just with nickname. */
731 silc_client_command_call(client, conn, NULL, "WHOIS", nick, NULL);
735 static void
736 silcpurple_add_buddy_pk_no(SilcPurpleBuddyRes r)
738 char tmp[512];
739 g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not trusted"),
740 purple_buddy_get_name(r->b));
741 purple_notify_error(r->client->application, _("Add Buddy"), tmp,
742 _("You cannot receive buddy notifications until you "
743 "import his/her public key. You can use the Get Public Key "
744 "command to get the public key."),
745 purple_request_cpar_from_account(purple_buddy_get_account(r->b)));
746 purple_protocol_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
749 static void
750 silcpurple_add_buddy_save(SilcBool success, void *context)
752 SilcPurpleBuddyRes r = context;
753 PurpleBuddy *b = r->b;
754 SilcClientEntry client_entry;
755 SilcAttributePayload attr;
756 SilcAttribute attribute;
757 SilcVCardStruct vcard;
758 SilcMime message = NULL, extension = NULL;
759 SilcMime usericon = NULL;
760 SilcAttributeObjPk serverpk, usersign, serversign;
761 gboolean usign_success = TRUE, ssign_success = TRUE;
762 char filename[512], filename2[512], *fingerprint = NULL, *tmp;
763 SilcUInt32 len;
764 SilcHash hash;
765 gsize i;
767 if (!success) {
768 /* The user did not trust the public key. */
769 silcpurple_add_buddy_pk_no(r);
770 silc_free(r->offline_pk);
771 if (r->public_key)
772 silc_pkcs_public_key_free(r->public_key);
773 silc_free(r);
774 return;
777 if (r->offline) {
778 /* User is offline. Associate the imported public key with
779 this user. */
780 fingerprint = silc_hash_fingerprint(NULL, r->offline_pk,
781 r->offline_pk_len);
782 for (i = 0; i < strlen(fingerprint); i++)
783 if (fingerprint[i] == ' ')
784 fingerprint[i] = '_';
785 g_snprintf(filename, sizeof(filename) - 1,
786 "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
787 silcpurple_silcdir(), fingerprint);
788 purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename);
789 purple_protocol_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_OFFLINE, NULL);
790 silc_free(fingerprint);
791 silc_free(r->offline_pk);
792 if (r->public_key)
793 silc_pkcs_public_key_free(r->public_key);
794 silc_free(r);
795 return;
798 /* Get the client entry. */
799 client_entry = silc_client_get_client_by_id(r->client, r->conn,
800 &r->client_id);
801 if (!client_entry) {
802 silc_free(r->offline_pk);
803 silc_pkcs_public_key_free(r->public_key);
804 if (r->public_key)
805 silc_pkcs_public_key_free(r->public_key);
806 silc_free(r);
807 return;
810 memset(&vcard, 0, sizeof(vcard));
811 memset(&serverpk, 0, sizeof(serverpk));
812 memset(&usersign, 0, sizeof(usersign));
813 memset(&serversign, 0, sizeof(serversign));
815 /* Now that we have the public key and we trust it now we
816 save the attributes of the buddy and update its status. */
818 if (client_entry->attrs) {
819 silc_dlist_start(client_entry->attrs);
820 while ((attr = silc_dlist_get(client_entry->attrs))
821 != SILC_LIST_END) {
822 attribute = silc_attribute_get_attribute(attr);
824 switch (attribute) {
825 case SILC_ATTRIBUTE_USER_INFO:
826 if (!silc_attribute_get_object(attr, (void *)&vcard,
827 sizeof(vcard)))
828 continue;
829 break;
831 case SILC_ATTRIBUTE_STATUS_MESSAGE:
832 message = silc_mime_alloc();
833 if (!silc_attribute_get_object(attr, (void *)message,
834 sizeof(*message)))
835 continue;
836 break;
838 case SILC_ATTRIBUTE_EXTENSION:
839 extension = silc_mime_alloc();
840 if (!silc_attribute_get_object(attr, (void *)extension,
841 sizeof(*extension)))
842 continue;
843 break;
845 case SILC_ATTRIBUTE_USER_ICON:
846 usericon = silc_mime_alloc();
847 if (!silc_attribute_get_object(attr, (void *)usericon,
848 sizeof(*usericon)))
849 continue;
850 break;
852 case SILC_ATTRIBUTE_SERVER_PUBLIC_KEY:
853 if (serverpk.type)
854 continue;
855 if (!silc_attribute_get_object(attr, (void *)&serverpk,
856 sizeof(serverpk)))
857 continue;
858 break;
860 case SILC_ATTRIBUTE_USER_DIGITAL_SIGNATURE:
861 if (usersign.data)
862 continue;
863 if (!silc_attribute_get_object(attr, (void *)&usersign,
864 sizeof(usersign)))
865 continue;
866 break;
868 case SILC_ATTRIBUTE_SERVER_DIGITAL_SIGNATURE:
869 if (serversign.data)
870 continue;
871 if (!silc_attribute_get_object(attr, (void *)&serversign,
872 sizeof(serversign)))
873 continue;
874 break;
876 default:
877 break;
882 /* Verify the attribute signatures */
883 silc_hash_alloc((const unsigned char *)"sha1", &hash);
885 if (usersign.data) {
886 unsigned char *verifyd;
887 SilcUInt32 verify_len;
889 verifyd = silc_attribute_get_verify_data(client_entry->attrs,
890 FALSE, &verify_len);
891 if (verifyd && !silc_pkcs_verify(client_entry->public_key,
892 usersign.data,
893 usersign.data_len,
894 verifyd, verify_len, hash))
895 usign_success = FALSE;
896 silc_free(verifyd);
899 if (serversign.data) {
900 SilcPublicKey public_key;
901 SilcPKCSType type = 0;
902 unsigned char *verifyd;
903 SilcUInt32 verify_len;
905 if (purple_strequal(serverpk.type, "silc-rsa"))
906 type = SILC_PKCS_SILC;
907 else if (purple_strequal(serverpk.type, "ssh-rsa"))
908 type = SILC_PKCS_SSH2;
909 else if (purple_strequal(serverpk.type, "x509v3-sign-rsa"))
910 type = SILC_PKCS_X509V3;
911 else if (purple_strequal(serverpk.type, "pgp-sign-rsa"))
912 type = SILC_PKCS_OPENPGP;
914 if (silc_pkcs_public_key_alloc(type, serverpk.data,
915 serverpk.data_len,
916 &public_key)) {
917 verifyd = silc_attribute_get_verify_data(client_entry->attrs,
918 TRUE, &verify_len);
919 if (verifyd && !silc_pkcs_verify(public_key,
920 serversign.data,
921 serversign.data_len,
922 verifyd, verify_len,
923 hash))
924 ssign_success = FALSE;
925 silc_pkcs_public_key_free(public_key);
926 silc_free(verifyd);
930 fingerprint = silc_fingerprint(client_entry->fingerprint, 20);
931 for (i = 0; i < strlen(fingerprint); i++)
932 if (fingerprint[i] == ' ')
933 fingerprint[i] = '_';
935 if (usign_success || ssign_success) {
936 struct passwd *pw;
938 memset(filename2, 0, sizeof(filename2));
940 /* Filename for dir */
941 tmp = fingerprint + strlen(fingerprint) - 9;
942 g_snprintf(filename, sizeof(filename) - 1,
943 "%s" G_DIR_SEPARATOR_S "friends" G_DIR_SEPARATOR_S "%s",
944 silcpurple_silcdir(), tmp);
946 pw = getpwuid(getuid());
947 if (!pw)
948 return;
950 /* Create dir if it doesn't exist */
951 if (pw->pw_uid == geteuid()) {
952 if (g_mkdir(filename, 0755) != 0 && errno != EEXIST)
953 return;
956 /* Save VCard */
957 g_snprintf(filename2, sizeof(filename2) - 1,
958 "%s" G_DIR_SEPARATOR_S "vcard", filename);
959 if (vcard.full_name) {
960 tmp = (char *)silc_vcard_encode(&vcard, &len);
961 silc_file_writefile(filename2, tmp, len);
962 silc_free(tmp);
965 /* Save status message */
966 if (message) {
967 memset(filename2, 0, sizeof(filename2));
968 g_snprintf(filename2, sizeof(filename2) - 1,
969 "%s" G_DIR_SEPARATOR_S "status_message.mime",
970 filename);
971 tmp = (char *)silc_mime_get_data(message, &len);
972 silc_file_writefile(filename2, tmp, len);
973 silc_mime_free(message);
976 /* Save extension data */
977 if (extension) {
978 memset(filename2, 0, sizeof(filename2));
979 g_snprintf(filename2, sizeof(filename2) - 1,
980 "%s" G_DIR_SEPARATOR_S "extension.mime",
981 filename);
982 tmp = (char *)silc_mime_get_data(extension, &len);
983 silc_file_writefile(filename2, tmp, len);
984 silc_mime_free(extension);
987 /* Save user icon */
988 if (usericon) {
989 const char *type = silc_mime_get_field(usericon, "Content-Type");
990 if (type &&
991 (purple_strequal(type, "image/jpeg") ||
992 purple_strequal(type, "image/gif") ||
993 purple_strequal(type, "image/bmp") ||
994 purple_strequal(type, "image/png"))) {
995 const unsigned char *data;
996 SilcUInt32 data_len;
997 data = silc_mime_get_data(usericon, &data_len);
998 if (data) {
999 /* TODO: Check if SILC gives us something to use as the checksum instead */
1000 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);
1003 silc_mime_free(usericon);
1007 /* Save the public key path to buddy properties, as it is used
1008 to identify the buddy in the network (and not the nickname). */
1009 memset(filename, 0, sizeof(filename));
1010 g_snprintf(filename, sizeof(filename) - 1,
1011 "%s" G_DIR_SEPARATOR_S "clientkeys" G_DIR_SEPARATOR_S "clientkey_%s.pub",
1012 silcpurple_silcdir(), fingerprint);
1013 purple_blist_node_set_string((PurpleBlistNode *)b, "public-key", filename);
1015 /* Update online status */
1016 purple_protocol_got_user_status(purple_buddy_get_account(r->b), purple_buddy_get_name(r->b), SILCPURPLE_STATUS_ID_AVAILABLE, NULL);
1018 /* Finally, start watching this user so we receive its status
1019 changes from the server */
1020 g_snprintf(filename2, sizeof(filename2) - 1, "+%s", filename);
1021 silc_client_command_call(r->client, r->conn, NULL, "WATCH", "-pubkey",
1022 filename2, NULL);
1024 silc_hash_free(hash);
1025 silc_free(fingerprint);
1026 silc_free(r->offline_pk);
1027 if (r->public_key)
1028 silc_pkcs_public_key_free(r->public_key);
1029 silc_free(r);
1032 static void
1033 silcpurple_add_buddy_ask_import(void *user_data, const char *name)
1035 SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data;
1037 /* Load the public key */
1038 if (!silc_pkcs_load_public_key(name, &r->public_key)) {
1039 silcpurple_add_buddy_ask_pk_cb(r, 0);
1040 purple_notify_error(r->client->application, _("Add Buddy"),
1041 _("Could not load public key"), NULL,
1042 purple_request_cpar_from_account(
1043 purple_buddy_get_account(r->b)));
1044 return;
1047 /* Now verify the public key */
1048 r->offline_pk = silc_pkcs_public_key_encode(r->public_key, &r->offline_pk_len);
1049 silcpurple_verify_public_key(r->client, r->conn, purple_buddy_get_name(r->b),
1050 SILC_CONN_CLIENT, r->public_key,
1051 silcpurple_add_buddy_save, r);
1054 static void
1055 silcpurple_add_buddy_ask_pk_cancel(void *user_data, const char *name)
1057 SilcPurpleBuddyRes r = (SilcPurpleBuddyRes)user_data;
1059 /* The user did not import public key. The buddy is unusable. */
1060 silcpurple_add_buddy_pk_no(r);
1061 silc_free(r);
1064 static void
1065 silcpurple_add_buddy_ask_pk_cb(SilcPurpleBuddyRes r, gint id)
1067 if (id != 0) {
1068 /* The user did not import public key. The buddy is unusable. */
1069 silcpurple_add_buddy_pk_no(r);
1070 silc_free(r);
1071 return;
1074 /* Open file selector to select the public key. */
1075 purple_request_file(r->client->application, _("Open..."), NULL, FALSE,
1076 G_CALLBACK(silcpurple_add_buddy_ask_import),
1077 G_CALLBACK(silcpurple_add_buddy_ask_pk_cancel),
1078 purple_request_cpar_from_account(purple_buddy_get_account(r->b)), r);
1082 static void
1083 silcpurple_add_buddy_ask_pk(SilcPurpleBuddyRes r)
1085 char tmp[512];
1086 g_snprintf(tmp, sizeof(tmp), _("The %s buddy is not present in the network"),
1087 purple_buddy_get_name(r->b));
1088 purple_request_action(r->client->application, _("Add Buddy"), tmp,
1089 _("To add the buddy you must import his/her public key. "
1090 "Press Import to import a public key."), 0,
1091 purple_request_cpar_from_account(purple_buddy_get_account(r->b)), r, 2,
1092 _("Cancel"), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb),
1093 _("_Import..."), G_CALLBACK(silcpurple_add_buddy_ask_pk_cb));
1096 static SilcBool
1097 silcpurple_add_buddy_getkey_cb(SilcClient client, SilcClientConnection conn,
1098 SilcCommand command, SilcStatus status,
1099 SilcStatus error, void *context, va_list ap)
1101 SilcPurpleBuddyRes r = context;
1102 SilcClientEntry client_entry;
1104 if (status != SILC_STATUS_OK) {
1105 /* The buddy is offline/nonexistent. We will require user
1106 to associate a public key with the buddy or the buddy
1107 cannot be added. */
1108 r->offline = TRUE;
1109 silcpurple_add_buddy_ask_pk(r);
1110 return FALSE;
1113 /* Get the client entry. */
1114 client_entry = silc_client_get_client_by_id(r->client, r->conn,
1115 &r->client_id);
1116 if (!client_entry || !client_entry->public_key) {
1117 /* The buddy is offline/nonexistent. We will require user
1118 to associate a public key with the buddy or the buddy
1119 cannot be added. */
1120 r->offline = TRUE;
1121 silcpurple_add_buddy_ask_pk(r);
1122 return FALSE;
1125 /* Now verify the public key */
1126 silcpurple_verify_public_key(r->client, r->conn, client_entry->nickname,
1127 SILC_CONN_CLIENT, client_entry->public_key,
1128 silcpurple_add_buddy_save, r);
1129 return TRUE;
1132 static void
1133 silcpurple_add_buddy_select_cb(SilcPurpleBuddyRes r, PurpleRequestFields *fields)
1135 PurpleRequestField *f;
1136 GList *list;
1137 SilcClientEntry client_entry;
1138 SilcDList clients;
1140 f = purple_request_fields_get_field(fields, "list");
1141 list = purple_request_field_list_get_selected(f);
1142 if (!list) {
1143 /* The user did not select any user. */
1144 silcpurple_add_buddy_pk_no(r);
1145 silc_free(r);
1146 return;
1149 client_entry = purple_request_field_list_get_data(f, list->data);
1150 clients = silc_dlist_init();
1151 silc_dlist_add(clients, client_entry);
1152 silcpurple_add_buddy_resolved(r->client, r->conn, SILC_STATUS_OK,
1153 clients, r);
1154 silc_dlist_uninit(clients);
1157 static void
1158 silcpurple_add_buddy_select_cancel(SilcPurpleBuddyRes r, PurpleRequestFields *fields)
1160 /* The user did not select any user. */
1161 silcpurple_add_buddy_pk_no(r);
1162 silc_free(r);
1165 static void
1166 silcpurple_add_buddy_select(SilcPurpleBuddyRes r, SilcDList clients)
1168 PurpleRequestFields *fields;
1169 PurpleRequestFieldGroup *g;
1170 PurpleRequestField *f;
1171 char tmp[512], tmp2[128];
1172 char *fingerprint;
1173 SilcClientEntry client_entry;
1175 fields = purple_request_fields_new();
1176 g = purple_request_field_group_new(NULL);
1177 f = purple_request_field_list_new("list", NULL);
1178 purple_request_field_group_add_field(g, f);
1179 purple_request_field_list_set_multi_select(f, FALSE);
1180 purple_request_fields_add_group(fields, g);
1182 silc_dlist_start(clients);
1183 while ((client_entry = silc_dlist_get(clients))) {
1184 fingerprint = NULL;
1185 if (*client_entry->fingerprint) {
1186 fingerprint = silc_fingerprint(client_entry->fingerprint, 20);
1187 g_snprintf(tmp2, sizeof(tmp2), "\n%s", fingerprint);
1189 g_snprintf(tmp, sizeof(tmp), "%s - %s (%s@%s)%s",
1190 client_entry->realname, client_entry->nickname,
1191 client_entry->username, *client_entry->hostname ?
1192 client_entry->hostname : "",
1193 fingerprint ? tmp2 : "");
1194 purple_request_field_list_add_icon(f, tmp, NULL, client_entry);
1195 silc_free(fingerprint);
1198 purple_request_fields(r->client->application, _("Add Buddy"),
1199 _("Select correct user"),
1200 r->pubkey_search
1201 ? _("More than one user was found with the same public key. Select "
1202 "the correct user from the list to add to the buddy list.")
1203 : _("More than one user was found with the same name. Select "
1204 "the correct user from the list to add to the buddy list."),
1205 fields,
1206 _("OK"), G_CALLBACK(silcpurple_add_buddy_select_cb),
1207 _("Cancel"), G_CALLBACK(silcpurple_add_buddy_select_cancel),
1208 purple_request_cpar_from_account(purple_buddy_get_account(r->b)), r);
1211 static void
1212 silcpurple_add_buddy_resolved(SilcClient client,
1213 SilcClientConnection conn,
1214 SilcStatus status,
1215 SilcDList clients,
1216 void *context)
1218 SilcPurpleBuddyRes r = context;
1219 PurpleBuddy *b = r->b;
1220 SilcAttributePayload pub;
1221 SilcAttributeObjPk userpk;
1222 const char *filename;
1223 SilcClientEntry client_entry = NULL;
1224 SilcUInt16 cmd_ident;
1225 const char *name;
1227 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
1229 /* If the buddy is offline/nonexistent, we will require user
1230 to associate a public key with the buddy or the buddy
1231 cannot be added. */
1232 if (!clients) {
1233 if (r->init) {
1234 silc_free(r);
1235 return;
1238 r->offline = TRUE;
1239 /* If the user has already associated a public key, try loading it
1240 * before prompting the user to load it again */
1241 if (filename != NULL)
1242 silcpurple_add_buddy_ask_import(r, filename);
1243 else
1244 silcpurple_add_buddy_ask_pk(r);
1245 return;
1248 /* If more than one client was found with nickname, we need to verify
1249 from user which one is the correct. */
1250 if (silc_dlist_count(clients) > 1 && !r->pubkey_search) {
1251 if (r->init) {
1252 silc_free(r);
1253 return;
1256 silcpurple_add_buddy_select(r, clients);
1257 return;
1260 silc_dlist_start(clients);
1261 client_entry = silc_dlist_get(clients);
1263 name = purple_buddy_get_name(b);
1265 /* If we searched using public keys and more than one entry was found
1266 the same person is logged on multiple times. */
1267 if (silc_dlist_count(clients) > 1 && r->pubkey_search && name) {
1268 if (r->init) {
1269 /* Find the entry that closest matches to the
1270 buddy nickname. */
1271 SilcClientEntry entry;
1272 silc_dlist_start(clients);
1273 while ((entry = silc_dlist_get(clients))) {
1274 if (!g_ascii_strncasecmp(name, entry->nickname,
1275 strlen(name))) {
1276 client_entry = entry;
1277 break;
1280 } else {
1281 /* Verify from user which one is correct */
1282 silcpurple_add_buddy_select(r, clients);
1283 return;
1287 g_return_if_fail(client_entry != NULL);
1289 /* The client was found. Now get its public key and verify
1290 that before adding the buddy. */
1291 memset(&userpk, 0, sizeof(userpk));
1292 purple_buddy_set_protocol_data(b, silc_memdup(&client_entry->id, sizeof(client_entry->id)));
1293 r->client_id = client_entry->id;
1295 /* Get the public key from attributes, if not present then
1296 resolve it with GETKEY unless we have it cached already. */
1297 if (client_entry->attrs && !client_entry->public_key) {
1298 pub = silcpurple_get_attr(client_entry->attrs,
1299 SILC_ATTRIBUTE_USER_PUBLIC_KEY);
1300 if (!pub || !silc_attribute_get_object(pub, (void *)&userpk,
1301 sizeof(userpk))) {
1302 /* Get public key with GETKEY */
1303 cmd_ident =
1304 silc_client_command_call(client, conn, NULL,
1305 "GETKEY", client_entry->nickname, NULL);
1306 silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
1307 cmd_ident,
1308 silcpurple_add_buddy_getkey_cb,
1310 return;
1312 if (!silc_pkcs_public_key_alloc(SILC_PKCS_SILC,
1313 userpk.data, userpk.data_len,
1314 &client_entry->public_key))
1315 return;
1316 silc_free(userpk.data);
1317 } else if (filename && !client_entry->public_key) {
1318 if (!silc_pkcs_load_public_key(filename, &client_entry->public_key)) {
1319 /* Get public key with GETKEY */
1320 cmd_ident =
1321 silc_client_command_call(client, conn, NULL,
1322 "GETKEY", client_entry->nickname, NULL);
1323 silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
1324 cmd_ident,
1325 silcpurple_add_buddy_getkey_cb,
1327 return;
1329 } else if (!client_entry->public_key) {
1330 /* Get public key with GETKEY */
1331 cmd_ident =
1332 silc_client_command_call(client, conn, NULL,
1333 "GETKEY", client_entry->nickname, NULL);
1334 silc_client_command_pending(conn, SILC_COMMAND_GETKEY,
1335 cmd_ident,
1336 silcpurple_add_buddy_getkey_cb,
1338 return;
1341 /* We have the public key, verify it. */
1342 silcpurple_verify_public_key(client, conn, client_entry->nickname,
1343 SILC_CONN_CLIENT,
1344 client_entry->public_key,
1345 silcpurple_add_buddy_save, r);
1348 static void
1349 silcpurple_add_buddy_i(PurpleConnection *gc, PurpleBuddy *b, gboolean init)
1351 SilcPurple sg = purple_connection_get_protocol_data(gc);
1352 SilcClient client = sg->client;
1353 SilcClientConnection conn = sg->conn;
1354 SilcPurpleBuddyRes r;
1355 SilcBuffer attrs;
1356 const char *filename, *name = purple_buddy_get_name(b);
1358 r = silc_calloc(1, sizeof(*r));
1359 if (!r)
1360 return;
1361 r->client = client;
1362 r->conn = conn;
1363 r->b = b;
1364 r->init = init;
1366 /* See if we have this buddy's public key. If we do use that
1367 to search the details. */
1368 filename = purple_blist_node_get_string((PurpleBlistNode *)b, "public-key");
1369 if (filename) {
1370 SilcPublicKey public_key;
1371 SilcAttributeObjPk userpk;
1373 if (!silc_pkcs_load_public_key(filename, &public_key))
1374 return;
1376 /* Get all attributes, and use the public key to search user */
1377 name = NULL;
1378 attrs = silc_client_attributes_request(SILC_ATTRIBUTE_USER_INFO,
1379 SILC_ATTRIBUTE_SERVICE,
1380 SILC_ATTRIBUTE_STATUS_MOOD,
1381 SILC_ATTRIBUTE_STATUS_FREETEXT,
1382 SILC_ATTRIBUTE_STATUS_MESSAGE,
1383 SILC_ATTRIBUTE_PREFERRED_LANGUAGE,
1384 SILC_ATTRIBUTE_PREFERRED_CONTACT,
1385 SILC_ATTRIBUTE_TIMEZONE,
1386 SILC_ATTRIBUTE_GEOLOCATION,
1387 SILC_ATTRIBUTE_USER_ICON,
1388 SILC_ATTRIBUTE_DEVICE_INFO, 0);
1389 userpk.type = "silc-rsa";
1390 userpk.data = silc_pkcs_public_key_encode(public_key, &userpk.data_len);
1391 attrs = silc_attribute_payload_encode(attrs,
1392 SILC_ATTRIBUTE_USER_PUBLIC_KEY,
1393 SILC_ATTRIBUTE_FLAG_VALID,
1394 &userpk, sizeof(userpk));
1395 silc_free(userpk.data);
1396 silc_pkcs_public_key_free(public_key);
1397 r->pubkey_search = TRUE;
1398 } else {
1399 /* Get all attributes */
1400 attrs = silc_client_attributes_request(0);
1403 /* Resolve */
1404 silc_client_get_clients_whois(client, conn, name, NULL, attrs,
1405 silcpurple_add_buddy_resolved, r);
1406 silc_buffer_free(attrs);
1409 void silcpurple_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message)
1411 /* Don't add if the buddy is already on the list.
1413 * SILC doesn't have groups, so we also don't need to do anything
1414 * for a move. */
1415 if (purple_buddy_get_protocol_data(buddy) == NULL)
1416 silcpurple_add_buddy_i(gc, buddy, FALSE);
1419 void silcpurple_send_buddylist(PurpleConnection *gc)
1421 GSList *buddies;
1422 PurpleAccount *account;
1424 account = purple_connection_get_account(gc);
1426 for (buddies = purple_blist_find_buddies(account, NULL); buddies;
1427 buddies = g_slist_delete_link(buddies, buddies))
1429 PurpleBuddy *buddy = buddies->data;
1430 silcpurple_add_buddy_i(gc, buddy, TRUE);
1434 void silcpurple_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
1435 PurpleGroup *group)
1437 silc_free(purple_buddy_get_protocol_data(buddy));
1440 void silcpurple_idle_set(PurpleConnection *gc, int idle)
1443 SilcPurple sg;
1444 SilcClient client;
1445 SilcClientConnection conn;
1446 SilcAttributeObjService service;
1447 const char *server;
1448 int port;
1450 sg = purple_connection_get_protocol_data(gc);
1451 if (sg == NULL)
1452 return;
1454 client = sg->client;
1455 if (client == NULL)
1456 return;
1458 conn = sg->conn;
1459 if (conn == NULL)
1460 return;
1462 server = purple_account_get_string(sg->account, "server",
1463 "silc.silcnet.org");
1464 port = purple_account_get_int(sg->account, "port", 706),
1466 memset(&service, 0, sizeof(service));
1467 silc_client_attribute_del(client, conn,
1468 SILC_ATTRIBUTE_SERVICE, NULL);
1469 service.port = port;
1470 g_snprintf(service.address, sizeof(service.address), "%s", server);
1471 service.idle = idle;
1472 silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_SERVICE,
1473 &service, sizeof(service));
1476 char *silcpurple_status_text(PurpleBuddy *b)
1478 PurpleAccount *account = purple_buddy_get_account(b);
1479 PurpleConnection *gc = purple_account_get_connection(account);
1480 SilcPurple sg = purple_connection_get_protocol_data(gc);
1481 SilcClient client = sg->client;
1482 SilcClientConnection conn = sg->conn;
1483 SilcClientID *client_id = purple_buddy_get_protocol_data(b);
1484 SilcClientEntry client_entry;
1485 SilcAttributePayload attr;
1486 SilcAttributeMood mood = 0;
1488 /* Get the client entry. */
1489 client_entry = silc_client_get_client_by_id(client, conn, client_id);
1490 if (!client_entry)
1491 return NULL;
1493 /* If user is online, we show the mood status, if available.
1494 If user is offline or away that status is indicated. */
1496 if (client_entry->mode & SILC_UMODE_DETACHED)
1497 return g_strdup(_("Detached"));
1498 if (client_entry->mode & SILC_UMODE_GONE)
1499 return g_strdup(_("Away"));
1500 if (client_entry->mode & SILC_UMODE_INDISPOSED)
1501 return g_strdup(_("Indisposed"));
1502 if (client_entry->mode & SILC_UMODE_BUSY)
1503 return g_strdup(_("Busy"));
1504 if (client_entry->mode & SILC_UMODE_PAGE)
1505 return g_strdup(_("Wake Me Up"));
1506 if (client_entry->mode & SILC_UMODE_HYPER)
1507 return g_strdup(_("Hyper Active"));
1508 if (client_entry->mode & SILC_UMODE_ROBOT)
1509 return g_strdup(_("Robot"));
1511 attr = silcpurple_get_attr(client_entry->attrs, SILC_ATTRIBUTE_STATUS_MOOD);
1512 if (attr && silc_attribute_get_object(attr, &mood, sizeof(mood))) {
1513 /* The mood is a bit mask, so we could show multiple moods,
1514 but let's show only one for now. */
1515 if (mood & SILC_ATTRIBUTE_MOOD_HAPPY)
1516 return g_strdup(_("Happy"));
1517 if (mood & SILC_ATTRIBUTE_MOOD_SAD)
1518 return g_strdup(_("Sad"));
1519 if (mood & SILC_ATTRIBUTE_MOOD_ANGRY)
1520 return g_strdup(_("Angry"));
1521 if (mood & SILC_ATTRIBUTE_MOOD_JEALOUS)
1522 return g_strdup(_("Jealous"));
1523 if (mood & SILC_ATTRIBUTE_MOOD_ASHAMED)
1524 return g_strdup(_("Ashamed"));
1525 if (mood & SILC_ATTRIBUTE_MOOD_INVINCIBLE)
1526 return g_strdup(_("Invincible"));
1527 if (mood & SILC_ATTRIBUTE_MOOD_INLOVE)
1528 return g_strdup(_("In Love"));
1529 if (mood & SILC_ATTRIBUTE_MOOD_SLEEPY)
1530 return g_strdup(_("Sleepy"));
1531 if (mood & SILC_ATTRIBUTE_MOOD_BORED)
1532 return g_strdup(_("Bored"));
1533 if (mood & SILC_ATTRIBUTE_MOOD_EXCITED)
1534 return g_strdup(_("Excited"));
1535 if (mood & SILC_ATTRIBUTE_MOOD_ANXIOUS)
1536 return g_strdup(_("Anxious"));
1539 return NULL;
1542 void silcpurple_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
1544 PurpleAccount *account = purple_buddy_get_account(b);
1545 PurpleConnection *gc = purple_account_get_connection(account);
1546 SilcPurple sg = purple_connection_get_protocol_data(gc);
1547 SilcClient client = sg->client;
1548 SilcClientConnection conn = sg->conn;
1549 SilcClientID *client_id = purple_buddy_get_protocol_data(b);
1550 SilcClientEntry client_entry;
1551 char *moodstr, *statusstr, *contactstr, *langstr, *devicestr, *tzstr, *geostr;
1552 char tmp[256];
1554 /* Get the client entry. */
1555 client_entry = silc_client_get_client_by_id(client, conn, client_id);
1556 if (!client_entry)
1557 return;
1559 /* TODO: Check whether it's correct to call add_pair_html,
1560 or if we should be using add_pair_plaintext */
1561 purple_notify_user_info_add_pair_html(user_info, _("Nickname"), client_entry->nickname);
1562 g_snprintf(tmp, sizeof(tmp), "%s@%s", client_entry->username, client_entry->hostname);
1563 /* TODO: Check whether it's correct to call add_pair_html,
1564 or if we should be using add_pair_plaintext */
1565 purple_notify_user_info_add_pair_html(user_info, _("Username"), tmp);
1566 if (client_entry->mode) {
1567 memset(tmp, 0, sizeof(tmp));
1568 silcpurple_get_umode_string(client_entry->mode,
1569 tmp, sizeof(tmp) - strlen(tmp));
1570 purple_notify_user_info_add_pair_plaintext(user_info, _("User Modes"), tmp);
1573 silcpurple_parse_attrs(client_entry->attrs, &moodstr, &statusstr, &contactstr, &langstr, &devicestr, &tzstr, &geostr);
1575 if (statusstr) {
1576 /* TODO: Check whether it's correct to call add_pair_html,
1577 or if we should be using add_pair_plaintext */
1578 purple_notify_user_info_add_pair_html(user_info, _("Message"), statusstr);
1579 g_free(statusstr);
1582 if (full) {
1583 if (moodstr) {
1584 /* TODO: Check whether it's correct to call add_pair_html,
1585 or if we should be using add_pair_plaintext */
1586 purple_notify_user_info_add_pair_html(user_info, _("Mood"), moodstr);
1587 g_free(moodstr);
1590 if (contactstr) {
1591 /* TODO: Check whether it's correct to call add_pair_html,
1592 or if we should be using add_pair_plaintext */
1593 purple_notify_user_info_add_pair_html(user_info, _("Preferred Contact"), contactstr);
1594 g_free(contactstr);
1597 if (langstr) {
1598 /* TODO: Check whether it's correct to call add_pair_html,
1599 or if we should be using add_pair_plaintext */
1600 purple_notify_user_info_add_pair_html(user_info, _("Preferred Language"), langstr);
1601 g_free(langstr);
1604 if (devicestr) {
1605 /* TODO: Check whether it's correct to call add_pair_html,
1606 or if we should be using add_pair_plaintext */
1607 purple_notify_user_info_add_pair_html(user_info, _("Device"), devicestr);
1608 g_free(devicestr);
1611 if (tzstr) {
1612 /* TODO: Check whether it's correct to call add_pair_html,
1613 or if we should be using add_pair_plaintext */
1614 purple_notify_user_info_add_pair_html(user_info, _("Timezone"), tzstr);
1615 g_free(tzstr);
1618 if (geostr) {
1619 /* TODO: Check whether it's correct to call add_pair_html,
1620 or if we should be using add_pair_plaintext */
1621 purple_notify_user_info_add_pair_html(user_info, _("Geolocation"), geostr);
1622 g_free(geostr);
1627 static void
1628 silcpurple_buddy_kill(PurpleBlistNode *node, gpointer data)
1630 PurpleBuddy *b;
1631 PurpleConnection *gc;
1632 SilcPurple sg;
1634 g_return_if_fail(PURPLE_IS_BUDDY(node));
1636 b = (PurpleBuddy *) node;
1637 gc = purple_account_get_connection(purple_buddy_get_account(b));
1638 sg = purple_connection_get_protocol_data(gc);
1640 /* Call KILL */
1641 silc_client_command_call(sg->client, sg->conn, NULL, "KILL",
1642 purple_buddy_get_name(b), "Killed by operator", NULL);
1645 typedef struct {
1646 SilcPurple sg;
1647 SilcClientEntry client_entry;
1648 } *SilcPurpleBuddyWb;
1650 static void
1651 silcpurple_buddy_wb(PurpleBlistNode *node, gpointer data)
1653 SilcPurpleBuddyWb wb = data;
1654 silcpurple_wb_init(wb->sg, wb->client_entry);
1655 silc_free(wb);
1658 GList *silcpurple_buddy_menu(PurpleBuddy *buddy)
1660 PurpleAccount *account = purple_buddy_get_account(buddy);
1661 PurpleConnection *gc = purple_account_get_connection(account);
1662 SilcPurple sg = purple_connection_get_protocol_data(gc);
1663 SilcClientConnection conn = sg->conn;
1664 const char *pkfile = NULL;
1665 SilcClientEntry client_entry = NULL;
1666 PurpleMenuAction *act;
1667 GList *m = NULL;
1668 SilcPurpleBuddyWb wb;
1670 pkfile = purple_blist_node_get_string((PurpleBlistNode *) buddy, "public-key");
1671 client_entry = silc_client_get_client_by_id(sg->client,
1672 sg->conn,
1673 purple_buddy_get_protocol_data(buddy));
1675 if (client_entry &&
1676 silc_client_private_message_key_is_set(sg->client,
1677 sg->conn, client_entry)) {
1678 act = purple_menu_action_new(_("Reset IM Key"),
1679 PURPLE_CALLBACK(silcpurple_buddy_resetkey),
1680 NULL, NULL);
1681 m = g_list_append(m, act);
1682 } else {
1683 act = purple_menu_action_new(_("IM with Key Exchange"),
1684 PURPLE_CALLBACK(silcpurple_buddy_keyagr),
1685 NULL, NULL);
1686 m = g_list_append(m, act);
1688 act = purple_menu_action_new(_("IM with Password"),
1689 PURPLE_CALLBACK(silcpurple_buddy_privkey_menu),
1690 NULL, NULL);
1691 m = g_list_append(m, act);
1694 if (pkfile) {
1695 act = purple_menu_action_new(_("Show Public Key"),
1696 PURPLE_CALLBACK(silcpurple_buddy_showkey),
1697 NULL, NULL);
1698 m = g_list_append(m, act);
1700 } else {
1701 act = purple_menu_action_new(_("Get Public Key..."),
1702 PURPLE_CALLBACK(silcpurple_buddy_getkey_menu),
1703 NULL, NULL);
1704 m = g_list_append(m, act);
1707 if (conn && conn->local_entry->mode & SILC_UMODE_ROUTER_OPERATOR) {
1708 act = purple_menu_action_new(_("Kill User"),
1709 PURPLE_CALLBACK(silcpurple_buddy_kill),
1710 NULL, NULL);
1711 m = g_list_append(m, act);
1714 if (client_entry) {
1715 wb = silc_calloc(1, sizeof(*wb));
1716 wb->sg = sg;
1717 wb->client_entry = client_entry;
1718 act = purple_menu_action_new(_("Draw On Whiteboard"),
1719 PURPLE_CALLBACK(silcpurple_buddy_wb),
1720 (void *)wb, NULL);
1721 m = g_list_append(m, act);
1723 return m;
1726 void silcpurple_buddy_set_icon(PurpleConnection *gc, PurpleImage *img)
1728 SilcPurple sg = purple_connection_get_protocol_data(gc);
1729 SilcClient client = sg->client;
1730 SilcClientConnection conn = sg->conn;
1731 SilcMime mime;
1732 const gchar *type;
1734 /* Remove */
1735 if (!img) {
1736 silc_client_attribute_del(client, conn,
1737 SILC_ATTRIBUTE_USER_ICON, NULL);
1738 return;
1741 type = purple_image_get_mimetype(img);
1742 if (type == NULL)
1743 return;
1744 if (purple_strequal(purple_image_get_extension(img), "ico"))
1745 return;
1747 /* Add */
1748 mime = silc_mime_alloc();
1749 if (!mime)
1750 return;
1752 silc_mime_add_field(mime, "Content-Type", type);
1753 silc_mime_add_data(mime, purple_image_get_data(img),
1754 purple_image_get_data_size(img));
1756 silc_client_attribute_add(client, conn, SILC_ATTRIBUTE_USER_ICON,
1757 mime, sizeof(*mime));
1759 silc_mime_free(mime);