1 /* ====================================================================
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 * ====================================================================
19 */
20
21 #include "auth_spnego.h"
22 #include "serf.h"
23 #include "serf_private.h"
24
25 #ifdef SERF_USE_SSPI
26 #include <apr.h>
27 #include <apr_strings.h>
28
29 #define SECURITY_WIN32
30 #include <sspi.h>
31
32 /* SEC_E_MUTUAL_AUTH_FAILED is not defined in Windows Platform SDK 5.0. */
33 #ifndef SEC_E_MUTUAL_AUTH_FAILED
34 #define SEC_E_MUTUAL_AUTH_FAILED _HRESULT_TYPEDEF_(0x80090363L)
35 #endif
36
37 struct serf__spnego_context_t
38 {
39 CredHandle sspi_credentials;
40 CtxtHandle sspi_context;
41 BOOL initalized;
42 apr_pool_t *pool;
43
44 /* Service Principal Name (SPN) used for authentication. */
45 const char *target_name;
46
47 /* One of SERF_AUTHN_* authentication types.*/
48 int authn_type;
49 };
50
51 /* Map SECURITY_STATUS from SSPI to APR error code. Some error codes mapped
52 * to our own codes and some to Win32 error codes:
53 * http://support.microsoft.com/kb/113996
54 */
55 static apr_status_t
map_sspi_status(SECURITY_STATUS sspi_status)56 map_sspi_status(SECURITY_STATUS sspi_status)
57 {
58 switch(sspi_status)
59 {
60 case SEC_E_INSUFFICIENT_MEMORY:
61 return APR_FROM_OS_ERROR(ERROR_NO_SYSTEM_RESOURCES);
62 case SEC_E_INVALID_HANDLE:
63 return APR_FROM_OS_ERROR(ERROR_INVALID_HANDLE);
64 case SEC_E_UNSUPPORTED_FUNCTION:
65 return APR_FROM_OS_ERROR(ERROR_INVALID_FUNCTION);
66 case SEC_E_TARGET_UNKNOWN:
67 return APR_FROM_OS_ERROR(ERROR_BAD_NETPATH);
68 case SEC_E_INTERNAL_ERROR:
69 return APR_FROM_OS_ERROR(ERROR_INTERNAL_ERROR);
70 case SEC_E_SECPKG_NOT_FOUND:
71 case SEC_E_BAD_PKGID:
72 return APR_FROM_OS_ERROR(ERROR_NO_SUCH_PACKAGE);
73 case SEC_E_NO_IMPERSONATION:
74 return APR_FROM_OS_ERROR(ERROR_CANNOT_IMPERSONATE);
75 case SEC_E_NO_AUTHENTICATING_AUTHORITY:
76 return APR_FROM_OS_ERROR(ERROR_NO_LOGON_SERVERS);
77 case SEC_E_UNTRUSTED_ROOT:
78 return APR_FROM_OS_ERROR(ERROR_TRUST_FAILURE);
79 case SEC_E_WRONG_PRINCIPAL:
80 return APR_FROM_OS_ERROR(ERROR_WRONG_TARGET_NAME);
81 case SEC_E_MUTUAL_AUTH_FAILED:
82 return APR_FROM_OS_ERROR(ERROR_MUTUAL_AUTH_FAILED);
83 case SEC_E_TIME_SKEW:
84 return APR_FROM_OS_ERROR(ERROR_TIME_SKEW);
85 default:
86 return SERF_ERROR_AUTHN_FAILED;
87 }
88 }
89
90 /* Cleans the SSPI context object, when the pool used to create it gets
91 cleared or destroyed. */
92 static apr_status_t
cleanup_ctx(void * data)93 cleanup_ctx(void *data)
94 {
95 serf__spnego_context_t *ctx = data;
96
97 if (SecIsValidHandle(&ctx->sspi_context)) {
98 DeleteSecurityContext(&ctx->sspi_context);
99 SecInvalidateHandle(&ctx->sspi_context);
100 }
101
102 if (SecIsValidHandle(&ctx->sspi_credentials)) {
103 FreeCredentialsHandle(&ctx->sspi_credentials);
104 SecInvalidateHandle(&ctx->sspi_credentials);
105 }
106
107 return APR_SUCCESS;
108 }
109
110 static apr_status_t
cleanup_sec_buffer(void * data)111 cleanup_sec_buffer(void *data)
112 {
113 FreeContextBuffer(data);
114
115 return APR_SUCCESS;
116 }
117
118 apr_status_t
serf__spnego_create_sec_context(serf__spnego_context_t ** ctx_p,const serf__authn_scheme_t * scheme,apr_pool_t * result_pool,apr_pool_t * scratch_pool)119 serf__spnego_create_sec_context(serf__spnego_context_t **ctx_p,
120 const serf__authn_scheme_t *scheme,
121 apr_pool_t *result_pool,
122 apr_pool_t *scratch_pool)
123 {
124 SECURITY_STATUS sspi_status;
125 serf__spnego_context_t *ctx;
126 const char *sspi_package;
127
128 ctx = apr_pcalloc(result_pool, sizeof(*ctx));
129
130 SecInvalidateHandle(&ctx->sspi_context);
131 SecInvalidateHandle(&ctx->sspi_credentials);
132 ctx->initalized = FALSE;
133 ctx->pool = result_pool;
134 ctx->target_name = NULL;
135 ctx->authn_type = scheme->type;
136
137 apr_pool_cleanup_register(result_pool, ctx,
138 cleanup_ctx,
139 apr_pool_cleanup_null);
140
141 if (ctx->authn_type == SERF_AUTHN_NEGOTIATE)
142 sspi_package = "Negotiate";
143 else
144 sspi_package = "NTLM";
145
146 sspi_status = AcquireCredentialsHandleA(
147 NULL, sspi_package, SECPKG_CRED_OUTBOUND,
148 NULL, NULL, NULL, NULL,
149 &ctx->sspi_credentials, NULL);
150
151 if (FAILED(sspi_status)) {
152 return map_sspi_status(sspi_status);
153 }
154
155 *ctx_p = ctx;
156
157 return APR_SUCCESS;
158 }
159
160 static apr_status_t
get_canonical_hostname(const char ** canonname,const char * hostname,apr_pool_t * pool)161 get_canonical_hostname(const char **canonname,
162 const char *hostname,
163 apr_pool_t *pool)
164 {
165 struct addrinfo hints;
166 struct addrinfo *addrinfo;
167
168 ZeroMemory(&hints, sizeof(hints));
169 hints.ai_flags = AI_CANONNAME;
170
171 if (getaddrinfo(hostname, NULL, &hints, &addrinfo)) {
172 return apr_get_netos_error();
173 }
174
175 if (addrinfo) {
176 *canonname = apr_pstrdup(pool, addrinfo->ai_canonname);
177 }
178 else {
179 *canonname = apr_pstrdup(pool, hostname);
180 }
181
182 freeaddrinfo(addrinfo);
183 return APR_SUCCESS;
184 }
185
186 apr_status_t
serf__spnego_reset_sec_context(serf__spnego_context_t * ctx)187 serf__spnego_reset_sec_context(serf__spnego_context_t *ctx)
188 {
189 if (SecIsValidHandle(&ctx->sspi_context)) {
190 DeleteSecurityContext(&ctx->sspi_context);
191 SecInvalidateHandle(&ctx->sspi_context);
192 }
193
194 ctx->initalized = FALSE;
195
196 return APR_SUCCESS;
197 }
198
199 apr_status_t
serf__spnego_init_sec_context(serf_connection_t * conn,serf__spnego_context_t * ctx,const char * service,const char * hostname,serf__spnego_buffer_t * input_buf,serf__spnego_buffer_t * output_buf,apr_pool_t * result_pool,apr_pool_t * scratch_pool)200 serf__spnego_init_sec_context(serf_connection_t *conn,
201 serf__spnego_context_t *ctx,
202 const char *service,
203 const char *hostname,
204 serf__spnego_buffer_t *input_buf,
205 serf__spnego_buffer_t *output_buf,
206 apr_pool_t *result_pool,
207 apr_pool_t *scratch_pool
208 )
209 {
210 SECURITY_STATUS status;
211 ULONG actual_attr;
212 SecBuffer sspi_in_buffer;
213 SecBufferDesc sspi_in_buffer_desc;
214 SecBuffer sspi_out_buffer;
215 SecBufferDesc sspi_out_buffer_desc;
216 apr_status_t apr_status;
217 const char *canonname;
218
219 if (!ctx->initalized && ctx->authn_type == SERF_AUTHN_NEGOTIATE) {
220 apr_status = get_canonical_hostname(&canonname, hostname, scratch_pool);
221 if (apr_status) {
222 return apr_status;
223 }
224
225 ctx->target_name = apr_pstrcat(scratch_pool, service, "/", canonname,
226 NULL);
227
228 serf__log_skt(AUTH_VERBOSE, __FILE__, conn->skt,
229 "Using SPN '%s' for '%s'\n", ctx->target_name, hostname);
230 }
231 else if (ctx->authn_type == SERF_AUTHN_NTLM)
232 {
233 /* Target name is not used for NTLM authentication. */
234 ctx->target_name = NULL;
235 }
236
237 /* Prepare input buffer description. */
238 sspi_in_buffer.BufferType = SECBUFFER_TOKEN;
239 sspi_in_buffer.pvBuffer = input_buf->value;
240 sspi_in_buffer.cbBuffer = input_buf->length;
241
242 sspi_in_buffer_desc.cBuffers = 1;
243 sspi_in_buffer_desc.pBuffers = &sspi_in_buffer;
244 sspi_in_buffer_desc.ulVersion = SECBUFFER_VERSION;
245
246 /* Output buffers. Output buffer will be allocated by system. */
247 sspi_out_buffer.BufferType = SECBUFFER_TOKEN;
248 sspi_out_buffer.pvBuffer = NULL;
249 sspi_out_buffer.cbBuffer = 0;
250
251 sspi_out_buffer_desc.cBuffers = 1;
252 sspi_out_buffer_desc.pBuffers = &sspi_out_buffer;
253 sspi_out_buffer_desc.ulVersion = SECBUFFER_VERSION;
254
255 status = InitializeSecurityContextA(
256 &ctx->sspi_credentials,
257 ctx->initalized ? &ctx->sspi_context : NULL,
258 ctx->target_name,
259 ISC_REQ_ALLOCATE_MEMORY
260 | ISC_REQ_MUTUAL_AUTH
261 | ISC_REQ_CONFIDENTIALITY,
262 0, /* Reserved1 */
263 SECURITY_NETWORK_DREP,
264 &sspi_in_buffer_desc,
265 0, /* Reserved2 */
266 &ctx->sspi_context,
267 &sspi_out_buffer_desc,
268 &actual_attr,
269 NULL);
270
271 if (sspi_out_buffer.cbBuffer > 0) {
272 apr_pool_cleanup_register(result_pool, sspi_out_buffer.pvBuffer,
273 cleanup_sec_buffer,
274 apr_pool_cleanup_null);
275 }
276
277 ctx->initalized = TRUE;
278
279 /* Finish authentication if SSPI requires so. */
280 if (status == SEC_I_COMPLETE_NEEDED
281 || status == SEC_I_COMPLETE_AND_CONTINUE)
282 {
283 CompleteAuthToken(&ctx->sspi_context, &sspi_out_buffer_desc);
284 }
285
286 output_buf->value = sspi_out_buffer.pvBuffer;
287 output_buf->length = sspi_out_buffer.cbBuffer;
288
289 switch(status) {
290 case SEC_I_COMPLETE_AND_CONTINUE:
291 case SEC_I_CONTINUE_NEEDED:
292 return APR_EAGAIN;
293
294 case SEC_I_COMPLETE_NEEDED:
295 case SEC_E_OK:
296 return APR_SUCCESS;
297
298 default:
299 return map_sspi_status(status);
300 }
301 }
302
303 #endif /* SERF_USE_SSPI */
304