Coverage Report

Created: 2025-11-03 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-auth/auth-gs2.c
Line
Count
Source
1
/* Copyright (c) 2023 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
6
#include "auth-gs2.h"
7
8
static const unsigned char auth_gs2_cb_name_char_mask = (1<<0);
9
10
static const unsigned char auth_gs2_char_lookup[256] = {
11
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00
12
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10
13
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 20
14
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 30
15
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40
16
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 50
17
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60
18
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 70
19
20
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80
21
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90
22
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A0
23
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B0
24
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C0
25
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D0
26
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E0
27
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F0
28
};
29
30
7.84k
static inline bool auth_gs2_char_is_cb_name(unsigned char ch) {
31
7.84k
  return ((auth_gs2_char_lookup[ch] & auth_gs2_cb_name_char_mask) != 0);
32
7.84k
}
33
34
static inline const char *_char_sanitize(unsigned char c)
35
23
{
36
23
  if (c >= 0x20 && c < 0x7F)
37
11
    return t_strdup_printf("'%c'", c);
38
12
  return t_strdup_printf("<0x%02x>", c);
39
23
}
40
41
/* RFC 5801, Section 4:
42
43
   The "gs2-authzid" holds the SASL authorization identity. It is encoded using
44
   UTF-8 [RFC3629] with three exceptions:
45
46
   o  The NUL character is forbidden as required by section 3.4.1 of [RFC4422].
47
48
   o  The server MUST replace any "," (comma) in the string with "=2C".
49
50
   o  The server MUST replace any "=" (equals) in the string with "=3D".
51
 */
52
53
void auth_gs2_encode_username(const char *in, buffer_t *out)
54
3.01k
{
55
53.0M
  for (; *in != '\0'; in++) {
56
53.0M
    if (in[0] == ',')
57
39.3M
      str_append(out, "=2C");
58
13.6M
    else if (in[0] == '=')
59
423k
      str_append(out, "=3D");
60
13.2M
    else
61
13.2M
      str_append_c(out, *in);
62
53.0M
  }
63
3.01k
}
64
65
int auth_gs2_decode_username(const unsigned char *in, size_t in_size,
66
           const char **out_r)
67
2.53k
{
68
2.53k
  const unsigned char *p = in, *pend = in + in_size;
69
2.53k
  string_t *out;
70
71
2.53k
  out = t_str_new(64);
72
1.97M
  while (p < pend) {
73
1.97M
    if (*p == '\0' || *p == ',')
74
0
      return -1;
75
1.97M
    if (*p == '=') {
76
1.49M
      p++;
77
1.49M
      if (p >= pend)
78
3
        return -1;
79
1.49M
      if (*p == '2') {
80
1.41M
        p++;
81
1.41M
        if (p >= pend)
82
2
          return -1;
83
1.41M
        if (*p != 'C')
84
8
          return -1;
85
1.41M
        str_append_c(out, ',');
86
1.41M
      } else if (*p == '3') {
87
78.1k
        p++;
88
78.1k
        if (p >= pend)
89
1
          return -1;
90
78.1k
        if (*p != 'D')
91
7
          return -1;
92
78.0k
        str_append_c(out, '=');
93
78.0k
      } else {
94
19
        return -1;
95
19
      }
96
1.49M
    } else {
97
480k
      str_append_c(out, *p);
98
480k
    }
99
1.97M
    p++;
100
1.97M
  }
101
2.49k
  *out_r = str_c(out);
102
2.49k
  return 0;
103
2.53k
}
104
105
/* RFC 5801, Section 4:
106
107
    UTF8-1-safe    = %x01-2B / %x2D-3C / %x3E-7F
108
                     ;; As UTF8-1 in RFC 3629 except
109
                     ;; NUL, "=", and ",".
110
    UTF8-2         = <as defined in RFC 3629 (STD 63)>
111
    UTF8-3         = <as defined in RFC 3629 (STD 63)>
112
    UTF8-4         = <as defined in RFC 3629 (STD 63)>
113
    UTF8-char-safe = UTF8-1-safe / UTF8-2 / UTF8-3 / UTF8-4
114
115
    saslname       = 1*(UTF8-char-safe / "=2C" / "=3D")
116
    gs2-authzid    = "a=" saslname
117
                      ;; GS2 has to transport an authzid since
118
                      ;; the GSS-API has no equivalent
119
    gs2-nonstd-flag = "F"
120
                      ;; "F" means the mechanism is not a
121
                      ;; standard GSS-API mechanism in that the
122
                      ;; RFC 2743, Section 3.1 header was missing
123
    cb-name         = 1*(ALPHA / DIGIT / "." / "-")
124
                      ;; See RFC 5056, Section 7.
125
    gs2-cb-flag     = ("p=" cb-name) / "n" / "y"
126
                      ;; GS2 channel binding (CB) flag
127
                      ;; "p" -> client supports and used CB
128
                      ;; "n" -> client does not support CB
129
                      ;; "y" -> client supports CB, thinks the server
130
                      ;;           does not
131
    gs2-header = [gs2-nonstd-flag ","] gs2-cb-flag "," [gs2-authzid] ","
132
                      ;; The GS2 header is gs2-header.
133
 */
134
135
void auth_gs2_header_encode(const struct auth_gs2_header *hdr, buffer_t *out)
136
2.11k
{
137
  /* [gs2-nonstd-flag ","] */
138
2.11k
  if (hdr->nonstd)
139
0
    str_append(out, "F,");
140
141
  /* gs2-cb-flag "," */
142
2.11k
  switch (hdr->cbind.status) {
143
1.37k
  case AUTH_GS2_CBIND_STATUS_NO_CLIENT_SUPPORT:
144
1.37k
    str_append_c(out, 'n');
145
1.37k
    break;
146
0
  case AUTH_GS2_CBIND_STATUS_NO_SERVER_SUPPORT:
147
0
    str_append_c(out, 'y');
148
0
    break;
149
737
  case AUTH_GS2_CBIND_STATUS_PROVIDED:
150
737
    i_assert(hdr->cbind.name != NULL && *hdr->cbind.name != '\0');
151
737
    str_append(out, "p=");
152
737
    str_append(out, hdr->cbind.name);
153
737
    break;
154
2.11k
  };
155
2.11k
  str_append_c(out, ',');
156
157
  /* [gs2-authzid] "," */
158
2.11k
  if (hdr->authzid != NULL && *hdr->authzid != '\0') {
159
1.43k
    str_append(out, "a=");
160
1.43k
    auth_gs2_encode_username(hdr->authzid, out);
161
1.43k
  }
162
2.11k
  str_append_c(out, ',');
163
2.11k
}
164
165
int auth_gs2_header_decode(const unsigned char *data, size_t size,
166
         bool expect_nonstd, struct auth_gs2_header *hdr_r,
167
         const unsigned char **hdr_end_r,
168
         const char **error_r)
169
1.92k
{
170
1.92k
  if (size < 3) {
171
15
    *error_r = "Message too small for GS2 header";
172
15
    return -1;
173
15
  }
174
175
1.90k
  const unsigned char *p = data, *pend = data + size, *offset;
176
1.90k
  struct auth_gs2_header hdr;
177
178
1.90k
  i_zero(&hdr);
179
180
  /* [gs2-nonstd-flag ","] */
181
1.90k
  if (*p == 'F') {
182
1
    if (!expect_nonstd) {
183
1
      *error_r = "Unexpected nonstd 'F' flag";
184
1
      return -1;
185
1
    }
186
0
    p++;
187
0
    if (*p != ',') {
188
0
      *error_r = "Missing ',' after nonstd 'F' flag";
189
0
      return -1;
190
0
    }
191
0
    hdr.nonstd = TRUE;
192
0
    p++;
193
0
  }
194
195
  /* gs2-cb-flag "," */
196
1.90k
  switch (*p) {
197
1.21k
  case 'n':
198
1.21k
    hdr.cbind.status = AUTH_GS2_CBIND_STATUS_NO_CLIENT_SUPPORT;
199
1.21k
    break;
200
2
  case 'y':
201
2
    hdr.cbind.status = AUTH_GS2_CBIND_STATUS_NO_SERVER_SUPPORT;
202
2
    break;
203
661
  case 'p':
204
661
    hdr.cbind.status = AUTH_GS2_CBIND_STATUS_PROVIDED;
205
661
    break;
206
23
  default:
207
23
    *error_r = t_strdup_printf(
208
23
      "Invalid channel bind flag %s",
209
23
      _char_sanitize(*p));
210
23
    return -1;
211
1.90k
  }
212
1.88k
  p++;
213
1.88k
  if (hdr.cbind.status == AUTH_GS2_CBIND_STATUS_PROVIDED) {
214
    /* "=" cb-name */
215
661
    if (p >= pend || *p != '=') {
216
4
      *error_r = "Missing '=' after 'p' flag";
217
4
      return -1;
218
4
    }
219
657
    p++;
220
221
657
    offset = p;
222
657
    if (p >= pend || *p == ',') {
223
1
      *error_r = "Empty channel bind name";
224
1
      return -1;
225
1
    }
226
8.49k
    while (p < pend && *p != ',') {
227
7.84k
      if (!auth_gs2_char_is_cb_name(*p)) {
228
13
        *error_r = "Invalid channel bind name";
229
13
        return -1;
230
13
      }
231
7.83k
      p++;
232
7.83k
    }
233
643
    hdr.cbind.name = t_strdup_until(offset, p);
234
643
  }
235
1.86k
  if (p >= pend || *p != ',') {
236
12
    *error_r = "Missing ',' after channel bind flag";
237
12
    return -1;
238
12
  }
239
1.85k
  p++;
240
241
  /* [gs2-authzid] "," */
242
1.85k
  if (p < pend && *p == 'a') {
243
1.19k
    p++;
244
1.19k
    if (p >= pend || *p != '=') {
245
11
      *error_r = "Missing '=' after 'a'";
246
11
      return -1;
247
11
    }
248
1.18k
    p++;
249
250
1.18k
    offset = p;
251
1.18k
    if (p >= pend || *p == ',') {
252
2
      *error_r = "Empty authzid field";
253
2
      return -1;
254
2
    }
255
4.56M
    while (p < pend && *p != ',')
256
4.56M
      p++;
257
1.18k
    if (auth_gs2_decode_username(offset, p - offset,
258
1.18k
               &hdr.authzid) < 0) {
259
24
      *error_r = "Invalid authzid field";
260
24
      return -1;
261
24
    }
262
1.18k
  }
263
1.81k
  if (p >= pend || *p != ',') {
264
11
    *error_r = "Missing ',' after authzid field";
265
11
    return -1;
266
11
  }
267
1.80k
  p++;
268
269
1.80k
  *error_r = NULL;
270
1.80k
  *hdr_r = hdr;
271
1.80k
  *hdr_end_r = p;
272
1.80k
  return 0;
273
1.81k
}