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