Coverage Report

Created: 2025-12-31 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/auth.c
Line
Count
Source
1
/*
2
 * purple - Jabber Protocol Plugin
3
 *
4
 * Purple is the legal property of its developers, whose names are too numerous
5
 * to list here.  Please refer to the COPYRIGHT file distributed with this
6
 * source distribution.
7
 *
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.
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
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21
 *
22
 */
23
#include "internal.h"
24
25
#include "account.h"
26
#include "debug.h"
27
#include "cipher.h"
28
#include "core.h"
29
#include "conversation.h"
30
#include "request.h"
31
#include "sslconn.h"
32
#include "util.h"
33
#include "xmlnode.h"
34
35
#include "auth.h"
36
#include "disco.h"
37
#include "jabber.h"
38
#include "jutil.h"
39
#include "iq.h"
40
#include "notify.h"
41
42
static GSList *auth_mechs = NULL;
43
44
static void auth_old_result_cb(JabberStream *js, const char *from,
45
                               JabberIqType type, const char *id,
46
                               xmlnode *packet, gpointer data);
47
48
static void finish_plaintext_authentication(JabberStream *js)
49
0
{
50
0
  JabberIq *iq;
51
0
  xmlnode *query, *x;
52
53
0
  iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
54
0
  query = xmlnode_get_child(iq->node, "query");
55
0
  x = xmlnode_new_child(query, "username");
56
0
  xmlnode_insert_data(x, js->user->node, -1);
57
0
  x = xmlnode_new_child(query, "resource");
58
0
  xmlnode_insert_data(x, js->user->resource, -1);
59
0
  x = xmlnode_new_child(query, "password");
60
0
  xmlnode_insert_data(x, purple_connection_get_password(js->gc), -1);
61
0
  jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
62
0
  jabber_iq_send(iq);
63
0
}
64
65
static void allow_plaintext_auth(PurpleAccount *account)
66
0
{
67
0
  PurpleConnection *gc;
68
0
  JabberStream *js;
69
70
0
  purple_account_set_bool(account, "auth_plain_in_clear", TRUE);
71
72
0
  gc = purple_account_get_connection(account);
73
0
  js = purple_connection_get_protocol_data(gc);
74
75
0
  finish_plaintext_authentication(js);
76
0
}
77
78
static void disallow_plaintext_auth(PurpleAccount *account)
79
0
{
80
0
  purple_connection_error_reason(purple_account_get_connection(account),
81
0
    PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
82
0
    _("Server requires plaintext authentication over an unencrypted stream"));
83
0
}
84
85
#ifdef HAVE_CYRUS_SASL
86
static void
87
auth_old_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
88
{
89
  PurpleAccount *account;
90
  JabberStream *js;
91
  const char *entry;
92
  gboolean remember;
93
94
  /* The password prompt dialog doesn't get disposed if the account disconnects */
95
  if (!PURPLE_CONNECTION_IS_VALID(gc))
96
    return;
97
98
  account = purple_connection_get_account(gc);
99
  js = purple_connection_get_protocol_data(gc);
100
101
  entry = purple_request_fields_get_string(fields, "password");
102
  remember = purple_request_fields_get_bool(fields, "remember");
103
104
  if (!entry || !*entry)
105
  {
106
    purple_notify_error(account, NULL, _("Password is required to sign on."), NULL);
107
    return;
108
  }
109
110
  if (remember)
111
    purple_account_set_remember_password(account, TRUE);
112
113
  purple_account_set_password(account, entry);
114
115
  /* Restart our connection */
116
  jabber_auth_start_old(js);
117
}
118
119
static void
120
auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields)
121
{
122
  /* The password prompt dialog doesn't get disposed if the account disconnects */
123
  if (!PURPLE_CONNECTION_IS_VALID(gc))
124
    return;
125
126
  /* Disable the account as the user has cancelled connecting */
127
  purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE);
128
}
129
#endif
130
131
void
132
jabber_auth_start(JabberStream *js, xmlnode *packet)
133
0
{
134
0
  GSList *mechanisms = NULL;
135
0
  GSList *l;
136
0
  xmlnode *response = NULL;
137
0
  xmlnode *mechs, *mechnode;
138
0
  JabberSaslState state;
139
0
  char *msg = NULL;
140
141
0
  if(js->registration) {
142
0
    jabber_register_start(js);
143
0
    return;
144
0
  }
145
146
0
  mechs = xmlnode_get_child(packet, "mechanisms");
147
0
  if(!mechs) {
148
0
    purple_connection_error_reason(js->gc,
149
0
      PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
150
0
      _("Invalid response from server"));
151
0
    return;
152
0
  }
153
154
0
  for(mechnode = xmlnode_get_child(mechs, "mechanism"); mechnode;
155
0
      mechnode = xmlnode_get_next_twin(mechnode))
156
0
  {
157
0
    char *mech_name = xmlnode_get_data(mechnode);
158
159
0
    if (mech_name && *mech_name)
160
0
      mechanisms = g_slist_prepend(mechanisms, mech_name);
161
0
    else
162
0
      g_free(mech_name);
163
164
0
  }
165
166
0
  for (l = auth_mechs; l; l = l->next) {
167
0
    JabberSaslMech *possible = l->data;
168
169
    /* Is this the Cyrus SASL mechanism? */
170
0
    if (purple_strequal(possible->name, "*")) {
171
0
      js->auth_mech = possible;
172
0
      break;
173
0
    }
174
175
    /* Can we find this mechanism in the server's list? */
176
0
    if (g_slist_find_custom(mechanisms, possible->name, (GCompareFunc)strcmp)) {
177
0
      js->auth_mech = possible;
178
0
      break;
179
0
    }
180
0
  }
181
182
0
  while (mechanisms) {
183
0
    g_free(mechanisms->data);
184
0
    mechanisms = g_slist_delete_link(mechanisms, mechanisms);
185
0
  }
186
187
0
  if (js->auth_mech == NULL) {
188
    /* Found no good mechanisms... */
189
0
    purple_connection_error_reason(js->gc,
190
0
        PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
191
0
        _("Server does not use any supported authentication method"));
192
0
    return;
193
0
  }
194
195
0
  state = js->auth_mech->start(js, mechs, &response, &msg);
196
0
  if (state == JABBER_SASL_STATE_FAIL) {
197
0
    purple_connection_error_reason(js->gc,
198
0
        PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
199
0
        msg ? msg : _("Unknown Error"));
200
0
  } else if (response) {
201
0
    jabber_send(js, response);
202
0
    xmlnode_free(response);
203
0
  }
204
205
0
  g_free(msg);
206
0
}
207
208
static void auth_old_result_cb(JabberStream *js, const char *from,
209
                               JabberIqType type, const char *id,
210
                               xmlnode *packet, gpointer data)
211
0
{
212
0
  if (type == JABBER_IQ_RESULT) {
213
0
    jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
214
0
    jabber_disco_items_server(js);
215
0
  } else {
216
0
    PurpleAccount *account;
217
0
    PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
218
0
    char *msg = jabber_parse_error(js, packet, &reason);
219
0
    xmlnode *error;
220
0
    const char *err_code;
221
222
0
    account = purple_connection_get_account(js->gc);
223
224
    /* FIXME: Why is this not in jabber_parse_error? */
225
0
    if((error = xmlnode_get_child(packet, "error")) &&
226
0
          (err_code = xmlnode_get_attrib(error, "code")) &&
227
0
          purple_strequal(err_code, "401")) {
228
0
      reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
229
      /* Clear the pasword if it isn't being saved */
230
0
      if (!purple_account_get_remember_password(account))
231
0
        purple_account_set_password(account, NULL);
232
0
    }
233
234
0
    purple_connection_error_reason(js->gc, reason, msg);
235
0
    g_free(msg);
236
0
  }
237
0
}
238
239
static void auth_old_cb(JabberStream *js, const char *from,
240
                        JabberIqType type, const char *id,
241
                        xmlnode *packet, gpointer data)
242
0
{
243
0
  JabberIq *iq;
244
0
  xmlnode *query, *x;
245
0
  const char *pw = purple_connection_get_password(js->gc);
246
247
0
  if (type == JABBER_IQ_ERROR) {
248
0
    PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
249
0
    char *msg = jabber_parse_error(js, packet, &reason);
250
0
    purple_connection_error_reason(js->gc, reason, msg);
251
0
    g_free(msg);
252
0
  } else if (type == JABBER_IQ_RESULT) {
253
0
    query = xmlnode_get_child(packet, "query");
254
0
    if (js->stream_id && *js->stream_id &&
255
0
        xmlnode_get_child(query, "digest")) {
256
0
      char *s, *hash;
257
258
0
      iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
259
0
      query = xmlnode_get_child(iq->node, "query");
260
0
      x = xmlnode_new_child(query, "username");
261
0
      xmlnode_insert_data(x, js->user->node, -1);
262
0
      x = xmlnode_new_child(query, "resource");
263
0
      xmlnode_insert_data(x, js->user->resource, -1);
264
265
0
      x = xmlnode_new_child(query, "digest");
266
0
      s = g_strdup_printf("%s%s", js->stream_id, pw);
267
0
      hash = jabber_calculate_data_hash(s, strlen(s), "sha1");
268
0
      xmlnode_insert_data(x, hash, -1);
269
0
      g_free(hash);
270
0
      g_free(s);
271
0
      jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
272
0
      jabber_iq_send(iq);
273
0
    } else if ((x = xmlnode_get_child(query, "crammd5"))) {
274
      /* For future reference, this appears to be a custom OS X extension
275
       * to non-SASL authentication.
276
       */
277
0
      const char *challenge;
278
0
      gchar digest[33];
279
0
      PurpleCipherContext *hmac;
280
281
      /* Calculate the MHAC-MD5 digest */
282
0
      challenge = xmlnode_get_attrib(x, "challenge");
283
0
      hmac = purple_cipher_context_new_by_name("hmac", NULL);
284
0
      purple_cipher_context_set_option(hmac, "hash", "md5");
285
0
      purple_cipher_context_set_key(hmac, (guchar *)pw);
286
0
      purple_cipher_context_append(hmac, (guchar *)challenge, strlen(challenge));
287
0
      purple_cipher_context_digest_to_str(hmac, 33, digest, NULL);
288
0
      purple_cipher_context_destroy(hmac);
289
290
      /* Create the response query */
291
0
      iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth");
292
0
      query = xmlnode_get_child(iq->node, "query");
293
294
0
      x = xmlnode_new_child(query, "username");
295
0
      xmlnode_insert_data(x, js->user->node, -1);
296
0
      x = xmlnode_new_child(query, "resource");
297
0
      xmlnode_insert_data(x, js->user->resource, -1);
298
299
0
      x = xmlnode_new_child(query, "crammd5");
300
301
0
      xmlnode_insert_data(x, digest, 32);
302
303
0
      jabber_iq_set_callback(iq, auth_old_result_cb, NULL);
304
0
      jabber_iq_send(iq);
305
306
0
    } else if(xmlnode_get_child(query, "password")) {
307
0
      PurpleAccount *account = purple_connection_get_account(js->gc);
308
0
      if(!jabber_stream_is_ssl(js) && !purple_account_get_bool(account,
309
0
            "auth_plain_in_clear", FALSE)) {
310
0
        char *msg = g_strdup_printf(_("%s requires plaintext authentication over an unencrypted connection.  Allow this and continue authentication?"),
311
0
                      purple_account_get_username(account));
312
0
        purple_request_yes_no(js->gc, _("Plaintext Authentication"),
313
0
            _("Plaintext Authentication"),
314
0
            msg,
315
0
            1,
316
0
            account, NULL, NULL,
317
0
            account, allow_plaintext_auth,
318
0
            disallow_plaintext_auth);
319
0
        g_free(msg);
320
0
        return;
321
0
      }
322
0
      finish_plaintext_authentication(js);
323
0
    } else {
324
0
      purple_connection_error_reason(js->gc,
325
0
        PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
326
0
        _("Server does not use any supported authentication method"));
327
0
      return;
328
0
    }
329
0
  }
330
0
}
331
332
void jabber_auth_start_old(JabberStream *js)
333
0
{
334
0
  PurpleAccount *account;
335
0
  JabberIq *iq;
336
0
  xmlnode *query, *username;
337
338
0
  account = purple_connection_get_account(js->gc);
339
340
  /*
341
   * We can end up here without encryption if the server doesn't support
342
   * <stream:features/> and we're not using old-style SSL.  If the user
343
   * is requiring SSL/TLS, we need to enforce it.
344
   */
345
0
  if (!jabber_stream_is_ssl(js) &&
346
0
      purple_strequal("require_tls",
347
0
        purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) {
348
0
    purple_connection_error_reason(js->gc,
349
0
      PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR,
350
0
      _("You require encryption, but it is not available on this server."));
351
0
    return;
352
0
  }
353
354
0
  if (js->registration) {
355
0
    jabber_register_start(js);
356
0
    return;
357
0
  }
358
359
  /*
360
   * IQ Auth doesn't have support for resource binding, so we need to pick a
361
   * default resource so it will work properly.  jabberd14 throws an error and
362
   * iChat server just fails silently.
363
   */
364
0
  if (!js->user->resource || *js->user->resource == '\0') {
365
0
    g_free(js->user->resource);
366
0
    js->user->resource = g_strdup("Home");
367
0
  }
368
369
#ifdef HAVE_CYRUS_SASL
370
  /* If we have Cyrus SASL, then passwords will have been set
371
   * to OPTIONAL for this protocol. So, we need to do our own
372
   * password prompting here
373
   */
374
375
  if (!purple_account_get_password(account)) {
376
    purple_account_request_password(account, G_CALLBACK(auth_old_pass_cb), G_CALLBACK(auth_no_pass_cb), js->gc);
377
    return;
378
  }
379
#endif
380
0
  iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:auth");
381
382
0
  query = xmlnode_get_child(iq->node, "query");
383
0
  username = xmlnode_new_child(query, "username");
384
0
  xmlnode_insert_data(username, js->user->node, -1);
385
386
0
  jabber_iq_set_callback(iq, auth_old_cb, NULL);
387
388
0
  jabber_iq_send(iq);
389
0
}
390
391
void
392
jabber_auth_handle_challenge(JabberStream *js, xmlnode *packet)
393
0
{
394
0
  const char *ns = xmlnode_get_namespace(packet);
395
396
0
  if (!purple_strequal(ns, NS_XMPP_SASL)) {
397
0
    purple_connection_error_reason(js->gc,
398
0
      PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
399
0
      _("Invalid response from server"));
400
0
    return;
401
0
  }
402
403
0
  if (js->auth_mech && js->auth_mech->handle_challenge) {
404
0
    xmlnode *response = NULL;
405
0
    char *msg = NULL;
406
0
    JabberSaslState state = js->auth_mech->handle_challenge(js, packet, &response, &msg);
407
0
    if (state == JABBER_SASL_STATE_FAIL) {
408
0
      purple_connection_error_reason(js->gc,
409
0
          PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
410
0
          msg ? msg : _("Invalid challenge from server"));
411
0
    } else if (response) {
412
0
      jabber_send(js, response);
413
0
      xmlnode_free(response);
414
0
    }
415
416
0
    g_free(msg);
417
0
  } else
418
0
    purple_debug_warning("jabber", "Received unexpected (and unhandled) <challenge/>\n");
419
0
}
420
421
void jabber_auth_handle_success(JabberStream *js, xmlnode *packet)
422
0
{
423
0
  const char *ns = xmlnode_get_namespace(packet);
424
425
0
  if (!purple_strequal(ns, NS_XMPP_SASL)) {
426
0
    purple_connection_error_reason(js->gc,
427
0
      PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
428
0
      _("Invalid response from server"));
429
0
    return;
430
0
  }
431
432
0
  if (js->auth_mech && js->auth_mech->handle_success) {
433
0
    char *msg = NULL;
434
0
    JabberSaslState state = js->auth_mech->handle_success(js, packet, &msg);
435
436
0
    if (state == JABBER_SASL_STATE_FAIL) {
437
0
      purple_connection_error_reason(js->gc,
438
0
          PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
439
0
          msg ? msg : _("Invalid response from server"));
440
0
      return;
441
0
    } else if (state == JABBER_SASL_STATE_CONTINUE) {
442
0
      purple_connection_error_reason(js->gc,
443
0
          PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE,
444
0
          msg ? msg : _("Server thinks authentication is complete, but client does not"));
445
0
      return;
446
0
    }
447
448
0
    g_free(msg);
449
0
  }
450
451
  /*
452
   * The stream will be reinitialized later in jabber_recv_cb_ssl() or
453
   * jabber_bosh_connection_send.
454
   */
455
0
  js->reinit = TRUE;
456
0
  jabber_stream_set_state(js, JABBER_STREAM_POST_AUTH);
457
0
}
458
459
void jabber_auth_handle_failure(JabberStream *js, xmlnode *packet)
460
0
{
461
0
  PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
462
0
  char *msg = NULL;
463
464
0
  if (js->auth_mech && js->auth_mech->handle_failure) {
465
0
    xmlnode *stanza = NULL;
466
0
    JabberSaslState state = js->auth_mech->handle_failure(js, packet, &stanza, &msg);
467
468
0
    if (state != JABBER_SASL_STATE_FAIL) {
469
0
      if (stanza) {
470
0
        jabber_send(js, stanza);
471
0
        xmlnode_free(stanza);
472
0
      }
473
474
0
      return;
475
0
    }
476
0
  }
477
478
0
  if (!msg)
479
0
    msg = jabber_parse_error(js, packet, &reason);
480
481
0
  if (!msg) {
482
0
    purple_connection_error_reason(js->gc,
483
0
      PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
484
0
      _("Invalid response from server"));
485
0
  } else {
486
0
    purple_connection_error_reason(js->gc, reason, msg);
487
0
    g_free(msg);
488
0
  }
489
0
}
490
491
static gint compare_mech(gconstpointer a, gconstpointer b)
492
0
{
493
0
  const JabberSaslMech *mech_a = a;
494
0
  const JabberSaslMech *mech_b = b;
495
496
  /* higher priority comes *before* lower priority in the list */
497
0
  if (mech_a->priority > mech_b->priority)
498
0
    return -1;
499
0
  else if (mech_a->priority < mech_b->priority)
500
0
    return 1;
501
  /* This really shouldn't happen */
502
0
  return 0;
503
0
}
504
505
void jabber_auth_add_mech(JabberSaslMech *mech)
506
0
{
507
0
  auth_mechs = g_slist_insert_sorted(auth_mechs, mech, compare_mech);
508
0
}
509
510
void jabber_auth_remove_mech(JabberSaslMech *mech)
511
0
{
512
0
  auth_mechs = g_slist_remove(auth_mechs, mech);
513
0
}
514
515
void jabber_auth_init(void)
516
0
{
517
0
  JabberSaslMech **tmp;
518
0
  gint count, i;
519
520
0
  jabber_auth_add_mech(jabber_auth_get_plain_mech());
521
0
  jabber_auth_add_mech(jabber_auth_get_digest_md5_mech());
522
#ifdef HAVE_CYRUS_SASL
523
  jabber_auth_add_mech(jabber_auth_get_cyrus_mech());
524
#endif
525
526
0
  tmp = jabber_auth_get_scram_mechs(&count);
527
0
  for (i = 0; i < count; ++i)
528
0
    jabber_auth_add_mech(tmp[i]);
529
0
}
530
531
void jabber_auth_uninit(void)
532
0
{
533
0
  g_slist_free(auth_mechs);
534
  auth_mechs = NULL;
535
0
}