Coverage Report

Created: 2026-04-01 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pidgin/libpurple/protocols/jabber/auth_digest_md5.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 "debug.h"
26
#include "cipher.h"
27
#include "util.h"
28
#include "xmlnode.h"
29
30
#include "auth_digest_md5.h"
31
#include "auth.h"
32
#include "jabber.h"
33
34
static JabberSaslState
35
digest_md5_start(JabberStream *js, xmlnode *packet, xmlnode **response,
36
                 char **error)
37
0
{
38
0
  xmlnode *auth = xmlnode_new("auth");
39
0
  xmlnode_set_namespace(auth, NS_XMPP_SASL);
40
0
  xmlnode_set_attrib(auth, "mechanism", "DIGEST-MD5");
41
42
0
  *response = auth;
43
0
  return JABBER_SASL_STATE_CONTINUE;
44
0
}
45
46
/* Parts of this algorithm are inspired by stuff in libgsasl */
47
GHashTable* jabber_auth_digest_md5_parse(const char *challenge)
48
0
{
49
0
  const char *token_start, *val_start, *val_end, *cur;
50
0
  GHashTable *ret = g_hash_table_new_full(g_str_hash, g_str_equal,
51
0
      g_free, g_free);
52
53
0
  cur = challenge;
54
0
  while(*cur != '\0') {
55
    /* Find the end of the token */
56
0
    gboolean in_quotes = FALSE;
57
0
    char *name, *value = NULL;
58
0
    token_start = cur;
59
0
    while(*cur != '\0' && (in_quotes || (!in_quotes && *cur != ','))) {
60
0
      if (*cur == '"')
61
0
        in_quotes = !in_quotes;
62
0
      cur++;
63
0
    }
64
65
    /* Find start of value.  */
66
0
    val_start = strchr(token_start, '=');
67
0
    if (val_start == NULL || val_start > cur)
68
0
      val_start = cur;
69
70
0
    if (token_start != val_start) {
71
0
      name = g_strndup(token_start, val_start - token_start);
72
73
0
      if (val_start != cur) {
74
0
        val_start++;
75
0
        while (val_start != cur && (*val_start == ' ' || *val_start == '\t'
76
0
            || *val_start == '\r' || *val_start == '\n'
77
0
            || *val_start == '"'))
78
0
          val_start++;
79
80
0
        val_end = cur;
81
0
        while (val_end >= val_start && (*val_end == ' ' || *val_end == ',' || *val_end == '\t'
82
0
            || *val_end == '\r' || *val_end == '\n'
83
0
            || *val_end == '"'  || *val_end == '\0'))
84
0
          val_end--;
85
86
0
        if (val_end - val_start + 1 >= 0)
87
0
          value = g_strndup(val_start, val_end - val_start + 1);
88
0
      }
89
90
0
      g_hash_table_replace(ret, name, value);
91
0
    }
92
93
    /* Find the start of the next token, if there is one */
94
0
    if (*cur != '\0') {
95
0
      cur++;
96
0
      while (*cur == ' ' || *cur == ',' || *cur == '\t'
97
0
          || *cur == '\r' || *cur == '\n')
98
0
        cur++;
99
0
    }
100
0
  }
101
102
0
  return ret;
103
0
}
104
105
static char *
106
generate_response_value(JabberID *jid, const char *passwd, const char *nonce,
107
    const char *cnonce, const char *a2, const char *realm)
108
0
{
109
0
  PurpleCipher *cipher;
110
0
  PurpleCipherContext *context;
111
0
  guchar result[16];
112
0
  size_t a1len;
113
114
0
  gchar *a1, *convnode=NULL, *convpasswd = NULL, *ha1, *ha2, *kd, *x, *z;
115
116
0
  if((convnode = g_convert(jid->node, -1, "iso-8859-1", "utf-8",
117
0
          NULL, NULL, NULL)) == NULL) {
118
0
    convnode = g_strdup(jid->node);
119
0
  }
120
0
  if(passwd && ((convpasswd = g_convert(passwd, -1, "iso-8859-1",
121
0
            "utf-8", NULL, NULL, NULL)) == NULL)) {
122
0
    convpasswd = g_strdup(passwd);
123
0
  }
124
125
0
  cipher = purple_ciphers_find_cipher("md5");
126
0
  context = purple_cipher_context_new(cipher, NULL);
127
128
0
  x = g_strdup_printf("%s:%s:%s", convnode, realm, convpasswd ? convpasswd : "");
129
0
  purple_cipher_context_append(context, (const guchar *)x, strlen(x));
130
0
  purple_cipher_context_digest(context, sizeof(result), result, NULL);
131
132
0
  a1 = g_strdup_printf("xxxxxxxxxxxxxxxx:%s:%s", nonce, cnonce);
133
0
  a1len = strlen(a1);
134
0
  memmove(a1, result, 16);
135
136
0
  purple_cipher_context_reset(context, NULL);
137
0
  purple_cipher_context_append(context, (const guchar *)a1, a1len);
138
0
  purple_cipher_context_digest(context, sizeof(result), result, NULL);
139
140
0
  ha1 = purple_base16_encode(result, 16);
141
142
0
  purple_cipher_context_reset(context, NULL);
143
0
  purple_cipher_context_append(context, (const guchar *)a2, strlen(a2));
144
0
  purple_cipher_context_digest(context, sizeof(result), result, NULL);
145
146
0
  ha2 = purple_base16_encode(result, 16);
147
148
0
  kd = g_strdup_printf("%s:%s:00000001:%s:auth:%s", ha1, nonce, cnonce, ha2);
149
150
0
  purple_cipher_context_reset(context, NULL);
151
0
  purple_cipher_context_append(context, (const guchar *)kd, strlen(kd));
152
0
  purple_cipher_context_digest(context, sizeof(result), result, NULL);
153
0
  purple_cipher_context_destroy(context);
154
155
0
  z = purple_base16_encode(result, 16);
156
157
0
  g_free(convnode);
158
0
  g_free(convpasswd);
159
0
  g_free(x);
160
0
  g_free(a1);
161
0
  g_free(ha1);
162
0
  g_free(ha2);
163
0
  g_free(kd);
164
165
0
  return z;
166
0
}
167
168
static JabberSaslState
169
digest_md5_handle_challenge(JabberStream *js, xmlnode *packet,
170
                            xmlnode **response, char **msg)
171
0
{
172
0
  xmlnode *reply = NULL;
173
0
  char *enc_in = xmlnode_get_data(packet);
174
0
  char *dec_in;
175
0
  char *enc_out;
176
0
  GHashTable *parts;
177
0
  JabberSaslState state = JABBER_SASL_STATE_CONTINUE;
178
179
0
  if (!enc_in) {
180
0
    *msg = g_strdup(_("Invalid response from server"));
181
0
    return JABBER_SASL_STATE_FAIL;
182
0
  }
183
184
0
  dec_in = (char *)purple_base64_decode(enc_in, NULL);
185
0
  purple_debug_misc("jabber", "decoded challenge (%"
186
0
      G_GSIZE_FORMAT "): %s\n",
187
0
      strlen(dec_in),
188
0
      dec_in);
189
190
0
  parts = jabber_auth_digest_md5_parse(dec_in);
191
192
0
  if (g_hash_table_lookup(parts, "rspauth")) {
193
0
    char *rspauth = g_hash_table_lookup(parts, "rspauth");
194
0
    char *expected_rspauth = js->auth_mech_data;
195
196
0
    if (rspauth && purple_strequal(rspauth, expected_rspauth)) {
197
0
      reply = xmlnode_new("response");
198
0
      xmlnode_set_namespace(reply, NS_XMPP_SASL);
199
0
    } else {
200
0
      *msg = g_strdup(_("Invalid challenge from server"));
201
0
      state = JABBER_SASL_STATE_FAIL;
202
0
    }
203
0
    g_free(js->auth_mech_data);
204
0
    js->auth_mech_data = NULL;
205
0
  } else {
206
    /* assemble a response, and send it */
207
    /* see RFC 2831 */
208
0
    char *realm;
209
0
    char *nonce;
210
211
    /* Make sure the auth string contains everything that should be there.
212
       This isn't everything in RFC2831, but it is what we need. */
213
214
0
    nonce = g_hash_table_lookup(parts, "nonce");
215
216
    /* we're actually supposed to prompt the user for a realm if
217
     * the server doesn't send one, but that really complicates things,
218
     * so i'm not gonna worry about it until is poses a problem to
219
     * someone, or I get really bored */
220
0
    realm = g_hash_table_lookup(parts, "realm");
221
0
    if(!realm)
222
0
      realm = js->user->domain;
223
224
0
    if (nonce == NULL || realm == NULL) {
225
0
      *msg = g_strdup(_("Invalid challenge from server"));
226
0
      state = JABBER_SASL_STATE_FAIL;
227
0
    } else {
228
0
      GString *response = g_string_new("");
229
0
      char *a2;
230
0
      char *auth_resp;
231
0
      char *cnonce;
232
233
0
      cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL),
234
0
          g_random_int());
235
236
0
      a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm);
237
0
      auth_resp = generate_response_value(js->user,
238
0
          purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
239
0
      g_free(a2);
240
241
0
      a2 = g_strdup_printf(":xmpp/%s", realm);
242
0
      js->auth_mech_data = generate_response_value(js->user,
243
0
          purple_connection_get_password(js->gc), nonce, cnonce, a2, realm);
244
0
      g_free(a2);
245
246
0
      g_string_append_printf(response, "username=\"%s\"", js->user->node);
247
0
      g_string_append_printf(response, ",realm=\"%s\"", realm);
248
0
      g_string_append_printf(response, ",nonce=\"%s\"", nonce);
249
0
      g_string_append_printf(response, ",cnonce=\"%s\"", cnonce);
250
0
      g_string_append_printf(response, ",nc=00000001");
251
0
      g_string_append_printf(response, ",qop=auth");
252
0
      g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm);
253
0
      g_string_append_printf(response, ",response=%s", auth_resp);
254
0
      g_string_append_printf(response, ",charset=utf-8");
255
256
0
      g_free(auth_resp);
257
0
      g_free(cnonce);
258
259
0
      enc_out = purple_base64_encode((guchar *)response->str, response->len);
260
261
0
      purple_debug_misc("jabber", "decoded response (%"
262
0
          G_GSIZE_FORMAT "): %s\n",
263
0
          response->len, response->str);
264
265
0
      reply = xmlnode_new("response");
266
0
      xmlnode_set_namespace(reply, NS_XMPP_SASL);
267
0
      xmlnode_insert_data(reply, enc_out, -1);
268
269
0
      g_free(enc_out);
270
271
0
      g_string_free(response, TRUE);
272
0
    }
273
0
  }
274
275
0
  g_free(enc_in);
276
0
  g_free(dec_in);
277
0
  g_hash_table_destroy(parts);
278
279
0
  *response = reply;
280
0
  return state;
281
0
}
282
283
static void
284
digest_md5_dispose(JabberStream *js)
285
0
{
286
0
  g_free(js->auth_mech_data);
287
0
  js->auth_mech_data = NULL;
288
0
}
289
290
static JabberSaslMech digest_md5_mech = {
291
  10, /* priority */
292
  "DIGEST-MD5", /* name */
293
  digest_md5_start,
294
  digest_md5_handle_challenge,
295
  NULL, /* handle_success */
296
  NULL, /* handle_failure */
297
  digest_md5_dispose,
298
};
299
300
JabberSaslMech *jabber_auth_get_digest_md5_mech(void)
301
0
{
302
0
  return &digest_md5_mech;
303
0
}