Export error environment variables.
[mod_auth_kerb.git] / mod_auth_gssweb.c
index 22aa0df..5b1a0d5 100644 (file)
@@ -37,7 +37,7 @@
  * NOTE: Portions of the code in this file were derived from example
  * code distributed under the Apache 2.0 license:
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * This module implements the Apache server side of the GSSWeb
  * authentiction type which allows Moonshot to be used for
  * authentication in web applications.  The module consists of two
@@ -48,7 +48,7 @@
  * response content.
  *
  * This module uses a simple protocol between the client and server
- * to exchange GSS tokens and nonce information.  The protocol is 
+ * to exchange GSS tokens and nonce information.  The protocol is
  * described in the protocol.txt file included with module source.
  */
 
@@ -68,11 +68,11 @@ static const command_rec gssweb_config_cmds[] = {
 
     { NULL }
 };
-  
+
 #define DEFAULT_ENCTYPE                "application/x-www-form-urlencoded"
 #define GSS_MAX_TOKEN_SIZE     4096    //TBD -- check this value
 
-/* gssweb_read_req() -- reads the request data into a buffer 
+/* gssweb_read_req() -- reads the request data into a buffer
  */
 static int gssweb_read_req(request_rec *r, const char **rbuf, apr_off_t *size)
 {
@@ -82,7 +82,7 @@ static int gssweb_read_req(request_rec *r, const char **rbuf, apr_off_t *size)
     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_get_post_data: Failed to set up client block");
     return(rc);
   }
-  
+
   if(ap_should_client_block(r)) {
     char          argsbuffer[HUGE_STRING_LEN];
     apr_off_t     rsize, len_read, rpos = 0;
@@ -108,7 +108,7 @@ static int gssweb_read_req(request_rec *r, const char **rbuf, apr_off_t *size)
 /* gssweb_get_post_data() -- Gets the token and nonce from the request
  * data.
  */
-static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *input_token)
+static int gssweb_get_post_data(request_rec *r, unsigned int *nonce, gss_buffer_desc *input_token)
 {
   const char *data;
   apr_off_t datalen;
@@ -121,14 +121,14 @@ static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *inp
   input_token->value = NULL;
 
     gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_get_post_data: Entering function");
+
   if(r->method_number != M_POST) {
     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_get_post_data: Request data is not a POST, declining.");
     return DECLINED;
   }
 
   type = apr_table_get(r->headers_in, "Content-Type");
-  if(strcasecmp(type, DEFAULT_ENCTYPE) != 0) {
+  if(strncasecmp(type, DEFAULT_ENCTYPE, strlen(DEFAULT_ENCTYPE)) != 0) {
     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_get_post_data: Unexpected content type, declining.");
     return DECLINED;
   }
@@ -137,8 +137,8 @@ static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *inp
     gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_get_post_data: Data read error, rc = %d", rc);
     return rc;
   }
-  
-  while(*data && (val = ap_getword(r->pool, &data, '&'))) { 
+
+  while(*data && (val = ap_getword(r->pool, &data, '&'))) {
     key = ap_getword(r->pool, &val, '=');
     ap_unescape_url((char*)key);
     ap_unescape_url((char*)val);
@@ -168,7 +168,7 @@ static int gssweb_get_post_data(request_rec *r, int *nonce, gss_buffer_desc *inp
 }
 
 /* gssweb_authenticate_filter() -- Output filter for gssweb authentication.
- * Wraps original response in JSON -- adding JSON to the beginning of the 
+ * Wraps original response in JSON -- adding JSON to the beginning of the
  * response, escapes double quotes in the original response, and adds JSON
  * to the end of the response.  Handles responses that involve more than
  * one filter call by maintaining state until an EOS bucket is received.
@@ -195,9 +195,9 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
 
   gss_log(APLOG_MARK, APLOG_DEBUG, 0, f->r, "Entering GSSWeb filter");
 
-  /* Get the context from the request.  If the context is NULL or 
-   * there is no outstanding request (no nonce set), just forward 
-   * all of the buckets as-is, because the client isn't gssweb 
+  /* Get the context from the request.  If the context is NULL or
+   * there is no outstanding request (no nonce set), just forward
+   * all of the buckets as-is, because the client isn't gssweb
    */
   if ((NULL == (conn_ctx = gss_retrieve_conn_ctx(r))) ||
       (0 == conn_ctx->nonce)) {
@@ -205,7 +205,7 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
         bkt_in != APR_BRIGADE_SENTINEL(brig_in);
         bkt_in = APR_BUCKET_NEXT(bkt_in))
       {
-       if (NULL == (brig_out = apr_brigade_create(r->pool, c->bucket_alloc))) {      
+       if (NULL == (brig_out = apr_brigade_create(r->pool, c->bucket_alloc))) {
          apr_brigade_cleanup(brig_in);
          return HTTP_INTERNAL_SERVER_ERROR;
        }
@@ -218,6 +218,13 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
     return OK;
   }
 
+  c_type = apr_table_get(r->headers_in, "Content-Type");
+  c_len = apr_table_get(r->headers_in, "Content-Length");
+  /* clear content-length and MD5 checksum */
+  apr_table_unset(r->headers_out, "Content-Length");
+  apr_table_unset(r->headers_out, "Content-MD5");
+  gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: Received Content-Type: %s, Content-Length: %d", c_type, c_len);
+
   /* If this is the first call for a response, send opening JSON block */
   if (GSS_FILT_NEW == conn_ctx->filter_stat) {
     gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: First filter call for response");
@@ -246,11 +253,11 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
     }
 
     /* Send opening JSON block */
-    snprintf((char *)data, len+1024, 
-            "{\"gssweb\": {\n\"token\": \"%s\",\n\"nonce\": \"%d\"},\n\"application\": {\n\"data\": \"", 
+    snprintf((char *)data, len+1024,
+            "{\"gssweb\": {\n\"token\": \"%s\",\n\"nonce\": \"%d\"},\n\"application\": {\n\"data\": \"",
             stoken, conn_ctx->nonce);
     gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: Sending (%d bytes): %s", strlen(data), data);
-    
+
     bkt_out = apr_bucket_heap_create(data, strlen(data), apr_bucket_free,
                                     c->bucket_alloc);
     APR_BRIGADE_INSERT_TAIL(brig_out, bkt_out);
@@ -279,7 +286,7 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
       if(APR_BUCKET_IS_EOS(bkt_in))
        {
          /* create and add the JSON closing block */
-         
+
          if (NULL == (data = apr_bucket_alloc(1024, c->bucket_alloc))) {
              gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_authenticate_filter: Unable to allocate space for closing JSON block");
              apr_brigade_cleanup(brig_in);
@@ -287,8 +294,6 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
              return HTTP_INTERNAL_SERVER_ERROR;
          }
 
-         c_type = apr_table_get(r->headers_in, "Content-Type");
-          c_len = apr_table_get(r->headers_in, "Content-Length");
          snprintf((char *)data, 1024, "\",\n\"content-type\": \"%s\",\n\"content-length\": \"%s\"\n}\n}", c_type, c_len);
          gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: Sending (%d bytes) %s", strlen(data), data);
 
@@ -298,11 +303,18 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
 
          /* Indicate that the next filter call is a new response */
          conn_ctx->filter_stat = GSS_FILT_NEW;
-         
+
          /* set EOS in the outbound brigade */
          bkt_eos = apr_bucket_eos_create(c->bucket_alloc);
          APR_BRIGADE_INSERT_TAIL (brig_out, bkt_eos);
-         
+
+         /* set application type to 'application/json' */
+         apr_table_set(r->headers_out, "Content-Type", "application/json");
+
+         /* clear content-length and MD5 checksum */
+         apr_table_unset(r->headers_out, "Content-Length");
+         apr_table_unset(r->headers_out, "Content-MD5");
+
          /* pass the brigade */
          gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: Sending: EOS");
          if (0 != (ret = ap_pass_brigade(f->next, brig_out))) {
@@ -333,7 +345,7 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
        buf[enc_len] = '\0';
        gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_filter: Sending (%d bytes)", enc_len);
        APR_BRIGADE_INSERT_TAIL(brig_out, bkt_out);
-       
+
        /* Send the output brigade */
        if (OK != (ret = ap_pass_brigade(f->next, brig_out))) {
          apr_brigade_cleanup(brig_in);
@@ -353,7 +365,7 @@ static apr_status_t gssweb_authenticate_filter (ap_filter_t *f,
  * gssweb_insert_error_filter hook.
  */
 static void
-gssweb_add_filter(request_rec *r) 
+gssweb_add_filter(request_rec *r)
 {
   gss_conn_ctx conn_ctx = NULL;
 
@@ -373,7 +385,7 @@ gssweb_add_filter(request_rec *r)
  * output token back to the client.
  */
 static int
-gssweb_authenticate_user(request_rec *r) 
+gssweb_authenticate_user(request_rec *r)
 {
   const char *auth_line = NULL;
   char *auth_type = NULL;
@@ -414,14 +426,17 @@ gssweb_authenticate_user(request_rec *r)
 
   /* Read the token and nonce from the POST */
   if (0 != gssweb_get_post_data(r, &nonce, &input_token)) {
-    gss_log(APLOG_MARK, APLOG_ERR, 0, r, "gssweb_authenticate_user: Unable to read nonce or input token from GSSWeb input");
-    gss_delete_sec_context(&minor_status, &conn_ctx->context, GSS_C_NO_BUFFER);
-    conn_ctx->context = GSS_C_NO_CONTEXT;
-    conn_ctx->state = GSS_CTX_FAILED;
-    if (0 != conn_ctx->output_token.length)
-      gss_release_buffer(&minor_status, &(conn_ctx->output_token));
-    conn_ctx->output_token.length = 0;
-    ret = HTTP_UNAUTHORIZED;
+    /* If we get spurious msg on an established session, say OK again */
+    if (GSS_CTX_ESTABLISHED ==  conn_ctx->state)
+      ret =  OK;
+    /* ...otherwise, if we are in progress, return HTTP_UNAUTHORIZED */
+    if (GSS_CTX_IN_PROGRESS == conn_ctx->state)
+      ret = HTTP_UNAUTHORIZED;
+    /* If this would start a new session, free the context and return DECLINED */
+    else {
+      gss_cleanup_conn_ctx(conn_ctx);
+      ret = DECLINED;
+    }
     goto end;
   }
   gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_user: GSSWeb nonce value = %u.", nonce);
@@ -470,6 +485,7 @@ gssweb_authenticate_user(request_rec *r)
            "%s", get_gss_error(r, major_status, minor_status,
                                "gssweb_authenticate_user: Failed to establish authentication"));
     conn_ctx->state = GSS_CTX_FAILED;
+    goto end;
   }
 
   /* If there was no token returned, clear token from context and exit */
@@ -491,8 +507,8 @@ gssweb_authenticate_user(request_rec *r)
   release_output_token = 0;
 
   /* If we aren't done yet, go around again */
-  gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_user: Accept sec context complete, continue needed");
   if (major_status & GSS_S_CONTINUE_NEEDED) {
+    gss_log(APLOG_MARK, APLOG_DEBUG, 0, r, "gssweb_authenticate_user: Accept sec context complete, continue needed");
     conn_ctx->state = GSS_CTX_IN_PROGRESS;
     ret = HTTP_UNAUTHORIZED;
     goto end;
@@ -502,15 +518,22 @@ gssweb_authenticate_user(request_rec *r)
   conn_ctx->state = GSS_CTX_ESTABLISHED;
   r->user = apr_pstrdup(r->pool, conn_ctx->user);
   r->ap_auth_type = "GSSWeb";
+
+  /* TODO: Make it a single call! */
+  if (conf->name_attributes) {
+    mag_get_name_attributes(r, conf, client_name, conn_ctx);
+    mag_set_req_data(r, conf, conn_ctx);
+  }
+
   ret = OK;
 
  end:
   if (delegated_cred)
     gss_release_cred(&minor_status, &delegated_cred);
-  
+
   if ((release_output_token) && (output_token.length))
     gss_release_buffer(&minor_status, &output_token);
-    
+
   if (client_name != GSS_C_NO_NAME)
     gss_release_name(&minor_status, &client_name);