1
/*
2
 * gssapi.c -- GSSAPI authentication (see RFC 1508)
3
 *
4
 * For license terms, see the file COPYING in this directory.
5
 */
6
7
#include  "config.h"
8
#include  <stdio.h>
9
#include  <string.h>
10
#include  <ctype.h>
11
#if defined(STDC_HEADERS)
12
#include  <stdlib.h>
13
#endif
14
#include  "fetchmail.h"
15
#include  "socket.h"
16
17
#include  "i18n.h"
18
#include "fm_md5.h"
19
20
#include <sys/types.h>
21
#include <netinet/in.h>  /* for htonl/ntohl */
22
23
#ifdef GSSAPI
24
#  ifdef HAVE_GSS_H
25
#    include <gss.h>
26
#  else
27
#  if defined(HAVE_GSSAPI_H) && !defined(HAVE_GSSAPI_GSSAPI_H)
28
#    include <gssapi.h>
29
#  endif
30
#  ifdef HAVE_GSSAPI_GSSAPI_H
31
#    include <gssapi/gssapi.h>
32
#  endif
33
#  ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H
34
#    include <gssapi/gssapi_generic.h>
35
#  endif
36
#  ifndef HAVE_GSS_C_NT_HOSTBASED_SERVICE
37
#    define GSS_C_NT_HOSTBASED_SERVICE gss_nt_service_name
38
#  endif
39
#  endif
40
41
static void decode_subr(const char *m, uint32_t code, int type, FILE *ostrm)
42
{
43
    uint32_t maj, min, context;
44
    gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
45
46
    context = 0;
47
    do {
48
	maj = gss_display_status(&min, code, type, GSS_C_NO_OID,
49
		&context, &msg);
50
51
	if (maj != GSS_S_COMPLETE) {
52
	    report(stderr, GT_("GSSAPI error in gss_display_status called from <%s>\n"), m);
53
	    break;
54
	}
55
	report(ostrm, GT_("GSSAPI error %s: %.*s\n"), m,
56
		(int)msg.length, (char *)msg.value);
57
	(void)gss_release_buffer(&min, &msg);
58
    } while(context);
59
}
60
61
static void decode_status(const char *m, uint32_t major, uint32_t minor,
62
		FILE *ostrm)
63
{
64
    decode_subr(m, major, GSS_C_GSS_CODE,  ostrm);
65
    decode_subr(m, minor, GSS_C_MECH_CODE, ostrm);
66
}
67
68
#define GSSAUTH_P_NONE      1
69
#define GSSAUTH_P_INTEGRITY 2
70
#define GSSAUTH_P_PRIVACY   4
71
72
static int import_name(const char *service, const char *hostname,
73
	gss_name_t *target_name, flag verbose)
74
{
75
    char *buf1;
76
    size_t buf1siz;
77
    OM_uint32 maj_stat, min_stat;
78
    gss_buffer_desc request_buf;
79
80
    /* first things first: get an imap ticket for host */
81
    buf1siz = strlen(service) + 1 + strlen(hostname) + 1;
82
    buf1 = (char *)xmalloc(buf1siz);
83
    snprintf(buf1, buf1siz, "%s@%s", service, hostname);
84
    request_buf.value = buf1;
85
    request_buf.length = strlen(buf1) + 1;
86
    maj_stat = gss_import_name(&min_stat, &request_buf,
87
	    GSS_C_NT_HOSTBASED_SERVICE, target_name);
88
    if (maj_stat != GSS_S_COMPLETE) {
89
	decode_status("gss_import_name", maj_stat, min_stat, stderr);
90
        report(stderr, GT_("Couldn't get service name for [%s]\n"), buf1);
91
        return PS_AUTHFAIL;
92
    }
93
    else if (outlevel >= O_DEBUG && verbose) {
94
        (void)gss_display_name(&min_stat, *target_name, &request_buf, NULL);
95
        report(stderr, GT_("Using service name [%s]\n"),
96
	       (char *)request_buf.value);
97
    }
98
    (void)gss_release_buffer(&min_stat, &request_buf);
99
100
    return PS_SUCCESS;
101
}
102
103
/* If we don't have suitable credentials, don't bother trying GSSAPI, but
104
 * fail right away. This is to avoid that a server - such as Microsoft
105
 * Exchange 2007 - gets wedged and refuses different authentication
106
 * mechanisms afterwards. */
107
int check_gss_creds(const char *service, const char *hostname)
108
{
109
    OM_uint32 maj_stat, min_stat;
110
    gss_cred_usage_t cu;
111
    gss_name_t target_name;
112
113
    (void)import_name(service, hostname, &target_name, FALSE);
114
    (void)gss_release_name(&min_stat, &target_name);
115
116
    maj_stat = gss_inquire_cred(&min_stat, GSS_C_NO_CREDENTIAL,
117
	    NULL, NULL, &cu, NULL);
118
    if (maj_stat != GSS_S_COMPLETE
119
	    || (cu != GSS_C_INITIATE && cu != GSS_C_BOTH)) {
120
	if (outlevel >= O_DEBUG) {
121
	    decode_status("gss_inquire_cred", maj_stat, min_stat, stdout);
122
	    report(stdout, GT_("No suitable GSSAPI credentials found. Skipping GSSAPI authentication.\n"));
123
	    report(stdout, GT_("If you want to use GSSAPI, you need credentials first, possibly from kinit.\n"));
124
	}
125
	return PS_AUTHFAIL;
126
    }
127
128
    return PS_SUCCESS;
129
}
130
131
int do_gssauth(int sock, const char *command, const char *service,
132
		const char *hostname, const char *username)
133
{
134
    gss_buffer_desc request_buf, send_token;
135
    gss_buffer_t sec_token;
136
    gss_name_t target_name;
137
    gss_ctx_id_t context;
138
    gss_qop_t quality;
139
    int cflags;
140
    OM_uint32 maj_stat, min_stat;
141
    char buf1[8192], buf2[8192], server_conf_flags;
142
    unsigned long buf_size;
143
    int result;
144
145
    result = import_name(service, hostname, &target_name, TRUE);
146
    if (result)
147
	return result;
148
149
    gen_send(sock, "%s GSSAPI", command);
150
151
    /* upon receipt of the GSSAPI authentication request, server returns
152
     * a null data ready challenge to us. */
153
    result = gen_recv(sock, buf1, sizeof buf1);
154
    if (result)
155
	return result;
156
157
    if (buf1[0] != '+' || strspn(buf1 + 1, " \t") < strlen(buf1 + 1)) {
158
	if (outlevel >= O_VERBOSE) {
159
	    report(stdout, GT_("Received malformed challenge to \"%s GSSAPI\"!\n"), command);
160
	}
161
	goto cancelfail;
162
    }
163
164
165
    /* now start the security context initialisation loop... */
166
    sec_token = GSS_C_NO_BUFFER;
167
    context = GSS_C_NO_CONTEXT;
168
    if (outlevel >= O_VERBOSE)
169
        report(stdout, GT_("Sending credentials\n"));
170
    do {
171
	send_token.length = 0;
172
	send_token.value = NULL;
173
	maj_stat = gss_init_sec_context(&min_stat,
174
					GSS_C_NO_CREDENTIAL,
175
					&context,
176
					target_name,
177
					GSS_C_NO_OID,
178
					GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG,
179
					0,
180
					GSS_C_NO_CHANNEL_BINDINGS,
181
					sec_token,
182
					NULL,
183
					&send_token,
184
					NULL,
185
					NULL);
186
187
	if (maj_stat!=GSS_S_COMPLETE && maj_stat!=GSS_S_CONTINUE_NEEDED) {
188
	    if (outlevel >= O_VERBOSE)
189
		decode_status("gss_init_sec_context", maj_stat, min_stat, stdout);
190
	    (void)gss_release_name(&min_stat, &target_name);
191
192
cancelfail:
193
	    /* wake up server and cancel authentication */
194
	    suppress_tags = TRUE;
195
	    gen_send(sock, "*");
196
	    suppress_tags = FALSE;
197
198
	    result = gen_recv(sock, buf1, sizeof buf1);
199
	    if (outlevel >= O_VERBOSE)
200
		report(stderr, GT_("Error exchanging credentials\n"));
201
	    if (result)
202
		return result;
203
	    return PS_AUTHFAIL;
204
	}
205
	to64frombits(buf1, send_token.value, send_token.length);
206
	gss_release_buffer(&min_stat, &send_token);
207
208
	suppress_tags = TRUE;
209
	gen_send(sock, "%s", buf1);
210
	suppress_tags = FALSE;
211
212
        if (maj_stat == GSS_S_CONTINUE_NEEDED) {
213
	    result = gen_recv(sock, buf1, sizeof buf1);
214
	    if (result) {
215
	        gss_release_name(&min_stat, &target_name);
216
	        return result;
217
	    }
218
	    request_buf.length = from64tobits(buf2, buf1 + 2, sizeof(buf2));
219
	    if ((int)request_buf.length == -1)	/* in case of bad data */
220
		request_buf.length = 0;
221
	    request_buf.value = buf2;
222
	    sec_token = &request_buf;
223
        }
224
    } while
225
	(maj_stat == GSS_S_CONTINUE_NEEDED);
226
    gss_release_name(&min_stat, &target_name);
227
228
    /* get security flags and buffer size */
229
    result = gen_recv(sock, buf1, sizeof buf1);
230
    if (result)
231
        return result;
232
233
    request_buf.length = from64tobits(buf2, buf1 + 2, sizeof(buf2));
234
    if ((int)request_buf.length == -1)	/* in case of bad data */
235
	request_buf.length = 0;
236
    request_buf.value = buf2;
237
238
    maj_stat = gss_unwrap(&min_stat, context,
239
			  &request_buf, &send_token, &cflags, &quality);
240
    if (maj_stat != GSS_S_COMPLETE) {
241
	decode_status("gss_unwrap", maj_stat, min_stat, stderr);
242
        report(stderr, GT_("Couldn't unwrap security level data\n"));
243
        gss_release_buffer(&min_stat, &send_token);
244
        return PS_AUTHFAIL;
245
    }
246
    if (outlevel >= O_DEBUG)
247
        report(stdout, GT_("Credential exchange complete\n"));
248
    /* first octet is security levels supported. We want none, for now */
249
    server_conf_flags = ((char *)send_token.value)[0];
250
    if ( !(((char *)send_token.value)[0] & GSSAUTH_P_NONE) ) {
251
        report(stderr, GT_("Server requires integrity and/or privacy\n"));
252
        gss_release_buffer(&min_stat, &send_token);
253
        return PS_AUTHFAIL;
254
    }
255
    ((char *)send_token.value)[0] = 0;
256
    buf_size = ntohl(*((long *)send_token.value));
257
    /* we don't care about buffer size if we don't wrap data */
258
    gss_release_buffer(&min_stat, &send_token);
259
    if (outlevel >= O_DEBUG) {
260
        report(stdout, GT_("Unwrapped security level flags: %s%s%s\n"),
261
            server_conf_flags & GSSAUTH_P_NONE ? "N" : "-",
262
            server_conf_flags & GSSAUTH_P_INTEGRITY ? "I" : "-",
263
            server_conf_flags & GSSAUTH_P_PRIVACY ? "C" : "-");
264
        report(stdout, GT_("Maximum GSS token size is %ld\n"),buf_size);
265
    }
266
267
    /* now respond in kind (hack!!!) */
268
    buf_size = htonl(buf_size); /* do as they do... only matters if we do enc */
269
    memcpy(buf1, &buf_size, 4);
270
    buf1[0] = GSSAUTH_P_NONE;
271
    strlcpy(buf1+4, username, sizeof(buf1) - 4); /* server decides if princ is user */
272
    request_buf.length = 4 + strlen(username) + 1;
273
    request_buf.value = buf1;
274
    maj_stat = gss_wrap(&min_stat, context, 0, GSS_C_QOP_DEFAULT, &request_buf,
275
        &cflags, &send_token);
276
    if (maj_stat != GSS_S_COMPLETE) {
277
        report(stderr, GT_("Error creating security level request\n"));
278
        return PS_AUTHFAIL;
279
    }
280
    to64frombits(buf1, send_token.value, send_token.length);
281
282
    suppress_tags = TRUE;
283
    result = gen_transact(sock, "%s", buf1);
284
    suppress_tags = FALSE;
285
286
    /* flush security context */
287
    if (outlevel >= O_DEBUG)
288
	report(stdout, GT_("Releasing GSS credentials\n"));
289
    maj_stat = gss_delete_sec_context(&min_stat, &context, &send_token);
290
    if (maj_stat != GSS_S_COMPLETE) {
291
	decode_status("gss_delete_sec_context", maj_stat, min_stat, stderr);
292
	report(stderr, GT_("Error releasing credentials\n"));
293
	return PS_AUTHFAIL;
294
    }
295
    /* send_token may contain a notification to the server to flush
296
     * credentials. RFC 1731 doesn't specify what to do, and since this
297
     * support is only for authentication, we'll assume the server
298
     * knows enough to flush its own credentials */
299
    gss_release_buffer(&min_stat, &send_token);
300
301
    if (result)
302
	return(result);
303
    else
304
	return(PS_SUCCESS);
305
}	
306
#endif /* GSSAPI */
307
308
/* gssapi.c ends here */