Missing files
[public/netxms.git] / src / server / core / cas_validator.cpp
1 /**
2 * This code is heavily based on "pam_cas" module from "cas-client-2.0.11" package.
3 *
4 * -- Alex Kirhenshtein <alk@netxms.org>
5 */
6
7 /*
8 * Copyright (c) 2000-2003 Yale University. All rights reserved.
9 *
10 * THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED
11 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY
13 * DISCLAIMED. IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE
14 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
15 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF
16 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR
17 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
18 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
19 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
20 * SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH
21 * DAMAGE.
22 *
23 * Redistribution and use of this software in source or binary forms,
24 * with or without modification, are permitted, provided that the
25 * following conditions are met:
26 *
27 * 1. Any redistribution must include the above copyright notice and
28 * disclaimer and this list of conditions in any related documentation
29 * and, if feasible, in the redistributed software.
30 *
31 * 2. Any redistribution must include the acknowledgment, "This product
32 * includes software developed by Yale University," in any related
33 * documentation and, if feasible, in the redistributed software.
34 *
35 * 3. The names "Yale" and "Yale University" must not be used to endorse
36 * or promote products derived from this software.
37 */
38
39 /*
40 * CAS 2.0 service- and proxy-ticket validator in C, using OpenSSL.
41 *
42 * Originally by Shawn Bayern, Yale ITS Technology and Planning.
43 * Patches submitted by Vincent Mathieu, University of Nancy, France.
44 */
45
46 #include <stdio.h>
47 #include <memory.h>
48 #include <errno.h>
49 #include <sys/types.h>
50 #include <sys/socket.h>
51 #include <netinet/in.h>
52 #include <arpa/inet.h>
53 #include <netdb.h>
54 #include <openssl/crypto.h>
55 #include <openssl/x509.h>
56 #include <openssl/pem.h>
57 #include <openssl/ssl.h>
58 #include <openssl/err.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <unistd.h>
62
63 /**
64 * Settings
65 */
66 static char m_host[128] = "localhost";
67 static int m_port = 8443;
68 static char m_trustedCA[1024] = "/Users/alk/Development/cas/pam_cas/ca.pem";
69 //static char m_validateURL[1024] = "/cas/proxyValidate";
70 static char m_validateURL[1024] = "/cas/serviceValidate";
71 static char *m_proxies[] = { NULL };
72
73
74 /**
75 * Main code
76 */
77
78 /**
79 * Ticket identifiers to avoid needless validating passwords that
80 * aren't tickets
81 */
82 #define CAS_BEGIN_PT "PT-"
83 #define CAS_BEGIN_ST "ST-"
84
85
86 /* Error codes (decided upon by ESUP-Portail group) */
87 #define CAS_SUCCESS 0
88 #define CAS_AUTHENTICATION_FAILURE -1
89 #define CAS_ERROR -2
90
91 #define CAS_SSL_ERROR_CTX -10
92 #define CAS_SSL_ERROR_CONN -11
93 #define CAS_SSL_ERROR_CERT -12
94 #define CAS_SSL_ERROR_HTTPS -13
95
96 #define CAS_ERROR_CONN -20
97 #define CAS_PROTOCOL_FAILURE -21
98 #define CAS_BAD_PROXY -22
99
100
101 #define SET_RET_AND_GOTO_END(x) { ret = (x); goto end; }
102 #define FAIL SET_RET_AND_GOTO_END(CAS_ERROR)
103 #define SUCCEED SET_RET_AND_GOTO_END(CAS_SUCCESS)
104
105 #define DEBUG
106 //#undef DEBUG
107
108 #ifdef DEBUG
109 # define LOG(X) printf("%s", (X))
110 #else
111 # define LOG(X)
112 #endif
113
114 int cas_validate(char *ticket, char *service, char *outbuf, int outbuflen, char *proxies[]);
115 static X509 *get_cert_from_file(char *filename);
116 static int valid_cert(X509 *cert, char *hostname);
117 static int arrayContains(char *array[], char *element);
118 char *element_body(char *doc, char *tagname, int n, char *buf, int buflen);
119
120 /** Returns status of certification: 0 for invalid, 1 for valid. */
121 static int valid_cert(X509 *cert, char *hostname) {
122 int i;
123 char buf[4096];
124 X509_STORE *store = X509_STORE_new();
125 X509_STORE_CTX *ctx = X509_STORE_CTX_new();
126 X509 *cacert = get_cert_from_file(m_trustedCA);
127 if (cacert != NULL) {
128 X509_STORE_add_cert(store, cacert);
129 }
130 X509_STORE_CTX_init(ctx, store, cert, sk_X509_new_null());
131 if (X509_verify_cert(ctx) == 0) {
132 return 0;
133 }
134 X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, buf, sizeof(buf) - 1);
135 // anal-retentive: make sure the hostname isn't as long as the
136 // buffer, since we don't want to match only because of truncation
137 if (strlen(hostname) >= sizeof(buf) - 1) {
138 return 0;
139 }
140 return (!strcmp(buf, hostname));
141 }
142
143 /** Returns status of ticket by filling 'buf' with a NetID if the ticket
144 * is valid and buf is large enough and returning 1. If not, 0 is
145 * returned.
146 */
147 int cas_validate(char *ticket, char *service, char *outbuf, int outbuflen, char *proxies[]) {
148 int s = 0, err, b, ret, total;
149 struct sockaddr_in sa;
150 struct hostent h, *hp2;
151 SSL_CTX *ctx = NULL;
152 SSL *ssl = NULL;
153 X509 *s_cert = NULL;
154 char buf[4096];
155 SSL_METHOD *method = NULL;
156 char *full_request, *str, *tmp;
157 char netid[14];
158 char parsebuf[128];
159 int i;
160
161 SSLeay_add_ssl_algorithms();
162 method = SSLv23_client_method();
163 SSL_load_error_strings();
164 ctx = SSL_CTX_new(method);
165 if (!ctx) {
166 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CTX);
167 }
168 if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
169 SET_RET_AND_GOTO_END(CAS_ERROR_CONN);
170 }
171
172 memset(&sa, 0, sizeof(sa));
173 sa.sin_family = AF_INET;
174 hp2 = gethostbyname(m_host);
175 memcpy(&h, hp2, sizeof(h));
176 // gethostbyname_r(m_host, &h, buf, sizeof(buf), &hp2, &b);
177
178 memcpy(&(sa.sin_addr.s_addr), h.h_addr_list[0], sizeof(long));
179 sa.sin_port = htons(m_port);
180 if (connect(s, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
181 SET_RET_AND_GOTO_END(CAS_ERROR_CONN);
182 }
183 if (!(ssl = SSL_new(ctx))) {
184 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CTX);
185 }
186 if (!SSL_set_fd(ssl, s)) {
187 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CTX);
188 }
189 if (! (err = SSL_connect(ssl))) {
190 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CONN);
191 }
192 if (!(s_cert = SSL_get_peer_certificate(ssl))) {
193 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CERT);
194 }
195 if (!valid_cert(s_cert, m_host)) {
196 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_CERT);
197 }
198
199 X509_free(s_cert);
200
201 full_request = (char *)malloc(4096);
202 if (snprintf(full_request, 4096, "GET %s?ticket=%s&service=%s HTTP/1.0\n\n", m_validateURL, ticket, service) >= 4096) {
203 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_HTTPS);
204 }
205 if (!SSL_write(ssl, full_request, strlen(full_request))) {
206 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_HTTPS);
207 }
208
209 total = 0;
210 do {
211 b = SSL_read(ssl, buf + total, (sizeof(buf) - 1) - total);
212 total += b;
213 } while (b > 0);
214 buf[total] = '\0';
215
216 if (b != 0 || total >= sizeof(buf) - 1) {
217 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_HTTPS); // unexpected read error or response too large
218 }
219
220 str = (char *)strstr(buf, "\r\n\r\n"); // find the end of the header
221
222 if (!str) {
223 SET_RET_AND_GOTO_END(CAS_SSL_ERROR_HTTPS); // no header
224 }
225
226 /*
227 * 'str' now points to the beginning of the body, which should be an
228 * XML document
229 */
230
231 // make sure that the authentication succeeded
232
233 if (!element_body(str, "cas:authenticationSuccess", 1, parsebuf, sizeof(parsebuf))) {
234 LOG("authentication failure\n");
235 LOG(str);
236 LOG("\n");
237 SET_RET_AND_GOTO_END(CAS_AUTHENTICATION_FAILURE);
238 }
239
240 // retrieve the NetID
241 if (!element_body(str, "cas:user", 1, netid, sizeof(netid))) {
242 LOG("unable to determine username\n");
243 SET_RET_AND_GOTO_END(CAS_PROTOCOL_FAILURE);
244 }
245
246 // check the first proxy (if present)
247 if (element_body(str, "cas:proxies", 1, parsebuf, sizeof(parsebuf))) {
248 if (element_body(str, "cas:proxy", 1, parsebuf, sizeof(parsebuf))) {
249 if (!arrayContains(proxies, parsebuf)) {
250 LOG("bad proxy: ");
251 LOG(parsebuf);
252 LOG("\n");
253 SET_RET_AND_GOTO_END(CAS_BAD_PROXY);
254 }
255 }
256 }
257
258 /*
259 * without enough space, fail entirely, since a partial NetID could
260 * be dangerous
261 */
262 if (outbuflen < strlen(netid) + 1) {
263 LOG("output buffer too short\n");
264 SET_RET_AND_GOTO_END(CAS_PROTOCOL_FAILURE);
265 }
266
267 strcpy(outbuf, netid);
268 SUCCEED;
269
270 /* cleanup and return */
271
272 end:
273 if (ssl) SSL_shutdown(ssl);
274 if (s > 0) close(s);
275 if (ssl) SSL_free(ssl);
276 if (ctx) SSL_CTX_free(ctx);
277 return ret;
278 }
279
280 static X509 *get_cert_from_file(char *filename) {
281 X509 *c;
282 FILE *f = fopen(filename, "r");
283 if (!f) {
284 return NULL;
285 }
286 c = PEM_read_X509(f, NULL, NULL, NULL);
287 fclose(f);
288 return c;
289 }
290
291 // returns 1 if a char* array contains the given element, 0 otherwise
292 static int arrayContains(char *array[], char *element) {
293 char *p;
294 int i = 0;
295
296 for (p = array[0]; p; p = array[++i]) {
297 LOG(" checking element ");
298 LOG(p);
299 LOG("\n");
300 if (!strcmp(p, element)) {
301 return 1;
302 }
303 }
304 return 0;
305 }
306
307 /*
308 * Fills buf (up to buflen characters) with all characters (including
309 * those representing other elements) within the nth element in the
310 * document with the name provided by tagname.
311 */
312 char *element_body(char *doc, char *tagname, int n, char *buf, int buflen) {
313 char *start_tag_pattern = (char *)malloc(strlen(tagname) + strlen("<") + strlen(">") + 1);
314 char *end_tag_pattern = (char *)malloc(strlen(tagname) + strlen("<") + strlen("/") + strlen(">") + 1);
315 char *body_start, *body_end;
316 char *ret = NULL;
317
318 buf[0] = 0;
319
320 if (start_tag_pattern != NULL && end_tag_pattern != NULL)
321 {
322 sprintf(start_tag_pattern, "<%s>", tagname);
323 sprintf(end_tag_pattern, "</%s>", tagname);
324 body_start = doc;
325 while (n-- > 0)
326 {
327 body_start = strstr(body_start, start_tag_pattern);
328 if (!body_start)
329 {
330 SET_RET_AND_GOTO_END(NULL);
331 }
332 body_start += strlen(start_tag_pattern);
333 }
334 body_end = strstr(body_start, end_tag_pattern);
335 if (!body_end)
336 {
337 SET_RET_AND_GOTO_END(NULL);
338 }
339 if (body_end - body_start < buflen - 1)
340 {
341 strncpy(buf, body_start, body_end - body_start);
342 buf[body_end - body_start] = 0;
343 }
344 else
345 {
346 strncpy(buf, body_start, buflen - 1);
347 buf[buflen - 1] = 0;
348 }
349 SET_RET_AND_GOTO_END(buf);
350 }
351
352 end:
353 if (start_tag_pattern) free(start_tag_pattern);
354 if (end_tag_pattern) free(end_tag_pattern);
355 return ret;
356 }