--- /dev/null
+/*
+ * mod_auth_ntlm_winbind.c -- Apache auth_ntlm_winbind module
+ *
+ * [Originally autogenerated via ``apxs -n ntlm_winbind -g'']
+ *
+ * Based on:
+ * mod_ntlm.c for Win32 by Tim Costello <tim.costello@bigfoot.com>
+ * pam_smb by Dave Airlie <Dave.Airlie@ul.ie>
+ *
+ * Copyright Andreas Gal <agal@uwsp.edu>, 2000
+ * Copyright Sverre H. Huseby <sverrehu@online.no>, 2000
+ * Copyright Tim Potter <tpot@samba.org>, 2001
+ * Copyright Andrew Bartlett <abartlet@samba.org>, 2004
+ * Apache2 code by Waider <waider@waider.ie>, 2006
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS`` AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES ARE DISCLAIMED.
+ *
+ * This code may be freely distributed, as long the above notices are
+ * reproduced.
+ */
+/* (CGI fork code)
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+
+/*
+ * OVERVIEW:
+ *
+ * The mod_auth_ntlm_winbind module allows authentication and authorisation
+ * over the web against a Windows NT/2000/XP domain controller using the
+ * winbind daemon running on the same machine apache is running on.
+ *
+ * Used only by IE and newer versions of Mozilla, the NTLM over HTTP protocol is completed undocumented
+ * by Microsoft but has been reverse engineered and described at the
+ * following URL:
+ *
+ * http://davenport.sf.net/ntlm.html
+ *
+ * CONFIGURATION:
+ *
+ * The following httpd.conf configuration describes an example
+ * configuration for this module:
+ *
+ * <Directory "/var/www/auth">
+ * AuthName "NTLM Authentication thingy"
+ * NTLMAuth on
+ * NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp"
+ * NTLMBasicAuthoritative on
+ * AuthType NTLM
+ * require valid-user
+ * </Directory>
+ *
+ * or, to enable 'Negotiate' authentication too:
+ *
+ * <Directory "/var/www/auth">
+ * AuthName "NTLM Authentication thingy"
+ * NTLMAuth on
+ * NegotiateAuth on
+ * NTLMAuthHelper "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp"
+ * NegotiateAuthHelper "/usr/bin/ntlm_auth --helper-protocol=gss-spnego"
+ * NTLMBasicAuthoritative on
+ * AuthType NTLM
+ * AuthType Negotiate
+ * require valid-user
+ * </Directory>
+ *
+ * To debug what is going on, add the following line to your httpd.conf
+ * to enable debug messages to be written to the apache error log file:
+ *
+ * LogLevel debug
+ *
+ */
+
+#include "httpd.h"
+#include "http_config.h"
+#include "http_protocol.h"
+#include "http_log.h"
+#include "http_core.h"
+#include "ap_config.h"
+#include "util_script.h" /* for ap_call_exec */
+#include <assert.h>
+
+#ifdef APACHE2
+#include "http_request.h"
+#include "http_connection.h"
+#include "apr_strings.h"
+#include "apr_pools.h"
+#include "apr_tables.h"
+#include "apr_base64.h"
+
+#define RDEBUG( x... ) ap_log_rerror( APLOG_MARK, NTLM_DEBUG, APR_SUCCESS, r, x )
+#define RERROR( c, x... ) ap_log_rerror( APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, c, r, x )
+#define CLEANUP(x) NULL
+#else
+
+/* compat stuff */
+#define apr_pool_t ap_pool
+#define apr_pstrcat(x...) ap_pstrcat(x)
+#define apr_pstrdup(x...) ap_pstrdup(x)
+#define apr_psprintf(x...) ap_psprintf(x)
+#define apr_table_add(x...) ap_table_add(x)
+#define apr_table_get(x...) ap_table_get(x)
+#define apr_table_setn(x...) ap_table_setn(x)
+#define apr_pcalloc(x...) ap_pcalloc(x)
+#define apr_pool_destroy(x...) ap_destroy_pool(x)
+
+#define RDEBUG( x... ) ap_log_rerror( APLOG_MARK, LOG_DEBUG, r, x )
+#define RERROR( c, x... ) ap_log_rerror( APLOG_MARK, NTLM_DEBUG|APLOG_NOERRNO, r, x )
+#define CLEANUP(x) x
+#endif
+
+/* The name of the NTLM authentication scheme. This appears in the
+ 'WWW-Authenticate' header in the initial HTTP request. */
+
+#define NTLM_AUTH_NAME "NTLM"
+#define NEGOTIATE_AUTH_NAME "Negotiate"
+
+/* A structure to hold information about the configuration for the
+ mod_auth_ntlm_winbind apache module. */
+
+typedef struct _ntlm_config_struct {
+ unsigned int ntlm_on;
+ unsigned int negotiate_on;
+ unsigned int ntlm_basic_on;
+ char *ntlm_basic_realm;
+ unsigned int authoritative;
+ char *ntlm_auth_helper;
+ char *negotiate_ntlm_auth_helper;
+ char *ntlm_plaintext_helper;
+} ntlm_config_rec;
+
+/* A structure to hold per-connection information about authentications
+ that are in progress. */
+
+struct _ntlm_auth_helper {
+ int sent_challenge;
+ int helper_pid;
+#ifdef APACHE2
+ apr_proc_t *proc;
+#else
+ BUFF *out_to_helper, *in_from_helper;
+#endif
+ apr_pool_t *pool;
+};
+
+struct _connected_user_authenticated {
+ char *user;
+ char *auth_type;
+ apr_pool_t *pool;
+ int keepalives; /* used to detect redirected auths */
+};
+
+struct _ntlm_child_stuff {
+ request_rec *r;
+ char *argv0;
+};
+
+typedef struct _conn_context {
+ struct _connected_user_authenticated *connected_user_authenticated;
+} ntlm_connection_context_t;
+
+typedef struct _ntlm_context {
+ struct _ntlm_auth_helper *ntlm_auth_helper;
+ struct _ntlm_auth_helper *negotiate_ntlm_auth_helper;
+ struct _ntlm_auth_helper *ntlm_plaintext_helper;
+} ntlm_context_t;
+
+#ifdef APACHE2
+module AP_MODULE_DECLARE_DATA auth_ntlm_winbind_module;
+#else
+module MODULE_VAR_EXPORT auth_ntlm_winbind_module;
+#endif
+
+#define NTLM_DEBUG (APLOG_DEBUG | APLOG_NOERRNO)
+
+/* If we have already authenticated then allow all subsequence accesses.
+ This appears to be what IE and IIS do when talking to each other. I
+ don't think there are any security problems with this, unless someone
+ hijacks the connection, but then MS is susceptible to exactly the same
+ problem. */
+
+/* Extra apache configuration directives defined for this module */
+static const command_rec ntlm_winbind_cmds[] = {
+#ifdef APACHE2
+ /* NTLM authentication commands */
+ AP_INIT_FLAG( "NTLMAuth", ap_set_flag_slot,
+ (void *) APR_OFFSETOF( ntlm_config_rec, ntlm_on ),
+ OR_AUTHCFG,
+ "set to 'on' to activate NTLM authentication" ),
+
+ AP_INIT_FLAG( "NegotiateAuth", ap_set_flag_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, negotiate_on),
+ OR_AUTHCFG,
+ "set to 'on' to activate Negotiate authentication" ),
+
+ AP_INIT_FLAG( "NTLMBasicAuthoritative", ap_set_flag_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, authoritative),
+ OR_AUTHCFG,
+ "set to 'off' to allow access control to be passed along to lower "
+ "modules if the UserID is not known to this module" ),
+ /* ntlm_auth location */
+ AP_INIT_TAKE1( "NTLMAuthHelper", ap_set_string_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, ntlm_auth_helper),
+ OR_AUTHCFG,
+ "location and arguments to the Samba ntlm_auth utility" ),
+
+ AP_INIT_TAKE1( "NegotiateAuthHelper", ap_set_string_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, negotiate_ntlm_auth_helper),
+ OR_AUTHCFG,
+ "location and arguments to the Samba ntlm_auth utility" ),
+
+ AP_INIT_TAKE1( "PlaintextAuthHelper", ap_set_string_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, ntlm_plaintext_helper ),
+ OR_AUTHCFG,
+ "location and arguments to the Samba ntlm_auth utility" ),
+
+ /* Basic Authentication transport for non-IE browsers */
+ AP_INIT_FLAG( "NTLMBasicAuth", ap_set_flag_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, ntlm_basic_on),
+ OR_AUTHCFG,
+ "set to 'on' to allow Basic authentication too" ),
+
+ AP_INIT_TAKE1( "NTLMBasicRealm", ap_set_string_slot,
+ (void *) APR_OFFSETOF(ntlm_config_rec, ntlm_basic_realm),
+ OR_AUTHCFG, "realm to use for Basic authentication" ),
+
+#else
+ /* NTLM authentication commands */
+
+ { "NTLMAuth", ap_set_flag_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, ntlm_on),
+ OR_AUTHCFG, FLAG, "set to 'on' to activate NTLM authentication" },
+
+ { "NegotiateAuth", ap_set_flag_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, negotiate_on),
+ OR_AUTHCFG, FLAG, "set to 'on' to activate Negotiate authentication" },
+
+ { "NTLMBasicAuthoritative", ap_set_flag_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, authoritative),
+ OR_AUTHCFG, FLAG,
+ "set to 'off' to allow access control to be passed along to lower "
+ "modules if the UserID is not known to this module" },
+
+ /* ntlm_auth location */
+
+ { "NTLMAuthHelper", ap_set_string_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, ntlm_auth_helper), OR_AUTHCFG,
+ TAKE1, "location and arguments to the Samba ntlm_auth utility"},
+
+ { "NegotiateAuthHelper", ap_set_string_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, negotiate_ntlm_auth_helper), OR_AUTHCFG,
+ TAKE1, "location and arguments to the Samba ntlm_auth utility"},
+
+ { "PlaintextAuthHelper", ap_set_string_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, ntlm_plaintext_helper), OR_AUTHCFG,
+ TAKE1, "location and arguments to the Samba ntlm_auth utility"},
+
+ /* Basic Authentcation transport for non-IE browsers */
+
+ { "NTLMBasicAuth", ap_set_flag_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, ntlm_basic_on),
+ OR_AUTHCFG, FLAG, "set to 'on' to allow Basic authentication too" },
+
+ { "NTLMBasicRealm", ap_set_string_slot,
+ (void *) XtOffsetOf(ntlm_config_rec, ntlm_basic_realm),
+ OR_AUTHCFG, TAKE1, "realm to use for Basic authentication" },
+#endif
+
+ { NULL }
+};
+
+/* both apache1 and apache2 use this to maintain per-child context */
+static ntlm_context_t global_ntlm_context;
+
+#ifndef APACHE2
+/* apache 1 doesn't seem to have a connection context I can use */
+static ntlm_connection_context_t global_connection_context;
+
+static void cleanup_ntlm_auth_helper(void *ntlm_conn_v)
+{
+ struct _ntlm_auth_helper *ntlm_conn = ntlm_conn_v;
+
+ ap_log_error( APLOG_MARK, NTLM_DEBUG, NULL, "freeing ntlm_helper" );
+
+ ap_bclose(ntlm_conn->out_to_helper);
+ ap_bclose(ntlm_conn->in_from_helper);
+
+ global_ntlm_context.ntlm_auth_helper = NULL;
+}
+
+static void cleanup_negotiate_ntlm_auth_helper(void *ntlm_conn_v)
+{
+ struct _ntlm_auth_helper *ntlm_conn = ntlm_conn_v;
+
+ ap_log_error( APLOG_MARK, NTLM_DEBUG, NULL, "freeing negotiate_helper" );
+
+ ap_bclose(ntlm_conn->out_to_helper);
+ ap_bclose(ntlm_conn->in_from_helper);
+
+ global_ntlm_context.negotiate_ntlm_auth_helper = NULL;
+}
+
+static void cleanup_ntlm_plaintext_helper( void *ntlm_conn_v ) {
+ struct _ntlm_auth_helper *ntlm_conn = ntlm_conn_v;
+
+ ap_log_error( APLOG_MARK, NTLM_DEBUG, NULL, "freeing plaintext_helper" );
+
+ ap_bclose( ntlm_conn->out_to_helper );
+ ap_bclose( ntlm_conn->in_from_helper );
+
+ global_ntlm_context.ntlm_plaintext_helper = NULL;
+}
+
+/* Dispose of a connected user */
+
+static void cleanup_connected_user_authenticated(void *unused)
+{
+ ap_log_error( APLOG_MARK, NTLM_DEBUG, NULL, "freeing user" );
+ global_connection_context.connected_user_authenticated = NULL;
+}
+#endif
+
+/* helper function to pull out the context data */
+static ntlm_connection_context_t *get_connection_context( struct conn_rec *connection ) {
+ ntlm_connection_context_t *retval = NULL;
+
+#ifdef APACHE2
+ retval = (ntlm_connection_context_t *)ap_get_module_config( connection->conn_config,
+ &auth_ntlm_winbind_module );
+#else
+ retval = &global_connection_context;
+#endif
+ return retval;
+}
+
+/* Authorisation has failed - we set some headers so the client can
+ get the hint and prompt for a password from the user. */
+
+static int
+note_auth_failure(request_rec * r, const char *negotiate_auth_line)
+{
+ ntlm_config_rec *crec
+ = (ntlm_config_rec *) ap_get_module_config(r->per_dir_config,
+ &auth_ntlm_winbind_module);
+ char *line;
+ ntlm_connection_context_t *ctxt = get_connection_context( r->connection );
+
+ /* MSIE will simply reply to the first, not strongest, protocol listed */
+ if (crec->negotiate_on) {
+ line = apr_pstrcat(r->pool, NEGOTIATE_AUTH_NAME, " ",
+ negotiate_auth_line, NULL);
+ apr_table_add(r->err_headers_out,
+ r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+ line);
+ }
+
+ /* Set header for NTLM authentication */
+
+ if (crec->ntlm_on) {
+ apr_table_add(r->err_headers_out,
+ r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+ NTLM_AUTH_NAME);
+ }
+
+ /* Set header for basic authentication so client can use this if
+ supported. */
+
+ if (crec->ntlm_basic_on) {
+ line = apr_pstrcat(r->pool,
+ "Basic realm=\"", crec->ntlm_basic_realm, "\"",
+ NULL);
+ apr_table_add(r->err_headers_out,
+ r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+ line);
+ }
+
+ if ( ctxt->connected_user_authenticated &&
+ ctxt->connected_user_authenticated->pool ) {
+ apr_pool_destroy( ctxt->connected_user_authenticated->pool );
+ }
+
+ return HTTP_UNAUTHORIZED;
+}
+
+
+const char *
+get_auth_header(request_rec * r, ntlm_config_rec * crec, const char *auth_scheme)
+{
+ const char *auth_line = apr_table_get(r->headers_in,
+ r->proxyreq ? "Proxy-Authorization"
+ : "Authorization");
+
+ if (!auth_line) {
+ RERROR( APR_EINIT, "no auth line present" );
+ return NULL;
+ }
+ if (strcmp(ap_getword_white(r->pool, &auth_line), auth_scheme)) {
+ RERROR( APR_EINIT, "%s auth name not present", auth_scheme );
+ return NULL;
+ }
+ return auth_line;
+}
+
+#ifndef APACHE2
+static int helper_child(void *child_stuff, child_info *pinfo)
+{
+ struct _ntlm_child_stuff *cld = (struct _ntlm_child_stuff *) child_stuff;
+ request_rec *r = cld->r;
+ char *argv0 = cld->argv0;
+ int child_pid;
+
+ RAISE_SIGSTOP(CGI_CHILD);
+
+ /* Transmute outselves into the helper
+ */
+
+ ap_cleanup_for_exec();
+
+ child_pid = ap_call_exec(r, pinfo, argv0, NULL, 1);
+
+ /* Uh oh. Still here. Where's the kaboom? There was supposed to be an
+ * EARTH-shattering kaboom!
+ *
+ * Oh, well. Muddle through as best we can...
+ *
+ * Note that only stderr is available at this point, so don't pass in
+ * a server to aplog_error.
+ */
+
+ RERROR( errno, "exec of %s failed", r->filename);
+ exit(0);
+ /* NOT REACHED */
+ return (0);
+}
+#endif
+
+
+static int
+send_auth_reply(request_rec * r, const char *auth_scheme, const char *reply)
+{
+ RDEBUG( "sending back %s", reply );
+ /* Read negotiate from ntlm_auth */
+
+ apr_table_setn(r->err_headers_out,
+ r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+ apr_psprintf(r->pool, "%s %s", auth_scheme, reply));
+
+ /* This is to make sure that when receiving later messages
+ * that the r->connection is still alive after sending the
+ * nonce to the client. This code is written, and the problem
+ * was pointed out by Michael Cai.
+ */
+ if(r->connection->keepalives >= r->server->keep_alive_max)
+ {
+ RDEBUG("Decrement the connection request count to keep it alive");
+ r->connection->keepalives -= 1;
+ }
+
+ return HTTP_UNAUTHORIZED;
+}
+
+/* get the current request's auth helper or fork one */
+static struct _ntlm_auth_helper *get_auth_helper( request_rec *r, struct _ntlm_auth_helper *auth_helper, char *cmd, void (*cleanup)(void *)) {
+#ifdef APACHE2
+ apr_procattr_t *attr;
+#endif
+
+ if ( auth_helper == NULL ) {
+ struct _ntlm_child_stuff cld;
+ apr_pool_t *pool;
+#ifdef APACHE2
+ char **argv_out;
+ apr_pool_create_ex( &pool, NULL, NULL, NULL ); /* xxx return code */
+#else
+ pool = ap_make_sub_pool( NULL );
+#endif
+ auth_helper = apr_pcalloc( pool, sizeof( struct _ntlm_auth_helper ));
+ auth_helper->pool = pool;
+ auth_helper->helper_pid = 0;
+
+#ifdef APACHE2
+ apr_tokenize_to_argv( cmd, &argv_out, pool );
+#else
+ ap_register_cleanup( pool, auth_helper, cleanup, ap_null_cleanup );
+#endif
+ cld.argv0 = cmd;
+ cld.r = r;
+
+#ifdef APACHE2
+ apr_procattr_create( &attr, pool );
+ apr_procattr_io_set( attr, APR_FULL_BLOCK, APR_FULL_BLOCK, APR_NO_PIPE );
+ apr_procattr_error_check_set( attr, 1 );
+ auth_helper->proc = (apr_proc_t *)apr_pcalloc(pool, sizeof(apr_proc_t)) ;
+ if ( apr_proc_create( auth_helper->proc, argv_out[0], (const char * const *)argv_out, NULL, attr, pool ) != APR_SUCCESS ) {
+ RERROR( errno, "couldn't spawn child ntlm helper process: %s", argv_out[0]);
+ return NULL;
+ }
+ auth_helper->helper_pid = auth_helper->proc->pid;
+#else
+ auth_helper->helper_pid = ap_bspawn_child(pool, helper_child,
+ (void *) &cld, just_wait,
+ &auth_helper->out_to_helper,
+ &auth_helper->in_from_helper,
+ NULL);
+
+ if (auth_helper->helper_pid == -1) {
+ RERROR( errno, "couldn't spawn child ntlm helper process: %s", cld.argv0);
+ return NULL;
+ }
+#endif
+
+ RDEBUG( "Launched ntlm_helper, pid %d", auth_helper->helper_pid );
+ } else {
+ RDEBUG( "Using existing auth helper %d", auth_helper->helper_pid );
+ }
+
+ return auth_helper;
+}
+
+/* Call winbind to authenticate a (user, password)
+ pair */
+static int winbind_authenticate_plaintext( request_rec *r, ntlm_config_rec * crec, char *user, char *pass)
+{
+ ntlm_connection_context_t *ctxt = get_connection_context( r->connection );
+ char *newline;
+ char args_to_helper[HUGE_STRING_LEN];
+ char args_from_helper[HUGE_STRING_LEN];
+ unsigned int bytes_written;
+ int bytes_read;
+
+ if (( global_ntlm_context.ntlm_plaintext_helper = get_auth_helper( r, global_ntlm_context.ntlm_plaintext_helper, crec->ntlm_plaintext_helper, CLEANUP(cleanup_ntlm_plaintext_helper))) == NULL ) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if ( ctxt->connected_user_authenticated == NULL ) {
+ apr_pool_t *pool;
+
+ RDEBUG( "creating auth user" );
+
+#ifdef APACHE2
+ apr_pool_create_ex( &pool, r->connection->pool, NULL, NULL );
+#else
+ pool = ap_make_sub_pool(r->connection->pool);
+#endif
+
+ ctxt->connected_user_authenticated =
+ apr_pcalloc(pool, sizeof( struct _connected_user_authenticated));
+
+#ifndef APACHE2
+ ap_register_cleanup(pool,ctxt->connected_user_authenticated,
+ cleanup_connected_user_authenticated, ap_null_cleanup );
+#endif
+
+ ctxt->connected_user_authenticated->pool = pool;
+ ctxt->connected_user_authenticated->user = NULL;
+ ctxt->connected_user_authenticated->auth_type = NULL;
+ } else {
+ /* what, we're already authenticated? */
+ return OK;
+ }
+
+ snprintf( args_to_helper, HUGE_STRING_LEN, "%s %s\n", user, pass );
+
+#ifdef APACHE2
+ bytes_written = strlen( args_to_helper );
+ apr_file_write( global_ntlm_context.ntlm_plaintext_helper->proc->in, args_to_helper, &bytes_written );
+#else
+ bytes_written = ap_bwrite( global_ntlm_context.ntlm_plaintext_helper->out_to_helper, args_to_helper, strlen( args_to_helper ));
+#endif
+
+ if ( bytes_written < strlen( args_to_helper )) {
+ RDEBUG( "failed to write user/pass to helper - wrote %d bytes", bytes_written );
+ apr_pool_destroy( global_ntlm_context.ntlm_plaintext_helper->pool );
+ apr_pool_destroy( ctxt->connected_user_authenticated->pool );
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#ifdef APACHE2
+ apr_file_flush( global_ntlm_context.ntlm_plaintext_helper->proc->in );
+ if ( apr_file_gets( args_from_helper, HUGE_STRING_LEN, global_ntlm_context.ntlm_plaintext_helper->proc->out ) == APR_SUCCESS ) {
+ bytes_read = strlen( args_from_helper );
+ } else {
+ bytes_read = 0;
+ }
+#else
+ ap_bflush( global_ntlm_context.ntlm_plaintext_helper->out_to_helper );
+ bytes_read = ap_bgets( args_from_helper, HUGE_STRING_LEN, global_ntlm_context.ntlm_plaintext_helper->in_from_helper );
+#endif
+ if ( bytes_read == 0 ) {
+ RERROR( errno, "early EOF from helper" );
+ apr_pool_destroy(global_ntlm_context.ntlm_plaintext_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ } else if (bytes_read == -1) {
+ RERROR( errno, "helper died!" );
+ apr_pool_destroy(global_ntlm_context.ntlm_plaintext_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ } else if (bytes_read < 2) {
+ RERROR( errno, "failed to read NTLMSSP string from helper - only got %d bytes", bytes_read);
+ apr_pool_destroy(global_ntlm_context.ntlm_plaintext_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ newline = strchr(args_from_helper, '\n');
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ RDEBUG( "got response: %s", args_from_helper );
+
+ if ( strncmp( args_from_helper, "OK", 2 ) == 0 ) {
+ RDEBUG( "authentication succeeded!" );
+ ctxt->connected_user_authenticated->user = apr_pstrdup(ctxt->connected_user_authenticated->pool, user);
+ ctxt->connected_user_authenticated->keepalives = r->connection->keepalives;
+#ifdef APACHE2
+ r->user = ctxt->connected_user_authenticated->user;
+ r->ap_auth_type = apr_pstrdup(r->connection->pool, "Basic");
+ /* disconnect the child process */
+ /* apr_proc_kill( global_ntlm_context.ntlm_plaintext_helper->proc, 9 );
+ apr_proc_wait( global_ntlm_context.ntlm_plaintext_helper->proc, &exit, &why, APR_WAIT );*/
+#else
+ r->connection->user = ctxt->connected_user_authenticated->user;
+ r->connection->ap_auth_type = ap_pstrdup(r->connection->pool, "Basic");
+#endif
+ RDEBUG( "authenticated %s", ctxt->connected_user_authenticated->user );
+ return OK;
+ } else {
+ if ( strncmp( args_from_helper, "ERR", 3 ) == 0 ) {
+ RDEBUG( "username/password incorrect" );
+ return note_auth_failure( r, NULL );
+ } else {
+ RDEBUG( "unknown helper response %s", args_from_helper );
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+}
+
+/* Process a message received from the client. This can be a request for a
+ challenge (type 1) or a request to authenticate a challenge/response
+ (type 3). */
+
+static int
+process_msg(request_rec * r, ntlm_config_rec * crec, const char *auth_type)
+{
+ const char *client_msg;
+ const char *message_type;
+ char *childarg;
+ char *newline;
+ char args_to_helper[HUGE_STRING_LEN];
+ char args_from_helper[HUGE_STRING_LEN];
+ ntlm_connection_context_t *ctxt = get_connection_context( r->connection );
+ unsigned int bytes_written;
+ int bytes_read;
+ struct _ntlm_auth_helper *auth_helper;
+
+ /* If this is the first request with this connection, then create
+ * a ntlm_auth_helper entry for it. It will be cleaned up when the
+ * connection is dropped */
+
+ if (strcmp(auth_type, NEGOTIATE_AUTH_NAME) == 0) {
+ auth_helper = get_auth_helper( r, global_ntlm_context.negotiate_ntlm_auth_helper, crec->negotiate_ntlm_auth_helper, CLEANUP(cleanup_negotiate_ntlm_auth_helper));
+ global_ntlm_context.negotiate_ntlm_auth_helper = auth_helper;
+ } else if (strcmp(auth_type, NTLM_AUTH_NAME) == 0) {
+ auth_helper = get_auth_helper( r, global_ntlm_context.ntlm_auth_helper, crec->ntlm_auth_helper, CLEANUP(cleanup_ntlm_auth_helper));
+ global_ntlm_context.ntlm_auth_helper = auth_helper;
+ } else {
+ auth_helper = NULL;
+ }
+
+ if ( auth_helper == NULL ) {
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if ( ctxt->connected_user_authenticated == NULL ) {
+ apr_pool_t *pool;
+
+ RDEBUG( "creating auth user" );
+
+#ifdef APACHE2
+ apr_pool_create_ex( &pool, r->connection->pool, NULL, NULL );
+#else
+ pool = ap_make_sub_pool(r->connection->pool);
+#endif
+
+ ctxt->connected_user_authenticated =
+ apr_pcalloc(pool, sizeof( struct _connected_user_authenticated));
+
+#ifndef APACHE2
+ ap_register_cleanup(pool,ctxt->connected_user_authenticated,
+ cleanup_connected_user_authenticated, ap_null_cleanup );
+#endif
+
+ ctxt->connected_user_authenticated->pool = pool;
+ ctxt->connected_user_authenticated->user = NULL;
+ ctxt->connected_user_authenticated->auth_type = NULL;
+
+ message_type = "YR";
+ } else {
+ message_type = "KK";
+ }
+
+ /* Decode the information the WWW-Authenticate header */
+ if ((client_msg = get_auth_header(r, crec, auth_type)) == NULL) {
+ RDEBUG( "client did not return NTLM authentication header");
+ return note_auth_failure(r, NULL);
+ }
+
+ /* Pipe to helper */
+ snprintf(args_to_helper, HUGE_STRING_LEN, "%s %s\n", message_type, client_msg);
+
+#ifdef APACHE2
+ bytes_written = strlen( args_to_helper );
+ apr_file_write( auth_helper->proc->in, args_to_helper, &bytes_written );
+#else
+ bytes_written = ap_bwrite(auth_helper->out_to_helper, args_to_helper, strlen(args_to_helper));
+#endif
+ if (bytes_written < strlen(args_to_helper)) {
+ RDEBUG("failed to write NTLMSSP string to helper - wrote %d bytes", bytes_written);
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+#ifdef APACHE2
+ apr_file_flush( auth_helper->proc->in );
+
+ RDEBUG( "parsing reply from helper to %s", args_to_helper );
+
+ if ( apr_file_gets(args_from_helper, HUGE_STRING_LEN, auth_helper->proc->out ) == APR_SUCCESS ) {
+ bytes_read = strlen( args_from_helper );
+ } else {
+ bytes_read = 0;
+ }
+#else
+ ap_bflush(auth_helper->out_to_helper);
+
+ bytes_read = ap_bgets(args_from_helper, HUGE_STRING_LEN, auth_helper->in_from_helper);
+#endif
+
+ if (bytes_read == 0) {
+ RERROR( errno, "early EOF from helper" );
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ } else if (bytes_read == -1) {
+ RERROR( errno, "helper died!");
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ } else if (bytes_read < 2) {
+ RERROR( errno, "failed to read NTLMSSP string from helper - only got %d bytes", bytes_read );
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ newline = strchr(args_from_helper, '\n');
+ if (newline != NULL) {
+ *newline = '\0';
+ }
+
+ RDEBUG( "got response: %s", args_from_helper );
+
+ /* inspect message type */
+
+ childarg = strchr(args_from_helper, ' ');
+ if (childarg == NULL) {
+ RERROR( errno, "failed to parse response from helper");
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ childarg++;
+
+ if (strcasecmp(auth_type, NTLM_AUTH_NAME) == 0) {
+ /* if TT, send to client */
+
+ if (strncmp(args_from_helper, "TT ", 3) == 0) {
+ return send_auth_reply(r, auth_type, childarg);
+ }
+
+ /* if NA, not authenticated */
+
+ if (strncmp(args_from_helper, "NA ", 3) == 0) {
+ RDEBUG("user not authenticated: %s", childarg);
+ return note_auth_failure(r, NULL);
+ }
+
+ /* if AF, record username */
+ if (strncmp(args_from_helper, "AF ", 3) == 0) {
+ ctxt->connected_user_authenticated->user =
+ apr_pstrdup(ctxt->connected_user_authenticated->pool,
+ childarg);
+ ctxt->connected_user_authenticated->keepalives =
+ r->connection->keepalives;
+#ifdef APACHE2
+ r->user = ctxt->connected_user_authenticated->user;
+ r->ap_auth_type = apr_pstrdup(r->connection->pool, auth_type);
+ /* disconnect the child process */
+ /* apr_proc_kill( auth_helper->proc, 9 );
+ apr_proc_wait( auth_helper->proc, &exit, &why, APR_WAIT );*/
+#else
+ r->connection->user = ctxt->connected_user_authenticated->user;
+ r->connection->ap_auth_type = ap_pstrdup(r->connection->pool, auth_type);
+#endif
+ RDEBUG( "authenticated %s",
+ ctxt->connected_user_authenticated->user );
+ return OK;
+ }
+ } else if (strcasecmp(auth_type, NEGOTIATE_AUTH_NAME) == 0) {
+
+ /* The child's reply contains 3 parts:
+ - The code: TT, AF or NA
+ - The blob to send to the client, coded in base64
+ - The argument:
+ For TT it's a dummy '*'
+ For AF it's domain\\user
+ For NA it's the NT error code
+ */
+
+ char *childarg3 = strchr(childarg, ' ');
+ if (childarg3 == NULL) {
+ RERROR( errno, "failed to parse response from helper");
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ *childarg3 = '\0';
+ childarg3++;
+
+ /* if TT, send to client */
+
+ if (strncmp(args_from_helper, "TT ", 3) == 0) {
+ return send_auth_reply(r, auth_type, childarg);
+ }
+
+ /* if NA, not authenticated */
+
+ if (strncmp(args_from_helper, "NA ", 3) == 0) {
+ RDEBUG("user not authenticated: %s", childarg3);
+ return note_auth_failure(r, childarg);
+ }
+
+ /* if AF, record username */
+ if (strncmp(args_from_helper, "AF ", 3) == 0) {
+ ctxt->connected_user_authenticated->user =
+ apr_pstrdup(ctxt->connected_user_authenticated->pool,
+ childarg3);
+#ifdef APACHE2
+ r->user = ctxt->connected_user_authenticated->user;
+ ctxt->connected_user_authenticated->auth_type =
+ apr_pstrdup(r->connection->pool, auth_type);
+ r->ap_auth_type = ctxt->connected_user_authenticated->auth_type;
+#else
+ r->connection->user = ctxt->connected_user_authenticated->user;
+ ctxt->connected_user_authenticated->auth_type = ap_pstrdup(r->connection->pool, auth_type);
+ r->connection->ap_auth_type = ctxt->connected_user_authenticated->auth_type;
+#endif
+
+ if (!strcmp("*", childarg)) {
+ /* Send last leg (possible mutual authentication token) */
+ apr_table_setn(r->headers_out,
+ r->proxyreq ? "Proxy-Authenticate" : "WWW-Authenticate",
+ apr_psprintf(r->pool, "%s %s", auth_type, childarg));
+ }
+
+ RDEBUG( "wow, we're all happy here" );
+
+ return OK;
+ }
+ }
+
+ /* Helper failed */
+
+ /* if BH, helper is busted */
+
+ if (strncmp(args_from_helper, "BH ", 3) == 0) {
+ RERROR( APR_EGENERAL, "ntlm_auth reports Broken Helper: %s", args_from_helper);
+ } else {
+ RERROR( APR_EGENERAL, "could not parse %s helper callback: %s", auth_type, args_from_helper);
+ }
+
+ apr_pool_destroy(auth_helper->pool);
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+
+ return HTTP_INTERNAL_SERVER_ERROR;
+}
+
+/* Called to create a configuration structure for each <Directory> section
+ that uses the ntlm auth module. */
+
+static void *
+ntlm_winbind_dir_config(apr_pool_t * p, char *d)
+{
+ ntlm_config_rec *crec
+ = (ntlm_config_rec *) apr_pcalloc(p, sizeof(ntlm_config_rec));
+
+ /* Set the defaults. */
+
+ crec->authoritative = 1;
+ crec->ntlm_on = 0;
+ crec->negotiate_on = 0;
+ crec->ntlm_basic_on = 0;
+ crec->ntlm_basic_realm = "REALM";
+ crec->ntlm_auth_helper = "ntlm_auth --helper-protocol=squid-2.5-ntlmssp";
+ crec->negotiate_ntlm_auth_helper = "ntlm_auth --helper-protocol=gss-spnego";
+ crec->ntlm_plaintext_helper = "ntlm_auth --helper-protocol=squid-2.5-basic";
+
+ return crec;
+}
+
+/* Authenticate a user using basic authentication */
+static int
+authenticate_basic_user(request_rec * r, ntlm_config_rec * crec,
+ const char *auth_line_after_Basic)
+{
+ char *sent_user = NULL, *sent_pw;
+ int result = HTTP_UNAUTHORIZED;
+
+ while (*auth_line_after_Basic == ' ' || *auth_line_after_Basic == '\t')
+ auth_line_after_Basic++;
+
+#ifdef APACHE2
+ sent_user = apr_pcalloc( r->pool, apr_base64_decode_len( auth_line_after_Basic ));
+ apr_base64_decode( sent_user, auth_line_after_Basic );
+#else
+ sent_user = ap_pbase64decode(r->pool, auth_line_after_Basic);
+#endif
+
+ if (sent_user != NULL) {
+ char *s;
+
+ if ((sent_pw = strchr(sent_user, ':')) != NULL) {
+ *sent_pw = '\0';
+ ++sent_pw;
+ } else
+ sent_pw = "";
+ if ((s = strchr(sent_user, '\\')) != NULL
+ || (s = strchr(sent_user, '/')) != NULL) {
+
+ /* Authenticate this user as a domain user */
+
+ result = winbind_authenticate_plaintext( r, crec, sent_user, sent_pw);
+
+ RDEBUG("authenticate domain user %s: %s", sent_user,
+ (result == OK) ? "OK" : "FAILED");
+ goto done;
+ }
+ } else {
+ RDEBUG("can't extract user from %s", auth_line_after_Basic );
+ sent_user = sent_pw = "";
+ }
+
+ RDEBUG("authenticate local user %s: %s", sent_user,
+ (result == OK) ? "OK" : "FAILED");
+
+ done:
+ return result;
+}
+
+/* Check the user id from a http request */
+static int check_user_id(request_rec * r) {
+ ntlm_config_rec *crec =
+ (ntlm_config_rec *) ap_get_module_config(r->per_dir_config,
+ &auth_ntlm_winbind_module);
+ ntlm_connection_context_t *ctxt = get_connection_context( r->connection );
+ const char *auth_line = apr_table_get(r->headers_in,
+ r->proxyreq ? "Proxy-Authorization"
+ : "Authorization");
+ const char *auth_line2;
+
+ /* Trust the authentication on an existing connection */
+ if (ctxt->connected_user_authenticated && ctxt->connected_user_authenticated->user) {
+ /* internal redirects cause this to get called more than once
+ per request on Apache 1.x. This compensates by checking if
+ the connection is the same as the one we authed against */
+ if ( !auth_line || ( ctxt->connected_user_authenticated->keepalives == r->connection->keepalives )) {
+ /* silently accept login with same credentials */
+ RDEBUG( "retaining user %s",
+ ctxt->connected_user_authenticated->user );
+ RDEBUG( "keepalives: %d", r->connection->keepalives );
+#ifdef APACHE2
+ r->user = ctxt->connected_user_authenticated->user;
+ r->ap_auth_type = ctxt->connected_user_authenticated->auth_type;
+#else
+ r->connection->user = ctxt->connected_user_authenticated->user;
+ r->connection->ap_auth_type = ctxt->connected_user_authenticated->auth_type;
+#endif
+ return OK;
+ } else {
+ RDEBUG( "reauth" );
+ /* client wishes to re-authenticate this TCP socket */
+ if ( ctxt->connected_user_authenticated->pool ) {
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+ ctxt->connected_user_authenticated = NULL;
+ }
+ }
+ }
+
+ /* No authentication line given. Return a 401 and a WWW-Authenticate
+ header so authentication can commence. */
+
+ if (!auth_line) {
+ note_auth_failure(r, NULL);
+ return HTTP_UNAUTHORIZED;
+ }
+
+ /* If basic authentication is requested and enabled, try to
+ authenticate the user with basic */
+
+ auth_line2 = auth_line;
+ if (crec->ntlm_basic_on
+ && strcasecmp(ap_getword(r->pool, &auth_line2, ' '), "Basic") == 0) {
+ RDEBUG( "trying basic auth" );
+ return authenticate_basic_user(r, crec, &auth_line[5]); /* seems clunky */
+ }
+
+ /* Process a 'Negotiate' SPNEGO over http message */
+ auth_line2 = auth_line;
+ if (strcasecmp(ap_getword(r->pool, &auth_line2, ' '), NEGOTIATE_AUTH_NAME) == 0) {
+ if (!crec->negotiate_on) {
+ RDEBUG("Negotiate authentication is not enabled");
+ return DECLINED;
+ } else {
+ return process_msg(r, crec, NEGOTIATE_AUTH_NAME);
+ }
+ }
+
+ /* Process a NTLM over http message */
+
+ auth_line2 = auth_line;
+ if (strcasecmp(ap_getword(r->pool, &auth_line2, ' '), NTLM_AUTH_NAME) == 0) {
+ if (!crec->ntlm_on) {
+ RDEBUG("NTLM authentication is not enabled");
+ return DECLINED;
+ } else {
+ RDEBUG( "doing ntlm auth dance" );
+ return process_msg(r, crec, NTLM_AUTH_NAME);
+ }
+ }
+
+ if (ctxt->connected_user_authenticated && ctxt->connected_user_authenticated->pool ) {
+ apr_pool_destroy(ctxt->connected_user_authenticated->pool);
+ }
+
+ RDEBUG( "declined" );
+
+ return DECLINED;
+}
+
+/* Dispatch list for API hooks */
+#ifdef APACHE2
+static int ntlm_pre_conn(conn_rec *c, void *csd) {
+ ntlm_connection_context_t *ctxt = apr_pcalloc(c->pool, sizeof(ntlm_connection_context_t));
+
+ ap_set_module_config(c->conn_config, &auth_ntlm_winbind_module, ctxt);
+
+ return OK;
+}
+
+
+static void register_hooks(apr_pool_t *pool)
+{
+ ap_hook_pre_connection(ntlm_pre_conn,NULL,NULL,APR_HOOK_MIDDLE);
+ ap_hook_check_user_id(check_user_id,NULL,NULL,APR_HOOK_MIDDLE);
+};
+
+module AP_MODULE_DECLARE_DATA auth_ntlm_winbind_module = {
+ STANDARD20_MODULE_STUFF,
+ ntlm_winbind_dir_config, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ NULL, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ ntlm_winbind_cmds, /* table of config file commands */
+ register_hooks, /* register hooks */
+};
+#else
+module MODULE_VAR_EXPORT auth_ntlm_winbind_module = {
+ STANDARD_MODULE_STUFF,
+ NULL, /* module initializer */
+ ntlm_winbind_dir_config, /* create per-dir config structures */
+ NULL, /* merge per-dir config structures */
+ NULL, /* create per-server config structures */
+ NULL, /* merge per-server config structures */
+ ntlm_winbind_cmds, /* table of config file commands */
+ NULL, /* [#8] MIME-typed-dispatched handlers */
+ NULL, /* [#1] URI to filename translation */
+ check_user_id, /* [#4] validate user id from request */
+ NULL, /* [#5] check if the user is ok _here_ */
+ NULL, /* [#3] check access by host address */
+ NULL, /* [#6] determine MIME type */
+ NULL, /* [#7] pre-run fixups */
+ NULL, /* [#9] log a transaction */
+ NULL, /* [#2] header parser */
+ NULL, /* child_init */
+ NULL, /* child_exit */
+ NULL /* [#0] post read-request */
+#ifdef EAPI
+ ,NULL, /* EAPI: add_module */
+ NULL, /* EAPI: remove_module */
+ NULL, /* EAPI: rewrite_command */
+ NULL /* EAPI: new_connection */
+#endif
+};
+#endif
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */