[http] Allow for domain names within NTLM user names
[ipxe.git] / src / net / tcp / httpntlm.c
1 /*
2 * Copyright (C) 2017 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 /**
27 * @file
28 *
29 * Hyper Text Transfer Protocol (HTTP) NTLM authentication
30 *
31 */
32
33 #include <string.h>
34 #include <errno.h>
35 #include <ipxe/uri.h>
36 #include <ipxe/base64.h>
37 #include <ipxe/ntlm.h>
38 #include <ipxe/netbios.h>
39 #include <ipxe/http.h>
40
41 struct http_authentication http_ntlm_auth __http_authentication;
42
43 /** Workstation name used for NTLM authentication */
44 static const char http_ntlm_workstation[] = "iPXE";
45
46 /**
47 * Parse HTTP "WWW-Authenticate" header for NTLM authentication
48 *
49 * @v http HTTP transaction
50 * @v line Remaining header line
51 * @ret rc Return status code
52 */
53 static int http_parse_ntlm_auth ( struct http_transaction *http, char *line ) {
54 struct http_response_auth_ntlm *rsp = &http->response.auth.ntlm;
55 char *copy;
56 int len;
57 int rc;
58
59 /* Create temporary copy of Base64-encoded challenge message */
60 copy = strdup ( line );
61 if ( ! copy ) {
62 rc = -ENOMEM;
63 goto err_alloc;
64 }
65
66 /* Decode challenge message, overwriting the original */
67 len = base64_decode ( copy, line, strlen ( line ) );
68 if ( len < 0 ) {
69 rc = len;
70 DBGC ( http, "HTTP %p could not decode NTLM challenge "
71 "\"%s\": %s\n", http, copy, strerror ( rc ) );
72 goto err_decode;
73 }
74
75 /* Parse challenge, if present */
76 if ( len ) {
77 rsp->challenge = ( ( void * ) line );
78 if ( ( rc = ntlm_challenge ( rsp->challenge, len,
79 &rsp->info ) ) != 0 ) {
80 DBGC ( http, "HTTP %p could not parse NTLM challenge: "
81 "%s\n", http, strerror ( rc ) );
82 goto err_challenge;
83 }
84 }
85
86 /* Allow HTTP request to be retried if the request had not
87 * already tried authentication. Note that NTLM requires an
88 * additional round trip to obtain the challenge message,
89 * which is not present in the initial WWW-Authenticate.
90 */
91 if ( ( http->request.auth.auth == NULL ) ||
92 ( ( http->request.auth.auth == &http_ntlm_auth ) &&
93 ( http->request.auth.ntlm.len == 0 ) && len ) ) {
94 http->response.flags |= HTTP_RESPONSE_RETRY;
95 }
96
97 /* Success */
98 rc = 0;
99
100 err_challenge:
101 err_decode:
102 free ( copy );
103 err_alloc:
104 return rc;
105 }
106
107 /**
108 * Perform HTTP NTLM authentication
109 *
110 * @v http HTTP transaction
111 * @ret rc Return status code
112 */
113 static int http_ntlm_authenticate ( struct http_transaction *http ) {
114 struct http_request_auth_ntlm *req = &http->request.auth.ntlm;
115 struct http_response_auth_ntlm *rsp = &http->response.auth.ntlm;
116 struct ntlm_key key;
117 const char *domain;
118 char *username;
119 const char *password;
120
121 /* If we have no challenge yet, then just send a Negotiate message */
122 if ( ! rsp->challenge ) {
123 DBGC ( http, "HTTP %p sending NTLM Negotiate\n", http );
124 return 0;
125 }
126
127 /* Record username */
128 if ( ! http->uri->user ) {
129 DBGC ( http, "HTTP %p has no username for NTLM "
130 "authentication\n", http );
131 return -EACCES;
132 }
133 req->username = http->uri->user;
134 password = ( http->uri->password ? http->uri->password : "" );
135
136 /* Split NetBIOS [domain\]username */
137 username = ( ( char * ) req->username );
138 domain = netbios_domain ( &username );
139
140 /* Generate key */
141 ntlm_key ( domain, username, password, &key );
142
143 /* Generate responses */
144 ntlm_response ( &rsp->info, &key, NULL, &req->lm, &req->nt );
145
146 /* Calculate Authenticate message length */
147 req->len = ntlm_authenticate_len ( &rsp->info, domain, username,
148 http_ntlm_workstation );
149
150 /* Restore NetBIOS [domain\]username */
151 netbios_domain_undo ( domain, username );
152
153 return 0;
154 }
155
156 /**
157 * Construct HTTP "Authorization" header for NTLM authentication
158 *
159 * @v http HTTP transaction
160 * @v buf Buffer
161 * @v len Length of buffer
162 * @ret len Length of header value, or negative error
163 */
164 static int http_format_ntlm_auth ( struct http_transaction *http,
165 char *buf, size_t len ) {
166 struct http_request_auth_ntlm *req = &http->request.auth.ntlm;
167 struct http_response_auth_ntlm *rsp = &http->response.auth.ntlm;
168 struct ntlm_authenticate *auth;
169 const char *domain;
170 char *username;
171 size_t check;
172
173 /* If we have no challenge yet, then just send a Negotiate message */
174 if ( ! rsp->challenge ) {
175 return base64_encode ( &ntlm_negotiate,
176 sizeof ( ntlm_negotiate ), buf, len );
177 }
178
179 /* Skip allocation if just calculating length */
180 if ( ! len )
181 return base64_encoded_len ( req->len );
182
183 /* Allocate temporary buffer for Authenticate message */
184 auth = malloc ( req->len );
185 if ( ! auth )
186 return -ENOMEM;
187
188 /* Split NetBIOS [domain\]username */
189 username = ( ( char * ) req->username );
190 domain = netbios_domain ( &username );
191
192 /* Construct raw Authenticate message */
193 check = ntlm_authenticate ( &rsp->info, domain, username,
194 http_ntlm_workstation, &req->lm,
195 &req->nt, auth );
196 assert ( check == req->len );
197
198 /* Restore NetBIOS [domain\]username */
199 netbios_domain_undo ( domain, username );
200
201 /* Base64-encode Authenticate message */
202 len = base64_encode ( auth, req->len, buf, len );
203
204 /* Free raw Authenticate message */
205 free ( auth );
206
207 return len;
208 }
209
210 /** HTTP NTLM authentication scheme */
211 struct http_authentication http_ntlm_auth __http_authentication = {
212 .name = "NTLM",
213 .parse = http_parse_ntlm_auth,
214 .authenticate = http_ntlm_authenticate,
215 .format = http_format_ntlm_auth,
216 };
217
218 /* Drag in HTTP authentication support */
219 REQUIRING_SYMBOL ( http_ntlm_auth );
220 REQUIRE_OBJECT ( httpauth );