1 /* MODULE: auth_httpform */
4 * Copyright (c) 2005 Pyx Engineering AG
5 * Copyright (c) 1998 Messaging Direct Ltd.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MESSAGING DIRECT LTD. OR
21 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
24 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
26 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
27 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
30 * Copyright 1998, 1999 Carnegie Mellon University
34 * Permission to use, copy, modify, and distribute this software and its
35 * documentation for any purpose and without fee is hereby granted,
36 * provided that the above copyright notice appear in all copies and that
37 * both that copyright notice and this permission notice appear in
38 * supporting documentation, and that the name of Carnegie Mellon
39 * University not be used in advertising or publicity pertaining to
40 * distribution of the software without specific, written prior
43 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
44 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
45 * FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE FOR
46 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
47 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
48 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
49 * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
53 * Proxy authentication to a remote HTTP server.
57 #ident "$Id: auth_httpform.c,v 1.2 2006/04/19 19:51:04 murch Exp $"
60 /* PUBLIC DEPENDENCIES */
70 #include <sys/types.h>
71 #include <sys/socket.h>
72 #include <netinet/in.h>
73 #include <arpa/inet.h>
77 #include "mechanisms.h"
81 #include "auth_httpform.h"
82 /* END PUBLIC DEPENDENCIES */
85 #define MAX(p,q) ((p >= q) ? p : q)
88 /* PRIVATE DEPENDENCIES */
89 static cfile config = NULL;
90 static const char *r_host = "localhost"; /* remote host (mech_option) */
91 static const char *r_port = "80"; /* remote port (mech_option) */
92 static const char *r_uri = NULL; /* URI to call (mech_option) */
93 static const char *formdata = NULL; /* HTML form data (mech_option) */
94 static struct addrinfo *ai = NULL; /* remote host, as looked up */
95 /* END PRIVATE DEPENDENCIES */
97 #define NETWORK_IO_TIMEOUT 30 /* network I/O timeout (seconds) */
98 #define RESP_LEN 1000 /* size of read response buffer */
100 #define TWO_CRLF "\r\n\r\n"
104 #define HTTP_STATUS_SUCCESS "200"
105 #define HTTP_STATUS_REFUSE "403"
107 /* Common failure response strings for auth_httpform() */
109 #define RESP_IERROR "NO [ALERT] saslauthd internal error"
110 #define RESP_UNAVAILABLE "NO [ALERT] The remote authentication server is currently unavailable"
111 #define RESP_UNEXPECTED "NO [ALERT] Unexpected response from remote authentication server"
113 /* FUNCTION: sig_null */
116 * Catch and ignore a signal.
119 static RETSIGTYPE /* R: OS dependent */
122 int sig /* I: signal being caught */
129 signal(SIGALRM, sig_null);
133 signal(SIGPIPE, sig_null);
137 logger(L_INFO, "auth_httpform", "unexpected signal %d", sig);
142 #else /* __APPLE__ */
143 # if RETSIGTYPE == void
145 # else /* RETSIGTYPE */
147 # endif /* RETSIGTYPE */
148 #endif /* __APPLE__ */
151 /* END FUNCTION: sig_null */
153 /* FUNCTION: url_escape */
156 * URL-escapes the given string
158 * Note: calling function must free memory.
161 static char *url_escape(
168 size_t length = strlen(string);
169 size_t alloc = length+50; /* add some reserve */
171 int outidx=0, inidx=0;
178 while (inidx < length) {
179 char in = string[inidx];
180 if (!(in >= 'a' && in <= 'z') &&
181 !(in >= 'A' && in <= 'Z') &&
182 !(in >= '0' && in <= '9') &&
183 in != '&' && in != '=' && in != '-' && in != '_') {
186 if (outidx+3 > alloc) {
187 /* the size grows with two, since this'll become a %XX */
190 tmp = realloc(out, alloc);
199 snprintf(&out[outidx], 4, "%%%02X", in);
208 out[outidx] = 0; /* terminate it */
212 /* END FUNCTION: url_escape */
214 /* FUNCTION: create_post_data */
217 * Replace %u, %p and %r in the form data read from the config file
218 * with the actual username and password.
220 * Large parts of this functions have been shamelessly copied from
221 * the sql_create_statement() in the sql.c plugin code
223 * Note: calling function must free memory.
227 static char *create_post_data(
229 const char *formdata,
231 const char *password,
237 const char *ptr, *line_ptr;
240 int ulen, plen, rlen;
246 /* calculate memory needed for creating the complete query string. */
248 plen = strlen(password);
249 rlen = strlen(realm);
251 /* what if we have multiple %foo occurrences in the input query? */
252 for (i = 0; i < strlen(formdata); i++) {
253 if (formdata[i] == '%') {
258 /* find the biggest of ulen, plen */
259 biggest = MAX(MAX(ulen, plen), rlen);
261 /* don't forget the trailing 0x0 */
262 filtersize = strlen(formdata) + 1 + (numpercents*biggest)+1;
264 /* ok, now try to allocate a chunk of that size */
265 buf = (char *) malloc(filtersize);
268 logger(LOG_ERR, "auth_httpform:create_post_data", "failed to allocate memory");
275 /* replace the strings */
276 while ( (ptr = strchr(line_ptr, '%')) ) {
277 /* copy up to but not including the next % */
278 memcpy(buf_ptr, line_ptr, ptr - line_ptr);
279 buf_ptr += ptr - line_ptr;
287 memcpy(buf_ptr, user, ulen);
291 memcpy(buf_ptr, password, plen);
295 memcpy(buf_ptr, realm, rlen);
308 /* don't forget the rest */
309 memcpy(buf_ptr, line_ptr, strlen(line_ptr)+1);
314 /* END FUNCTION: create_post_data */
316 /* FUNCTION: build_sasl_response */
319 * Build a SASL response out of the HTTP response
321 * Note: The returned string is malloced and will be free'd by the
325 static char *build_sasl_response(
327 const char *http_response
333 char *c, *http_response_code, *http_response_string;
337 /* parse the response, just the first line */
338 /* e.g. HTTP/1.1 200 OK */
339 /* e.g. HTTP/1.1 403 User unknown */
340 c = strpbrk(http_response, CRLF);
342 *c = '\0'; /* tie off line termination */
345 /* isolate the HTTP response code and string */
346 http_response_code = strpbrk(http_response, SPACE) + 1;
347 http_response_string = strpbrk(http_response_code, SPACE) + 1;
348 *(http_response_string-1) = '\0'; /* replace space after code with 0 */
350 if (!strcmp(http_response_code, HTTP_STATUS_SUCCESS)) {
351 return strdup("OK remote authentication successful");
353 if (!strcmp(http_response_code, HTTP_STATUS_REFUSE)) {
354 /* return the HTTP response string as the SASL response */
355 length = strlen(http_response_string) + 3 + 1;
356 sasl_response = malloc(length);
357 if (sasl_response == NULL)
360 snprintf(sasl_response, length, "NO %s", http_response_string);
361 return sasl_response;
364 logger(L_INFO, "auth_httpform", "unexpected response to auth request: %s %s",
365 http_response_code, http_response_string);
367 return strdup(RESP_UNEXPECTED);
370 /* END FUNCTION: build_sasl_response */
372 /* FUNCTION: auth_httpform_init */
375 * Validate the host and service names for the remote server.
381 void /* no parameters */
387 char *configname = NULL;
388 struct addrinfo hints;
391 /* name of config file may be given with -O option */
393 configname = mech_option;
394 else if (access(SASLAUTHD_CONF_FILE_DEFAULT, F_OK) == 0)
395 configname = SASLAUTHD_CONF_FILE_DEFAULT;
397 /* open and read config file */
399 char complaint[1024];
401 if (!(config = cfile_read(configname, complaint, sizeof (complaint)))) {
402 syslog(LOG_ERR, "auth_httpform_init %s", complaint);
408 r_host = cfile_getstring(config, "httpform_host", r_host);
409 r_port = cfile_getstring(config, "httpform_port", r_port);
410 r_uri = cfile_getstring(config, "httpform_uri", r_uri);
411 formdata = cfile_getstring(config, "httpform_data", formdata);
414 if (formdata == NULL || r_uri == NULL) {
415 syslog(LOG_ERR, "auth_httpform_init formdata and uri must be specified");
419 /* lookup the host/port - taken from auth_rimap */
422 memset(&hints, 0, sizeof(hints));
423 hints.ai_family = PF_UNSPEC;
424 hints.ai_socktype = SOCK_STREAM;
425 hints.ai_flags = AI_CANONNAME;
426 if ((rc = getaddrinfo(r_host, r_port, &hints, &ai)) != 0) {
427 syslog(LOG_ERR, "auth_httpform_init: getaddrinfo %s/%s: %s",
428 r_host, r_port, gai_strerror(rc));
432 /* Make sure we have AF_INET or AF_INET6 addresses. */
433 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) {
434 syslog(LOG_ERR, "auth_httpform_init: no IP address info for %s",
435 ai->ai_canonname ? ai->ai_canonname : r_host);
444 /* END FUNCTION: auth_httpform_init */
446 /* FUNCTION: auth_httpform */
449 * Proxy authenticate to a remote HTTP server with a form POST.
451 * This mechanism takes the plaintext authenticator and password, forms
452 * them into an HTTP POST request. If the HTTP server responds with a 200
453 * status code, the credentials are considered valid. If it responds with
454 * a 403 HTTP status code, the credentials are considered wrong. Any other
455 * HTTP status code is treated like a network error.
458 /* XXX This should be extended to support SASL PLAIN authentication */
460 char * /* R: Allocated response string */
463 const char *user, /* I: plaintext authenticator */
464 const char *password, /* I: plaintext password */
471 int s=-1; /* socket to remote auth host */
472 struct addrinfo *r; /* remote socket address info */
473 char *req; /* request, with user and pw */
474 char *escreq; /* URL-escaped request */
475 char *c; /* scratch pointer */
476 int rc; /* return code scratch area */
477 char postbuf[RESP_LEN]; /* request buffer */
478 int postlen; /* length of post request */
479 char rbuf[RESP_LEN]; /* response read buffer */
480 char hbuf[NI_MAXHOST], pbuf[NI_MAXSERV];
486 assert(user != NULL);
487 assert(password != NULL);
489 /*establish connection to remote */
490 for (r = ai; r; r = r->ai_next) {
491 s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
494 if (connect(s, r->ai_addr, r->ai_addrlen) >= 0)
499 niflags = (NI_NUMERICHOST | NI_NUMERICSERV);
500 #ifdef NI_WITHSCOPEID
501 if (r->ai_family == AF_INET6)
502 niflags |= NI_WITHSCOPEID;
504 if (getnameinfo(r->ai_addr, r->ai_addrlen, hbuf, sizeof(hbuf),
505 pbuf, sizeof(pbuf), niflags) != 0) {
506 strlcpy(hbuf, "unknown", sizeof(hbuf));
507 strlcpy(pbuf, "unknown", sizeof(pbuf));
510 syslog(LOG_WARNING, "auth_httpform: connect %s[%s]/%s: %m",
511 ai->ai_canonname ? ai->ai_canonname : r_host, hbuf, pbuf);
514 if (getnameinfo(ai->ai_addr, ai->ai_addrlen, NULL, 0,
515 pbuf, sizeof(pbuf), NI_NUMERICSERV) != 0)
516 strlcpy(pbuf, "unknown", sizeof(pbuf));
517 syslog(LOG_WARNING, "auth_httpform: couldn't connect to %s/%s",
518 ai->ai_canonname ? ai->ai_canonname : r_host, pbuf);
519 return strdup("NO [ALERT] Couldn't contact remote authentication server");
522 /* CLAIM: we now have a TCP connection to the remote HTTP server */
525 * Install noop signal handlers. These just reinstall the handler
526 * and return so that we take an EINTR during network I/O.
528 (void) signal(SIGALRM, sig_null);
529 (void) signal(SIGPIPE, sig_null);
531 /* build the HTTP request */
532 req = create_post_data(formdata, user, password, realm);
535 syslog(LOG_WARNING, "auth_httpform: create_post_data == NULL");
536 return strdup(RESP_IERROR);
538 escreq = url_escape(req);
539 if (escreq == NULL) {
540 memset(req, 0, strlen(req));
543 syslog(LOG_WARNING, "auth_httpform: url_escape == NULL");
544 return strdup(RESP_IERROR);
547 postlen = snprintf(postbuf, RESP_LEN-1,
548 "POST %s HTTP/1.1" CRLF
550 "User-Agent: saslauthd" CRLF
552 "Content-Type: application/x-www-form-urlencoded" CRLF
553 "Content-Length: %d" TWO_CRLF
555 r_uri, r_host, r_port, strlen(escreq), escreq);
557 if (flags & VERBOSE) {
558 syslog(LOG_DEBUG, "auth_httpform: sending %s %s %s",
559 r_host, r_uri, escreq);
563 alarm(NETWORK_IO_TIMEOUT);
564 rc = tx_rec(s, postbuf, postlen);
568 syslog(LOG_WARNING, "auth_httpform: failed to send request");
569 memset(req, 0, strlen(req));
571 memset(escreq, 0, strlen(escreq));
573 memset(postbuf, 0, postlen);
575 return strdup(RESP_IERROR);
578 /* don't need these any longer */
579 memset(req, 0, strlen(req));
581 memset(escreq, 0, strlen(escreq));
583 memset(postbuf, 0, postlen);
585 /* read and parse the response */
586 alarm(NETWORK_IO_TIMEOUT);
587 rc = read(s, rbuf, sizeof(rbuf));
590 close(s); /* we're done with the remote */
593 syslog(LOG_WARNING, "auth_httpform: read (response): %m");
594 return strdup(RESP_IERROR);
597 if (flags & VERBOSE) {
598 syslog(LOG_DEBUG, "auth_httpform: [%s] %s", user, rbuf);
601 rbuf[rc] = '\0'; /* make sure str-funcs find null */
602 return build_sasl_response(rbuf);
605 /* END FUNCTION: auth_httpform */
607 /* END MODULE: auth_httpform */