telepathy: only change to "connected" state once
[siplcs.git] / src / telepathy / telepathy-connection.c
blobe2adf50e3d814d321fd6c2b13c6d38fbd573840b
1 /**
2 * @file telepathy-connection.c
4 * pidgin-sipe
6 * Copyright (C) 2012 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
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.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <string.h>
29 #include <glib-object.h>
30 #include <telepathy-glib/base-connection.h>
31 #include <telepathy-glib/base-protocol.h>
32 #include <telepathy-glib/handle-repo-dynamic.h>
33 #include <telepathy-glib/simple-password-manager.h>
34 #include <telepathy-glib/telepathy-glib.h>
36 #include "sipe-backend.h"
37 #include "sipe-common.h"
38 #include "sipe-core.h"
40 #include "telepathy-private.h"
42 G_BEGIN_DECLS
44 * Connection class - data structures
46 typedef struct _SipeConnectionClass {
47 TpBaseConnectionClass parent_class;
48 } SipeConnectionClass;
50 typedef struct _SipeConnection {
51 TpBaseConnection parent;
53 TpSimplePasswordManager *password_manager;
55 struct sipe_backend_private private;
56 gchar *account;
57 gchar *login;
58 gchar *password;
59 gchar *server;
60 gchar *port;
61 guint transport;
62 gchar *user_agent;
63 gchar *authentication;
64 gboolean is_disconnecting;
65 } SipeConnection;
67 #define SIPE_PUBLIC_TO_CONNECTION sipe_public->backend_private->connection
70 * Connection class - type macros
72 static GType sipe_connection_get_type(void) G_GNUC_CONST;
73 #define SIPE_TYPE_CONNECTION \
74 (sipe_connection_get_type())
75 #define SIPE_CONNECTION(obj) \
76 (G_TYPE_CHECK_INSTANCE_CAST((obj), SIPE_TYPE_CONNECTION, \
77 SipeConnection))
78 G_END_DECLS
81 * Connection class - type definition
83 G_DEFINE_TYPE(SipeConnection,
84 sipe_connection,
85 TP_TYPE_BASE_CONNECTION)
88 * Connection class - instance methods
90 static gchar *normalize_contact(SIPE_UNUSED_PARAMETER TpHandleRepoIface *repo,
91 const gchar *id,
92 SIPE_UNUSED_PARAMETER gpointer context,
93 GError **error)
95 return(sipe_telepathy_protocol_normalize_contact(NULL, id, error));
98 static void create_handle_repos(SIPE_UNUSED_PARAMETER TpBaseConnection *conn,
99 TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
101 repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new(TP_HANDLE_TYPE_CONTACT,
102 normalize_contact,
103 NULL);
106 static gboolean connect_to_core(SipeConnection *self,
107 GError **error)
109 gchar *login_domain = NULL;
110 gchar *login_account = NULL;
111 struct sipe_core_public *sipe_public;
112 const gchar *errmsg;
114 /* login name specified? */
115 if (self->login && strlen(self->login)) {
116 /* Allowed domain-account separators are / or \ */
117 gchar **domain_user = g_strsplit_set(self->login, "/\\", 2);
118 gboolean has_domain = domain_user[1] != NULL;
119 SIPE_DEBUG_INFO("connect_to_core: login '%s'", self->login);
120 login_domain = has_domain ? g_strdup(domain_user[0]) : NULL;
121 login_account = g_strdup(domain_user[has_domain ? 1 : 0]);
122 SIPE_DEBUG_INFO("connect_to_core: auth domain '%s' user '%s'",
123 login_domain ? login_domain : "",
124 login_account);
125 g_strfreev(domain_user);
128 sipe_public = sipe_core_allocate(self->account,
129 login_domain, login_account,
130 self->password,
131 NULL, /* @TODO: email */
132 NULL, /* @TODO: email_url */
133 &errmsg);
134 g_free(login_domain);
135 g_free(login_account);
137 SIPE_DEBUG_INFO("connect_to_core: created %p", sipe_public);
139 if (sipe_public) {
140 struct sipe_backend_private *telepathy_private = &self->private;
142 /* initialize backend private data */
143 sipe_public->backend_private = telepathy_private;
144 telepathy_private->public = sipe_public;
145 telepathy_private->connection = self;
146 telepathy_private->transport = NULL;
147 telepathy_private->ipaddress = NULL;
149 /* map option list to flags - default is NTLM */
150 SIPE_CORE_FLAG_UNSET(KRB5);
151 SIPE_CORE_FLAG_UNSET(TLS_DSK);
152 #ifdef HAVE_LIBKRB5
153 if (sipe_strequal(self->authentication, "krb5")) {
154 SIPE_DEBUG_INFO_NOFORMAT("connect_to_core: KRB5 selected");
155 SIPE_CORE_FLAG_SET(KRB5);
156 } else
157 #endif
158 if (sipe_strequal(self->authentication, "tls-dsk")) {
159 SIPE_DEBUG_INFO_NOFORMAT("connect_to_core: TLS-DSK selected");
160 SIPE_CORE_FLAG_SET(TLS_DSK);
163 /* @TODO: add parameter for SSO */
164 SIPE_CORE_FLAG_UNSET(SSO);
166 sipe_core_transport_sip_connect(sipe_public,
167 self->transport,
168 self->server,
169 self->port);
171 return(TRUE);
172 } else {
173 g_set_error_literal(error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT,
174 errmsg);
175 return(FALSE);
179 static void password_manager_cb(GObject *source,
180 GAsyncResult *result,
181 gpointer data)
183 SipeConnection *self = data;
184 TpBaseConnection *base = TP_BASE_CONNECTION(self);
185 GError *error = NULL;
186 const GString *password = tp_simple_password_manager_prompt_finish(
187 TP_SIMPLE_PASSWORD_MANAGER(source),
188 result,
189 &error);
191 if (error) {
192 SIPE_DEBUG_ERROR("password_manager_cb: failed: %s",
193 error->message);
195 if (base->status != TP_CONNECTION_STATUS_DISCONNECTED) {
196 tp_base_connection_disconnect_with_dbus_error(base,
197 tp_error_get_dbus_name(error->code),
198 NULL,
199 TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED);
201 g_error_free(error);
202 } else {
204 g_free(self->password);
205 self->password = g_strdup(password->str);
207 if (!connect_to_core(self, &error)) {
208 if (base->status != TP_CONNECTION_STATUS_DISCONNECTED) {
209 tp_base_connection_disconnect_with_dbus_error(base,
210 tp_error_get_dbus_name(error->code),
211 NULL,
212 TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED);
214 g_error_free(error);
219 static gboolean start_connecting(TpBaseConnection *base,
220 GError **error)
222 SipeConnection *self = SIPE_CONNECTION(base);
223 gboolean rc = TRUE;
224 gchar *uri = sipe_telepathy_protocol_normalize_contact(NULL,
225 self->account,
226 error);
228 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::start_connecting");
230 /* set up mandatory self-handle */
231 if (uri) {
232 base->self_handle = tp_handle_ensure(tp_base_connection_get_handles(base,
233 TP_HANDLE_TYPE_CONTACT),
234 uri,
235 NULL,
236 error);
237 g_free(uri);
238 if (!base->self_handle) {
239 SIPE_DEBUG_ERROR("SipeConnection::start_connecting: self handle creation failed: %s",
240 (*error)->message);
241 return(FALSE);
243 } else {
244 SIPE_DEBUG_ERROR("SipeConnection::start_connecting: %s",
245 (*error)->message);
246 return(FALSE);
249 tp_base_connection_change_status(base, TP_CONNECTION_STATUS_CONNECTING,
250 TP_CONNECTION_STATUS_REASON_REQUESTED);
252 /* we need a password */
253 if (self->password)
254 rc = connect_to_core(self, error);
255 else {
256 #if 0
257 /* this doesn't work. Nobody has a clue why */
258 SIPE_DEBUG_INFO("SipeConnection::start_connecting: requesting password from user: %p",
259 self->password_manager);
260 tp_simple_password_manager_prompt_async(self->password_manager,
261 password_manager_cb,
262 self);
263 #else
264 /* MAJOR HACK */
265 gchar *password_file = g_strdup_printf("%s/.sipe-password",
266 g_getenv("HOME"));
267 SIPE_DEBUG_INFO("SipeConnection::start_connecting - password file %s",
268 password_file);
269 rc = FALSE;
270 if (g_file_get_contents(password_file,
271 &self->password,
272 NULL, error)) {
273 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::start_connecting - got password");
274 g_strchomp(self->password);
275 rc = connect_to_core(self, error);
276 } else {
277 SIPE_DEBUG_ERROR("SipeConnection::start_connecting: %s",
278 (*error)->message);
280 g_free(password_file);
282 /* to make compiler happy */
283 (void)password_manager_cb;
284 #endif
287 return(rc);
290 static gboolean disconnect_from_core(gpointer data)
292 TpBaseConnection *base = data;
293 SipeConnection *self = SIPE_CONNECTION(base);
294 struct sipe_backend_private *telepathy_private = &self->private;
295 struct sipe_core_public *sipe_public = telepathy_private->public;
297 SIPE_DEBUG_INFO("disconnect_from_core: %p", sipe_public);
299 if (sipe_public)
300 sipe_core_deallocate(sipe_public);
301 telepathy_private->public = NULL;
302 telepathy_private->transport = NULL;
304 g_free(telepathy_private->ipaddress);
305 telepathy_private->ipaddress = NULL;
307 SIPE_DEBUG_INFO_NOFORMAT("disconnect_from_core: core deallocated");
309 /* now it is OK to destroy the connection object */
310 tp_base_connection_finish_shutdown(base);
312 return(FALSE);
315 static void shut_down(TpBaseConnection *base)
317 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::shut_down");
319 /* this can be called synchronously, defer destruction */
320 g_idle_add(disconnect_from_core, base);
323 static GPtrArray *create_channel_managers(TpBaseConnection *base)
325 SipeConnection *self = SIPE_CONNECTION(base);
326 GPtrArray *channel_managers = g_ptr_array_new();
328 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::create_channel_managers");
330 self->password_manager = tp_simple_password_manager_new(base);
331 g_ptr_array_add(channel_managers, self->password_manager);
332 SIPE_DEBUG_INFO("SipeConnection::create_channel_managers: password %p",
333 self->password_manager);
335 return(channel_managers);
338 static void sipe_connection_finalize(GObject *object)
340 SipeConnection *self = SIPE_CONNECTION(object);
342 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::finalize");
344 g_free(self->authentication);
345 g_free(self->user_agent);
346 g_free(self->port);
347 g_free(self->server);
348 g_free(self->password);
349 g_free(self->login);
350 g_free(self->account);
352 G_OBJECT_CLASS(sipe_connection_parent_class)->finalize(object);
356 * Connection class - type implementation
358 static void sipe_connection_class_init(SipeConnectionClass *klass)
360 GObjectClass *object_class = G_OBJECT_CLASS(klass);
361 TpBaseConnectionClass *base_class = TP_BASE_CONNECTION_CLASS(klass);
363 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::class_init");
365 object_class->finalize = sipe_connection_finalize;
367 base_class->create_handle_repos = create_handle_repos;
368 base_class->start_connecting = start_connecting;
369 base_class->shut_down = shut_down;
370 base_class->create_channel_managers = create_channel_managers;
373 static void sipe_connection_init(SIPE_UNUSED_PARAMETER SipeConnection *self)
375 SIPE_DEBUG_INFO_NOFORMAT("SipeConnection::init");
378 /* create new connection object */
379 TpBaseConnection *sipe_telepathy_connection_new(TpBaseProtocol *protocol,
380 GHashTable *params,
381 SIPE_UNUSED_PARAMETER GError **error)
383 SipeConnection *conn = g_object_new(SIPE_TYPE_CONNECTION,
384 "protocol", tp_base_protocol_get_name(protocol),
385 NULL);
386 const gchar *value;
387 guint port;
388 gboolean valid;
390 SIPE_DEBUG_INFO_NOFORMAT("sipe_telepathy_connection_new");
392 /* initialize private fields */
393 conn->is_disconnecting = FALSE;
395 /* account & login are required fields */
396 conn->account = g_strdup(tp_asv_get_string(params, "account"));
397 conn->login = g_strdup(tp_asv_get_string(params, "login"));
399 /* password */
400 value = tp_asv_get_string(params, "password");
401 if (value && strlen(value))
402 conn->password = g_strdup(value);
403 else
404 conn->password = NULL;
406 /* server name */
407 value = tp_asv_get_string(params, "server");
408 if (value && strlen(value))
409 conn->server = g_strdup(value);
410 else
411 conn->server = NULL;
413 /* server port: core expects a string */
414 port = tp_asv_get_uint32(params, "port", &valid);
415 if (valid)
416 conn->port = g_strdup_printf("%d", port);
417 else
418 conn->port = NULL;
420 /* transport type */
421 value = tp_asv_get_string(params, "transport");
422 if (sipe_strequal(value, "auto")) {
423 conn->transport = conn->server ?
424 SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_AUTO;
425 } else if (sipe_strequal(value, "tls")) {
426 conn->transport = SIPE_TRANSPORT_TLS;
427 } else {
428 conn->transport = SIPE_TRANSPORT_TCP;
431 /* User-Agent: override */
432 value = tp_asv_get_string(params, "useragent");
433 if (value && strlen(value))
434 conn->user_agent = g_strdup(value);
435 else
436 conn->user_agent = NULL;
438 /* authentication type */
439 value = tp_asv_get_string(params, "authentication");
440 if (value && strlen(value) && strcmp(value, "ntlm"))
441 conn->authentication = g_strdup(value);
442 else
443 conn->authentication = NULL; /* NTLM is default */
445 return(TP_BASE_CONNECTION(conn));
450 * Backend adaptor functions
452 void sipe_backend_connection_completed(struct sipe_core_public *sipe_public)
454 SipeConnection *self = SIPE_PUBLIC_TO_CONNECTION;
455 TpBaseConnection *base = TP_BASE_CONNECTION(self);
457 /* we are only allowed to do this once */
458 if (base->status != TP_CONNECTION_STATUS_CONNECTED)
459 tp_base_connection_change_status(base,
460 TP_CONNECTION_STATUS_CONNECTED,
461 TP_CONNECTION_STATUS_REASON_REQUESTED);
464 void sipe_backend_connection_error(struct sipe_core_public *sipe_public,
465 sipe_connection_error error,
466 const gchar *msg)
468 SipeConnection *self = SIPE_PUBLIC_TO_CONNECTION;
469 TpBaseConnection *base = TP_BASE_CONNECTION(self);
470 GHashTable *details = tp_asv_new("debug-message", G_TYPE_STRING, msg,
471 NULL);
472 TpConnectionStatusReason reason;
473 const gchar *name;
475 self->is_disconnecting = TRUE;
477 switch (error) {
478 case SIPE_CONNECTION_ERROR_NETWORK:
479 reason = TP_CONNECTION_STATUS_REASON_NETWORK_ERROR;
480 if (base->status == TP_CONNECTION_STATUS_CONNECTING)
481 name = TP_ERROR_STR_CONNECTION_FAILED;
482 else
483 name = TP_ERROR_STR_CONNECTION_LOST;
484 break;
486 case SIPE_CONNECTION_ERROR_INVALID_USERNAME:
487 case SIPE_CONNECTION_ERROR_INVALID_SETTINGS:
488 case SIPE_CONNECTION_ERROR_AUTHENTICATION_FAILED:
489 case SIPE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE:
490 /* copied from haze code. I agree there should be better ones */
491 reason = TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED;
492 name = TP_ERROR_STR_AUTHENTICATION_FAILED;
493 break;
495 default:
496 reason = TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED;
497 name = TP_ERROR_STR_DISCONNECTED;
498 break;
501 SIPE_DEBUG_ERROR("sipe_backend_connection_error: %s (%s)", name, msg);
502 tp_base_connection_disconnect_with_dbus_error(base,
503 name,
504 details,
505 reason);
506 g_hash_table_unref(details);
509 gboolean sipe_backend_connection_is_disconnecting(struct sipe_core_public *sipe_public)
511 SipeConnection *self = SIPE_PUBLIC_TO_CONNECTION;
513 /* disconnect was requested or transport was already disconnected */
514 return(self->is_disconnecting ||
515 self->private.transport == NULL);
518 gboolean sipe_backend_connection_is_valid(struct sipe_core_public *sipe_public)
520 return(!sipe_backend_connection_is_disconnecting(sipe_public));
523 const gchar *sipe_backend_setting(struct sipe_core_public *sipe_public,
524 sipe_setting type)
526 SipeConnection *self = SIPE_PUBLIC_TO_CONNECTION;
527 const gchar *value;
529 switch (type) {
530 case SIPE_SETTING_USER_AGENT:
531 value = self->user_agent;
532 break;
533 default:
534 /* @TODO: update when settings are implemented */
535 value = NULL;
536 break;
539 return(value);
544 Local Variables:
545 mode: c
546 c-file-style: "bsd"
547 indent-tabs-mode: t
548 tab-width: 8
549 End: