--- /dev/null
+/*
+ Copyright (c) 2003-10, Andrew McNab, Shiv Kaushal, Joseph Dada,
+ and Yibiao Li, University of Manchester. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, are permitted provided that the following
+ conditions are met:
+
+ o Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following
+ disclaimer.
+ o Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+
+ This program includes code from dav_parse_range() from Apache mod_dav.c,
+ and associated code contributed by David O Callaghan
+
+ Copyright 2000-2005 The Apache Software Foundation or its licensors, as
+ applicable.
+
+ 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.
+
+ This work has been partially funded by the EU Commission (contract
+ INFSO-RI-222667) under the EGEE-III collaboration.
+*/
+
+/*------------------------------------------------------------------*
+ * This program is part of GridSite: http://www.gridsite.org/ *
+ *------------------------------------------------------------------*/
+
+#ifndef VERSION
+#define VERSION "x.x.x"
+#endif
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <apr_strings.h>
+#include <apr_tables.h>
+#include <apr_network_io.h>
+
+#include <ap_config.h>
+#include <httpd.h>
+#include <http_config.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <http_protocol.h>
+#include <http_request.h>
+
+#if AP_MODULE_MAGIC_AT_LEAST(20051115,0)
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+#endif
+
+#include <unixd.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <time.h>
+
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+
+#include <openssl/x509v3.h>
+
+#include "canl_mod_ssl-private.h"
+
+#include "gridsite.h"
+
+#include <canl.h>
+#include <canl_ssl.h>
+
+#ifndef IPV6_ADD_MEMBERSHIP
+#ifdef IPV6_JOIN_GROUP
+#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
+#endif
+#endif
+
+#ifndef UNSET
+#define UNSET -1
+#endif
+
+#define GRST_SESSIONS_DIR "/var/www/sessions"
+
+module AP_MODULE_DECLARE_DATA gridsite_module;
+
+#define GRST_SITECAST_GROUPS 32
+
+struct sitecast_group
+ { char *address; int port; };
+
+#define GRST_SITECAST_ALIASES 32
+
+struct sitecast_alias
+ { const char *sitecast_url; const char *scheme; int port;
+ const char *local_path; const char *local_hostname; };
+
+/* Globals, defined by main server directives in httpd.conf
+ These are assigned default values in create_gridsite_srv_config() */
+
+int gridhttpport = 0; /* set by create_gridsite_srv_config, used as flag */
+char *sessionsdir = NULL;
+char *sitecastdnlists = NULL;
+char *ocspmodes = NULL;
+struct sitecast_group sitecastgroups[GRST_SITECAST_GROUPS+1];
+struct sitecast_alias sitecastaliases[GRST_SITECAST_ALIASES];
+
+ /* This global records whether the SSLSrvConfigRec struct will have
+ the extra BOOL insecure_reneg member */
+int mod_ssl_with_insecure_reneg = 0;
+
+struct sitecast_sockets {
+ fd_set fds;
+ int max_fd;
+} sitecast_sockets;
+
+typedef struct
+{
+ int auth;
+ int autopasscode;
+ int requirepasscode;
+ int zoneslashes;
+ int envs;
+ int format;
+ int indexes;
+ char *indexheader;
+ int gridsitelink;
+ char *adminfile;
+ char *adminuri;
+ char *helpuri;
+ char *loginuri;
+ char *dnlists;
+ char *dnlistsuri;
+ char *adminlist;
+ int gsiproxylimit;
+ char *unzip;
+ char *methods;
+ char *editable;
+ char *headfile;
+ char *footfile;
+ int gridhttp;
+ char *aclformat;
+ char *aclpath;
+ char *execmethod;
+ char *delegationuri;
+ ap_unix_identity_t execugid;
+ apr_fileperms_t diskmode;
+} mod_gridsite_dir_cfg; /* per-directory config choices */
+
+
+/*
+ * parse_content_range() is loosely
+ * based on modules/dav/main/mod_dav.c from Apache
+ */
+
+int parse_content_range(request_rec *r, apr_off_t *range_start,
+ apr_off_t *range_end, apr_off_t *range_length)
+{
+// this all needs verifying to be ok for large (>2GB, >4GB) files
+
+ const char *range_c;
+ char *range;
+ char *dash;
+ char *slash;
+
+ range_c = apr_table_get(r->headers_in, "content-range");
+ if (range_c == NULL) return 0;
+
+ range = apr_pstrdup(r->pool, range_c);
+
+ if ((strncasecmp(range, "bytes ", 6) != 0) ||
+ ((dash = ap_strchr(range, '-')) == NULL) ||
+ ((slash = ap_strchr(range, '/')) == NULL))
+ {
+ return 0; /* malformed header. ignore it (per S14.16 of RFC2616) */
+ }
+
+ *dash = *slash = '\0';
+
+ // Check for GridSite-specific Content-Range: bytes *-*/LENGTH form
+
+ if ((range[6] == '*') && (dash[1] == '*'))
+ {
+ if (slash[1] == '*') return 0; /* invalid truncation length */
+
+ *range_length = apr_atoi64(&slash[1]);
+ *range_start = 0;
+ *range_end = 0;
+
+ return 1; /* a valid (truncation) length */
+ }
+
+ *range_length = 0;
+ *range_start = apr_atoi64(&range[6]);
+ *range_end = apr_atoi64(&dash[1]);
+
+ if ((*range_end < *range_start) ||
+ ((slash[1] != '*') && (apr_atoi64(&slash[1]) <= *range_end)))
+ return 0; /* ignore invalid ranges */
+
+ /* we now have a valid range */
+ return 1;
+}
+
+char *html_escape(apr_pool_t *pool, char *s)
+{
+ int htmlspecials, i;
+ char *escaped, *p;
+
+ for (htmlspecials=0,p=s; *p != '\0'; ++p)
+ if ((*p == '<') || (*p == '>') || (*p == '&') || (*p == '"'))
+ ++htmlspecials;
+
+ escaped = apr_palloc(pool, strlen(s) + htmlspecials * 6 + 1);
+
+ for (i=0,p=s; *p != '\0'; ++p)
+ {
+ if (*p == '<')
+ {
+ strcpy(&escaped[i], "<");
+ i += 4;
+ }
+ else if (*p == '>')
+ {
+ strcpy(&escaped[i], ">");
+ i += 4;
+ }
+ else if (*p == '&')
+ {
+ strcpy(&escaped[i], "&");
+ i += 5;
+ }
+ else if (*p == '"')
+ {
+ strcpy(&escaped[i], """);
+ i += 6;
+ }
+ else
+ {
+ escaped[i] = *p;
+ ++i;
+ }
+ }
+
+ escaped[i] = '\0';
+
+ return escaped;
+}
+
+char *make_admin_footer(request_rec *r, mod_gridsite_dir_cfg *conf,
+ int isdirectory)
+/*
+ make string holding last modified text and admin links
+*/
+{
+ char *out, *https, *p, *dn = NULL, *file = NULL, *permstr = NULL,
+ *temp, modified[99], *dir_uri, *grst_cred_auri_0 = NULL;
+ GRSTgaclPerm perm = GRST_PERM_NONE;
+ struct tm mtime_tm;
+ time_t mtime_time;
+
+ https = (char *) apr_table_get(r->subprocess_env, "HTTPS");
+
+ dir_uri = apr_pstrdup(r->pool, r->uri);
+ p = rindex(dir_uri, '/');
+
+ if (p == NULL) return "";
+
+ file = apr_pstrdup(r->pool, &p[1]);
+ p[1] = '\0';
+ /* dir_uri always gets both a leading and a trailing slash */
+
+ out = apr_pstrdup(r->pool, "<p>\n");
+
+ if (!isdirectory)
+ {
+ mtime_time = apr_time_sec(r->finfo.mtime);
+
+ localtime_r(&mtime_time, &mtime_tm);
+ strftime(modified, sizeof(modified),
+ "%a %e %B %Y", &mtime_tm);
+ temp = apr_psprintf(r->pool,"<hr><small>Last modified %s\n", modified);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+
+ if ((conf->adminuri != NULL) &&
+ (conf->adminuri[0] != '\0') &&
+ (conf->adminfile != NULL) &&
+ (conf->adminfile[0] != '\0') &&
+ (strncmp(file, GRST_HIST_PREFIX, sizeof(GRST_HIST_PREFIX)-1) != 0))
+ {
+ temp = apr_psprintf(r->pool,
+ ". <a href=\"%s?cmd=history&file=%s\">"
+ "View page history</a>\n",
+ conf->adminfile, file);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ out = apr_pstrcat(r->pool, out, "</small>", NULL);
+ }
+
+ out = apr_pstrcat(r->pool, out, "<hr><small>", NULL);
+
+ if (r->connection->notes != NULL)
+ {
+ grst_cred_auri_0 = (char *)
+ apr_table_get(r->notes, "GRST_CRED_AURI_0");
+ }
+
+ if ((grst_cred_auri_0 != NULL) &&
+ (strncmp(grst_cred_auri_0, "dn:", 3) == 0))
+ {
+ dn = GRSThttpUrlDecode(&grst_cred_auri_0[3]);
+ if (dn[0] == '\0')
+ {
+ free(dn);
+ dn = NULL;
+ }
+ }
+
+ if (dn != NULL)
+ {
+ temp = apr_psprintf(r->pool,
+ "You are %s<br>\n", html_escape(r->pool,dn));
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+
+ if (r->notes != NULL)
+ permstr = (char *) apr_table_get(r->notes, "GRST_PERM");
+
+ if ((permstr != NULL) &&
+ (conf->adminuri != NULL) &&
+ (conf->adminuri[0] != '\0') &&
+ (conf->adminfile != NULL) &&
+ (conf->adminfile[0] != '\0'))
+ {
+ sscanf(permstr, "%d", &perm);
+
+ if (!isdirectory &&
+ GRSTgaclPermHasWrite(perm) &&
+ (strncmp(file, GRST_HIST_PREFIX,
+ sizeof(GRST_HIST_PREFIX) - 1) != 0))
+ {
+ temp = apr_psprintf(r->pool,
+ "<a href=\"%s?cmd=edit&file=%s\">"
+ "Edit page</a> .\n", conf->adminfile, file);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ if (GRSTgaclPermHasList(perm) || GRSTgaclPermHasWrite(perm))
+ {
+ temp = apr_psprintf(r->pool,
+ "<a href=\"%s%s?cmd=managedir\">Manage directory</a> .\n",
+ dir_uri, conf->adminfile);
+
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+ }
+
+ free(dn);
+ }
+
+ if ((https != NULL) && (strcasecmp(https, "on") == 0))
+ temp = apr_psprintf(r->pool,
+ "<a href=\"http://%s%s\">Switch to HTTP</a> \n",
+ r->server->server_hostname, r->unparsed_uri);
+ else temp = apr_psprintf(r->pool,
+ "<a href=\"https://%s%s\">Switch to HTTPS</a> \n",
+ r->server->server_hostname, r->unparsed_uri);
+
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+
+ if ((conf->loginuri != NULL) && (conf->loginuri[0] != '\0'))
+ {
+ temp = apr_psprintf(r->pool,
+ ". <a href=\"%s%s\">Login/Logout</a>\n",
+ conf->loginuri, r->unparsed_uri);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ if ((conf->helpuri != NULL) && (conf->helpuri[0] != '\0'))
+ {
+ temp = apr_psprintf(r->pool,
+ ". <a href=\"%s\">Website Help</a>\n", conf->helpuri);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ if ((!isdirectory) &&
+ (conf->adminuri != NULL) &&
+ (conf->adminuri[0] != '\0') &&
+ (conf->adminfile != NULL) &&
+ (conf->adminfile[0] != '\0'))
+ {
+ temp = apr_psprintf(r->pool, ". <a href=\"%s?cmd=print&file=%s\">"
+ "Print View</a>\n", conf->adminfile, file);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ if (conf->gridsitelink)
+ {
+ temp = apr_psprintf(r->pool,
+ ". Built with <a href=\"http://www.gridsite.org/\">"
+ "GridSite</a> %s\n", VERSION);
+ out = apr_pstrcat(r->pool, out, temp, NULL);
+ }
+
+ out = apr_pstrcat(r->pool, out, "\n</small>\n", NULL);
+
+ return out;
+}
+
+void delegation_header(request_rec *r, mod_gridsite_dir_cfg *conf)
+{
+ apr_table_add(r->headers_out,
+ apr_pstrdup(r->pool, "Proxy-Delegation-Service"),
+ apr_psprintf(r->pool,"https://%s%s", r->hostname, conf->delegationuri));
+ return;
+
+}
+
+int html_format(request_rec *r, mod_gridsite_dir_cfg *conf)
+/*
+ try to do GridSite formatting of .html files (NOT .shtml etc)
+*/
+{
+ int fd;
+ char *buf, *p, *file, *s, *head_formatted, *header_formatted,
+ *body_formatted, *admin_formatted, *footer_formatted;
+ size_t length;
+ struct stat statbuf;
+ apr_file_t *fp;
+
+ if (r->finfo.filetype == APR_NOFILE) return HTTP_NOT_FOUND;
+
+ if (apr_file_open(&fp, r->filename, APR_READ, 0, r->pool) != 0)
+ return HTTP_INTERNAL_SERVER_ERROR;
+
+
+ /* Put in Delegation service header if required */
+ if (conf->delegationuri) delegation_header(r, conf);
+
+ file = rindex(r->uri, '/');
+ if (file != NULL) ++file; /* file points to name without path */
+
+ buf = apr_palloc(r->pool, (size_t)(r->finfo.size + 1));
+ length = r->finfo.size;
+ apr_file_read(fp, buf, &length);
+ buf[r->finfo.size] = '\0';
+ apr_file_close(fp);
+
+ /* **** try to find a header file in this or parent directories **** */
+
+ fd = -1;
+
+ if (conf->headfile[0] == '/') /* try absolute */
+ {
+ fd = open(conf->headfile, O_RDONLY);
+ }
+ else /* try relative */
+ {
+ /* first make a buffer big enough to hold path names we want to try */
+ s = apr_palloc(r->pool,
+ strlen(r->filename) + strlen(conf->headfile) + 1);
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+ p[1] = '\0';
+ strcat(p, conf->headfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+ }
+
+ if (fd == -1) /* not found, so set up not to output one */
+ {
+ head_formatted = apr_pstrdup(r->pool, "");
+ header_formatted = apr_pstrdup(r->pool, "");
+ body_formatted = buf;
+ }
+ else /* found a header file, so set up head and body to surround it */
+ {
+ fstat(fd, &statbuf);
+ header_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, header_formatted, statbuf.st_size);
+ header_formatted[statbuf.st_size] = '\0';
+ close(fd);
+
+ p = strstr(buf, "<body");
+ if (p == NULL) p = strstr(buf, "<BODY");
+ if (p == NULL) p = strstr(buf, "<Body");
+
+ if (p == NULL)
+ {
+ head_formatted = apr_pstrdup(r->pool, "");
+ body_formatted = buf;
+ }
+ else
+ {
+ *p = '\0';
+ head_formatted = buf;
+ ++p;
+
+ while ((*p != '>') && (*p != '\0')) ++p;
+
+ if (*p == '\0')
+ {
+ body_formatted = p;
+ }
+ else
+ {
+ *p = '\0';
+ ++p;
+ body_formatted = p;
+ }
+ }
+ }
+
+ /* **** remove closing </body> tag from body **** */
+
+ p = strstr(body_formatted, "</body");
+ if (p == NULL) p = strstr(body_formatted, "</BODY");
+ if (p == NULL) p = strstr(body_formatted, "</Body");
+
+ if (p != NULL) *p = '\0';
+
+ /* **** set up dynamic part of footer to go at end of body **** */
+
+ admin_formatted = make_admin_footer(r, conf, FALSE);
+
+ /* **** try to find a footer file in this or parent directories **** */
+
+ fd = -1;
+
+ if (conf->footfile[0] == '/') /* try absolute */
+ {
+ fd = open(conf->footfile, O_RDONLY);
+ }
+ else /* try relative */
+ {
+ /* first make a buffer big enough to hold path names we want to try */
+ s = apr_palloc(r->pool,
+ strlen(r->filename) + strlen(conf->footfile) + 1);
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+
+ p[1] = '\0';
+ strcat(p, conf->footfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+ }
+
+ if (fd == -1) /* failed to find a footer, so set up empty default */
+ {
+ footer_formatted = apr_pstrdup(r->pool, "");
+ }
+ else /* found a footer, so set up to use it */
+ {
+ fstat(fd, &statbuf);
+ footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, footer_formatted, statbuf.st_size);
+ footer_formatted[statbuf.st_size] = '\0';
+ close(fd);
+ }
+
+ /* **** can now calculate the Content-Length and output headers **** */
+
+ length = strlen(head_formatted) + strlen(header_formatted) +
+ strlen(body_formatted) + strlen(admin_formatted) +
+ strlen(footer_formatted);
+
+ ap_set_content_length(r, length);
+ ap_set_content_type(r, "text/html");
+
+ /* ** output the HTTP body (HTML Head+Body) ** */
+
+ ap_rputs(head_formatted, r);
+ ap_rputs(header_formatted, r);
+ ap_rputs(body_formatted, r);
+ ap_rputs(admin_formatted, r);
+ ap_rputs(footer_formatted, r);
+
+ return OK;
+}
+
+int html_dir_list(request_rec *r, mod_gridsite_dir_cfg *conf)
+/*
+ output HTML directory listing, with level of formatting controlled
+ by GridSiteHtmlFormat/conf->format
+*/
+{
+ int fd, n, nn;
+ char *p, *s, *head_formatted, *header_formatted,
+ *body_formatted, *admin_formatted, *footer_formatted, *temp,
+ modified[999], *d_namepath, *indexheaderpath, *indexheadertext,
+ *encoded, *escaped;
+ size_t length;
+ struct stat statbuf;
+ struct tm mtime_tm;
+ struct dirent **namelist;
+
+ if (r->finfo.filetype == APR_NOFILE) return HTTP_NOT_FOUND;
+
+ /* Put in Delegation service header if required */
+ if (conf->delegationuri) delegation_header(r, conf);
+
+ head_formatted = apr_psprintf(r->pool,
+ "<head><title>Directory listing %s</title></head>\n", r->uri);
+
+ if (conf->format)
+ {
+ /* **** try to find a header file in this or parent directories **** */
+
+ /* first make a buffer big enough to hold path names we want to try */
+ fd = -1;
+ s = apr_palloc(r->pool,
+ strlen(r->filename) + strlen(conf->headfile) + 1);
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+ p[1] = '\0';
+ strcat(p, conf->headfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+
+ if (fd == -1) /* not found, so set up to output sensible default */
+ {
+ header_formatted = apr_pstrdup(r->pool, "<body bgcolor=white>");
+ }
+ else /* found a header file, so set up head and body to surround it */
+ {
+ fstat(fd, &statbuf);
+ header_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, header_formatted, statbuf.st_size);
+ header_formatted[statbuf.st_size] = '\0';
+ close(fd);
+ }
+ }
+ else header_formatted = apr_pstrdup(r->pool, "<body bgcolor=white>");
+
+ body_formatted = apr_psprintf(r->pool,
+ "<h1>Directory listing %s</h1>\n", r->uri);
+
+ if (conf->indexheader != NULL)
+ {
+ indexheaderpath = apr_psprintf(r->pool, "%s/%s", r->filename,
+ conf->indexheader);
+ fd = open(indexheaderpath, O_RDONLY);
+ if (fd != -1)
+ {
+ fstat(fd, &statbuf);
+ indexheadertext = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, indexheadertext, statbuf.st_size);
+ indexheadertext[statbuf.st_size] = '\0';
+ close(fd);
+
+ body_formatted = apr_pstrcat(r->pool, body_formatted,
+ indexheadertext, NULL);
+ }
+ }
+
+ body_formatted = apr_pstrcat(r->pool, body_formatted, "<p><table>\n", NULL);
+
+ if (r->unparsed_uri[1] != '\0')
+ body_formatted = apr_pstrcat(r->pool, body_formatted,
+ "<tr><td colspan=3>[<a href=\"../\">Parent directory</a>]</td></tr>\n",
+ NULL);
+
+ nn = scandir(r->filename, &namelist, 0, versionsort);
+ for (n=0; n < nn; ++n)
+ {
+ if ((namelist[n]->d_name[0] != '.') &&
+ ((conf->indexheader == NULL) ||
+ (strcmp(conf->indexheader, namelist[n]->d_name) != 0)))
+ {
+ d_namepath = apr_psprintf(r->pool, "%s/%s", r->filename,
+ namelist[n]->d_name);
+ stat(d_namepath, &statbuf);
+
+ localtime_r(&(statbuf.st_mtime), &mtime_tm);
+ strftime(modified, sizeof(modified),
+ "<td align=right>%R</td><td align=right>%e %b %y</td>",
+ &mtime_tm);
+
+ encoded = GRSThttpUrlEncode(namelist[n]->d_name);
+ escaped = html_escape(r->pool, namelist[n]->d_name);
+
+ if (S_ISDIR(statbuf.st_mode))
+ temp = apr_psprintf(r->pool,
+ "<tr><td><a href=\"%s/\" content-length=\"%ld\" "
+ "last-modified=\"%ld\">"
+ "%s/</a></td>"
+ "<td align=right>%ld</td>%s</tr>\n",
+ encoded, statbuf.st_size, statbuf.st_mtime,
+ escaped,
+ statbuf.st_size, modified);
+ else temp = apr_psprintf(r->pool,
+ "<tr><td><a href=\"%s\" content-length=\"%ld\" "
+ "last-modified=\"%ld\">"
+ "%s</a></td>"
+ "<td align=right>%ld</td>%s</tr>\n",
+ encoded, statbuf.st_size, statbuf.st_mtime,
+ escaped,
+ statbuf.st_size, modified);
+
+ free(encoded);
+ /* escaped done with pool so no free() */
+
+ body_formatted = apr_pstrcat(r->pool,body_formatted,temp,NULL);
+ }
+
+ free(namelist[n]);
+ }
+
+ free(namelist);
+
+ body_formatted = apr_pstrcat(r->pool, body_formatted, "</table>\n", NULL);
+
+ if (conf->format)
+ {
+ /* **** set up dynamic part of footer to go at end of body **** */
+
+ admin_formatted = make_admin_footer(r, conf, TRUE);
+
+ /* **** try to find a footer file in this or parent directories **** */
+
+ /* first make a buffer big enough to hold path names we want to try */
+ fd = -1;
+ s = apr_palloc(r->pool,
+ strlen(r->filename) + strlen(conf->footfile) + 1);
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+
+ p[1] = '\0';
+ strcat(p, conf->footfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+
+ if (fd == -1) /* failed to find a footer, so use standard default */
+ {
+ footer_formatted = apr_pstrdup(r->pool, "</body>");
+ }
+ else /* found a footer, so set up to use it */
+ {
+ fstat(fd, &statbuf);
+ footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, footer_formatted, statbuf.st_size);
+ footer_formatted[statbuf.st_size] = '\0';
+ close(fd);
+ }
+ }
+ else
+ {
+ admin_formatted = apr_pstrdup(r->pool, "");
+ footer_formatted = apr_pstrdup(r->pool, "</body>");
+ }
+
+ /* **** can now calculate the Content-Length and output headers **** */
+
+ length = strlen(head_formatted) + strlen(header_formatted) +
+ strlen(body_formatted) + strlen(admin_formatted) +
+ strlen(footer_formatted);
+
+ ap_set_content_length(r, length);
+ ap_set_content_type(r, "text/html");
+
+ /* ** output the HTTP body (HTML Head+Body) ** */
+
+ ap_rputs(head_formatted, r);
+ ap_rputs(header_formatted, r);
+ ap_rputs(body_formatted, r);
+ ap_rputs(admin_formatted, r);
+ ap_rputs(footer_formatted, r);
+
+ return OK;
+}
+
+char *make_passcode_file(request_rec *r, mod_gridsite_dir_cfg *conf,
+ char *path, apr_time_t expires_time)
+{
+ int i;
+ char *filetemplate, *notename_i, *grst_cred_i, *cookievalue=NULL;
+ apr_uint64_t gridauthcookie;
+ apr_file_t *fp;
+
+ /* create random for use in GRIDHTTP_PASSCODE cookies and file name */
+
+ if (apr_generate_random_bytes((char *) &gridauthcookie,
+ sizeof(gridauthcookie))
+ != APR_SUCCESS) return NULL;
+
+ filetemplate = apr_psprintf(r->pool, "%s/passcode-%016lxXXXXXX",
+ ap_server_root_relative(r->pool,
+ sessionsdir),
+ gridauthcookie);
+
+ if (apr_file_mktemp(&fp,
+ filetemplate,
+ APR_CREATE | APR_WRITE | APR_EXCL,
+ r->pool)
+ != APR_SUCCESS) return NULL;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Created passcode file %s", filetemplate);
+
+ if (expires_time > 0) apr_file_printf(fp, "expires=%lu\n",
+ (time_t) apr_time_sec(expires_time));
+
+ apr_file_printf(fp, "domain=%s\npath=%s\n", r->hostname, path);
+
+ for (i=0; ; ++i)
+ {
+ notename_i = apr_psprintf(r->pool, "GRST_CRED_AURI_%d", i);
+ if (grst_cred_i = (char *)
+ apr_table_get(r->connection->notes, notename_i))
+ {
+ apr_file_printf(fp, "%s=%s\n", notename_i, grst_cred_i);
+ }
+ else break; /* GRST_CRED_AURI_i are numbered consecutively */
+
+ notename_i = apr_psprintf(r->pool, "GRST_CRED_VALID_%d", i);
+ if (grst_cred_i = (char *)
+ apr_table_get(r->connection->notes, notename_i))
+ {
+ apr_file_printf(fp, "%s=%s\n", notename_i, grst_cred_i);
+ }
+ else break; /* GRST_CRED_VALID_i are numbered consecutively */
+ }
+
+ if (apr_file_close(fp) != APR_SUCCESS)
+ {
+ apr_file_remove(filetemplate, r->pool); /* try to clean up */
+ return NULL;
+ }
+
+ cookievalue = rindex(filetemplate, '-');
+ if (cookievalue != NULL)
+ {
+ ++cookievalue;
+ return cookievalue;
+ }
+ else return NULL;
+}
+
+int http_gridhttp(request_rec *r, mod_gridsite_dir_cfg *conf)
+{
+ char *httpurl, *cookievalue, expires_str[APR_RFC822_DATE_LEN];
+ apr_time_t expires_time;
+
+ /* passcode cookies are valid for only 5 mins! */
+ expires_time = apr_time_now() + apr_time_from_sec(300);
+
+ /* try to generate passcode and make passcode file */
+ cookievalue = make_passcode_file(r, conf, r->uri, expires_time);
+
+ if (cookievalue == NULL) return HTTP_INTERNAL_SERVER_ERROR;
+
+ /* send redirection header back to client */
+
+ apr_rfc822_date(expires_str, expires_time);
+
+ apr_table_add(r->headers_out,
+ apr_pstrdup(r->pool, "Set-Cookie"),
+ apr_psprintf(r->pool,
+ "GRIDHTTP_PASSCODE=%s; "
+ "expires=%s; "
+ "domain=%s; "
+ "path=%s",
+ cookievalue, expires_str, r->hostname, r->uri));
+
+ if (gridhttpport != DEFAULT_HTTP_PORT)
+ httpurl = apr_psprintf(r->pool, "http://%s:%d%s", r->hostname,
+ gridhttpport, ap_escape_uri(r->pool, r->uri));
+ else httpurl = apr_pstrcat(r->pool, "http://", r->hostname,
+ ap_escape_uri(r->pool, r->uri), NULL);
+
+ apr_table_setn(r->headers_out, apr_pstrdup(r->pool, "Location"), httpurl);
+
+ r->status = HTTP_MOVED_TEMPORARILY;
+ return OK;
+}
+
+int http_put_method(request_rec *r, mod_gridsite_dir_cfg *conf)
+{
+ char buf[2048], *filename, *dirname, *basename;
+ const char *p;
+ size_t block_length, length_sent;
+ int retcode, stat_ret;
+ apr_file_t *fp;
+ struct stat statbuf;
+ int has_range = 0, is_done = 0;
+ apr_off_t range_start, range_end, range_length, length_to_send, length = 0;
+
+ /* *** check if directory creation: PUT /.../ *** */
+
+ if ((r->unparsed_uri != NULL) &&
+ (r->unparsed_uri[0] != '\0') &&
+ (r->unparsed_uri[strlen(r->unparsed_uri) - 1] == '/'))
+ {
+ if (apr_dir_make(r->filename,
+ conf->diskmode
+ | APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE,
+ r->pool) != 0) return HTTP_INTERNAL_SERVER_ERROR;
+
+ /* we force the permissions, rather than accept any existing ones */
+
+ apr_file_perms_set(r->filename, conf->diskmode
+ | APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE);
+
+ ap_set_content_length(r, 0);
+ ap_set_content_type(r, "text/html");
+ return OK;
+ }
+
+ /* *** otherwise assume trying to create a regular file *** */
+
+ stat_ret = stat(r->filename, &statbuf);
+
+ /* find if a range is specified */
+
+ has_range = parse_content_range(r, &range_start, &range_end, &range_length);
+
+ if (has_range)
+ {
+ if ((range_start == 0) && (range_end == 0)) /* truncate? */
+ {
+ if (stat_ret != 0) return HTTP_NOT_FOUND;
+
+ if (truncate(r->filename, range_length) != 0)
+ return HTTP_INTERNAL_SERVER_ERROR;
+ else return OK;
+ }
+
+ filename = r->filename;
+
+ if (apr_file_open(&fp, filename, APR_WRITE | APR_CREATE | APR_BUFFERED,
+ conf->diskmode, r->pool) != 0) return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else /* use temporary file if not a partial transfer */
+ {
+ dirname = apr_pstrdup(r->pool, r->filename);
+ basename = rindex(dirname, '/');
+ if (basename == NULL) return HTTP_INTERNAL_SERVER_ERROR;
+
+ *basename = '\0';
+ ++basename;
+
+ filename = apr_psprintf(r->pool,
+ "%s/.grsttmp-%s-XXXXXX", dirname, basename);
+
+ if (apr_file_mktemp(&fp, filename,
+ APR_CREATE | APR_WRITE | APR_BUFFERED | APR_EXCL, r->pool)
+ != APR_SUCCESS) return HTTP_INTERNAL_SERVER_ERROR;
+/*
+ p = apr_table_get(r->headers_in, "Content-Length");
+ if (p != NULL)
+ {
+ length = (apr_off_t) atol(p);
+ if (length > 16384)
+ {
+ if (apr_file_seek(fp, APR_SET, &length) == 0)
+ {
+ block_length = 1;
+ apr_file_write(fp, "0", &block_length);
+ }
+
+ apr_file_seek(fp, APR_SET, 0);
+ }
+ }
+*/
+ }
+
+ /* we force the permissions, rather than accept any existing ones */
+
+ apr_file_perms_set(filename, conf->diskmode);
+
+ if (has_range)
+ {
+ if (apr_file_seek(fp, APR_SET, &range_start) != 0)
+ {
+ retcode = HTTP_INTERNAL_SERVER_ERROR;
+ return retcode;
+ }
+
+ length_to_send = range_end - range_start + 1;
+ }
+
+ retcode = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
+ if (retcode == OK)
+ {
+ if (has_range) length_sent = 0;
+
+ if (ap_should_client_block(r))
+ while ((block_length = ap_get_client_block(r, buf, sizeof(buf))) > 0)
+ {
+ if (has_range && (length_sent + block_length > length_to_send))
+ {
+ block_length = length_to_send - length_sent;
+ is_done = 1;
+ }
+
+ if (apr_file_write(fp, buf, &block_length) != 0)
+ {
+ retcode = HTTP_INTERNAL_SERVER_ERROR;
+ break;
+ }
+
+ if (has_range)
+ {
+ if (is_done) break;
+ else length_sent += block_length;
+ }
+ }
+ ap_set_content_length(r, 0);
+ ap_set_content_type(r, "text/html");
+ }
+
+ if ((apr_file_close(fp) != 0) || (retcode == HTTP_INTERNAL_SERVER_ERROR))
+ {
+ if (strcmp(filename, r->filename) != 0) remove(filename);
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if ((strcmp(filename, r->filename) != 0) &&
+ (apr_file_rename(filename, r->filename, r->pool) != 0))
+ return HTTP_FORBIDDEN; /* best guess as to the problem ... */
+
+ if ((retcode == OK) && (stat_ret != 0))
+ {
+ retcode = HTTP_CREATED;
+ ap_custom_response(r, HTTP_CREATED, "");
+ }
+
+ return retcode;
+}
+
+int http_delete_method(request_rec *r, mod_gridsite_dir_cfg *conf)
+{
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Try remove(%s)", r->filename);
+
+ if (remove(r->filename) != 0) return HTTP_FORBIDDEN;
+
+ ap_set_content_length(r, 0);
+ ap_set_content_type(r, "text/html");
+
+ return OK;
+}
+
+int http_move_method(request_rec *r, mod_gridsite_dir_cfg *conf)
+{
+ char *destination_translated = NULL;
+
+ if (r->notes != NULL) destination_translated =
+ (char *) apr_table_get(r->notes, "GRST_DESTINATION_TRANSLATED");
+
+ if (destination_translated == NULL) return HTTP_BAD_REQUEST;
+
+ if (strcmp(r->filename, destination_translated) == 0)
+ return HTTP_FORBIDDEN;
+
+ if (apr_file_rename(r->filename, destination_translated, r->pool) != 0)
+ return HTTP_FORBIDDEN;
+
+ ap_set_content_length(r, 0);
+ ap_set_content_type(r, "text/html");
+
+ return OK;
+}
+
+static int mod_gridsite_dir_handler(request_rec *r, mod_gridsite_dir_cfg *conf)
+/*
+ handler switch for directories
+*/
+{
+ /* *** is this a write method? only possible if GridSiteAuth on *** */
+
+ if (conf->auth)
+ {
+ if ((r->method_number == M_PUT) &&
+ (conf->methods != NULL) &&
+ (strstr(conf->methods, " PUT " ) != NULL))
+ return http_put_method(r, conf);
+
+ if ((r->method_number == M_DELETE) &&
+ (conf->methods != NULL) &&
+ (strstr(conf->methods, " DELETE ") != NULL))
+ return http_delete_method(r, conf);
+ }
+
+ /* *** directory listing? *** */
+ if ((r->method_number == M_GET) && (conf->indexes))
+ return html_dir_list(r, conf); /* directory listing */
+
+ return DECLINED; /* *** nothing to see here, move along *** */
+}
+
+static int mod_gridsite_nondir_handler(request_rec *r, mod_gridsite_dir_cfg *conf)
+/*
+ one big handler switch for everything other than directories, since we
+ might be responding to MIME * / * for local PUT, MOVE, COPY and DELETE,
+ and GET inside ghost directories.
+*/
+{
+ char *upgradeheader, *upgradespaced, *p;
+ const char *https_env;
+
+ /* *** is this a write method or GridHTTP HTTPS->HTTP redirection?
+ only possible if GridSiteAuth on *** */
+
+ if (conf->auth)
+ {
+ if ((conf->gridhttp) &&
+ ((r->method_number == M_GET) ||
+ ((r->method_number == M_PUT) &&
+ (strstr(conf->methods, " PUT ") != NULL))) &&
+ ((upgradeheader = (char *) apr_table_get(r->headers_in,
+ "Upgrade")) != NULL) &&
+ ((https_env=apr_table_get(r->subprocess_env,"HTTPS")) != NULL) &&
+ (strcasecmp(https_env, "on") == 0))
+ {
+ upgradespaced = apr_psprintf(r->pool, " %s ", upgradeheader);
+
+ for (p=upgradespaced; *p != '\0'; ++p)
+ if ((*p == ',') || (*p == '\t')) *p = ' ';
+
+// TODO: what if we're pointing at a CGI or some dynamic content???
+
+ if (strstr(upgradespaced, " GridHTTP/1.0 ") != NULL)
+ return http_gridhttp(r, conf);
+ }
+
+ if ((r->method_number == M_PUT) &&
+ (conf->methods != NULL) &&
+ (strstr(conf->methods, " PUT " ) != NULL))
+ return http_put_method(r, conf);
+
+ if ((r->method_number == M_DELETE) &&
+ (conf->methods != NULL) &&
+ (strstr(conf->methods, " DELETE ") != NULL))
+ return http_delete_method(r, conf);
+
+ if ((r->method_number == M_MOVE) &&
+ (conf->methods != NULL) &&
+ (strstr(conf->methods, " MOVE ") != NULL))
+ return http_move_method(r, conf);
+ }
+
+ /* *** check if a special ghost admin CGI *** */
+
+ if (conf->adminfile && conf->adminuri &&
+ (strlen(r->filename) > strlen(conf->adminfile) + 1) &&
+ (strcmp(&(r->filename[strlen(r->filename) - strlen(conf->adminfile)]),
+ conf->adminfile) == 0) &&
+ (r->filename[strlen(r->filename)-strlen(conf->adminfile)-1] == '/') &&
+ ((r->method_number == M_POST) ||
+ (r->method_number == M_GET)))
+ {
+ ap_internal_redirect(conf->adminuri, r);
+ return OK;
+ }
+
+ /* *** finally look for .html files that we should format *** */
+
+ if ((conf->format) && /* conf->format set by GridSiteHtmlFormat on */
+ (strlen(r->filename) > 5) &&
+ (strcmp(&(r->filename[strlen(r->filename)-5]), ".html") == 0) &&
+ (r->method_number == M_GET)) return html_format(r, conf);
+
+ return DECLINED; /* *** nothing to see here, move along *** */
+}
+
+static void recurse4dirlist(char *dirname, time_t *dirs_time,
+ char *fulluri, int fullurilen,
+ char *encfulluri, int enclen,
+ request_rec *r, char **body,
+ int recurse_level)
+/* try to find DN Lists in dir[] and its subdirs that match the fulluri[]
+ prefix. add blobs of HTML to body as they are found. */
+{
+ char *unencname, modified[99], *oneline, *d_namepath,
+ *mildencoded;
+ DIR *oneDIR;
+ struct dirent *onedirent;
+ struct tm mtime_tm;
+ struct stat statbuf;
+
+ if ((stat(dirname, &statbuf) != 0) ||
+ (!S_ISDIR(statbuf.st_mode)) ||
+ ((oneDIR = opendir(dirname)) == NULL)) return;
+
+ if (statbuf.st_mtime > *dirs_time) *dirs_time = statbuf.st_mtime;
+
+ while ((onedirent = readdir(oneDIR)) != NULL)
+ {
+ if (onedirent->d_name[0] == '.') continue;
+
+ d_namepath = apr_psprintf(r->pool, "%s/%s", dirname, onedirent->d_name);
+
+ if (stat(d_namepath, &statbuf) != 0) continue;
+
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ if (recurse_level < GRST_RECURS_LIMIT)
+ recurse4dirlist(d_namepath, dirs_time, fulluri,
+ fullurilen, encfulluri, enclen,
+ r, body, recurse_level + 1);
+ }
+ else if ((strncmp(onedirent->d_name, encfulluri, enclen) == 0) &&
+ (onedirent->d_name[strlen(onedirent->d_name) - 1] != '~'))
+ {
+ unencname = GRSThttpUrlDecode(onedirent->d_name);
+
+ if (strncmp(unencname, fulluri, fullurilen) == 0)
+ {
+ if (statbuf.st_mtime > *dirs_time)
+ *dirs_time = statbuf.st_mtime;
+
+ localtime_r(&(statbuf.st_mtime), &mtime_tm);
+ strftime(modified, sizeof(modified),
+ "<td align=right>%R</td><td align=right>%e %b %y</td>",
+ &mtime_tm);
+
+ mildencoded = GRSThttpUrlMildencode(&unencname[fullurilen]);
+
+ oneline = apr_psprintf(r->pool,
+ "<tr><td><a href=\"%s\" "
+ "content-length=\"%ld\" "
+ "last-modified=\"%ld\">"
+ "%s</a></td>"
+ "<td align=right>%ld</td>%s</tr>\n",
+ mildencoded, statbuf.st_size,
+ statbuf.st_mtime,
+ html_escape(r->pool, unencname),
+ statbuf.st_size, modified);
+
+ free(mildencoded);
+
+ *body = apr_pstrcat(r->pool, *body, oneline, NULL);
+ }
+
+ free(unencname); /* libgridsite doesnt use pools */
+ }
+ }
+
+ closedir(oneDIR);
+}
+
+static int mod_gridsite_dnlistsuri_dir_handler(request_rec *r,
+ mod_gridsite_dir_cfg *conf)
+/*
+ virtual DN-list file lister: make all DN lists on the dn-lists
+ path of this server appear to be in the dn-lists directory itself
+ (ie where they appear in the DN lists path doesnt matter, as long
+ as their name matches)
+*/
+{
+ int enclen, fullurilen, fd;
+ char *fulluri, *encfulluri, *dn_list_ptr, *dirname,
+ *body, *oneline, *p, *s,
+ *head_formatted, *header_formatted, *footer_formatted,
+ *permstr = NULL;
+ struct stat statbuf;
+ size_t length;
+ time_t dirs_time = 0;
+ GRSTgaclPerm perm = GRST_PERM_NONE;
+
+ if (r->notes != NULL)
+ permstr = (char *) apr_table_get(r->notes, "GRST_PERM");
+
+ if (permstr != NULL) sscanf(permstr, "%d", &perm);
+
+ fulluri = apr_psprintf(r->pool, "https://%s%s",
+ r->hostname, conf->dnlistsuri);
+ fullurilen = strlen(fulluri);
+
+ encfulluri = GRSThttpUrlEncode(fulluri);
+ enclen = strlen(encfulluri);
+
+ if (conf->dnlists != NULL) p = conf->dnlists;
+ else p = getenv("GRST_DN_LISTS");
+
+ if (p == NULL) p = GRST_DN_LISTS;
+ dn_list_ptr = apr_pstrdup(r->pool, p);
+
+ head_formatted = apr_psprintf(r->pool,
+ "<head><title>Directory listing %s</title></head>\n", r->uri);
+
+ if (conf->format)
+ {
+ /* **** try to find a header file in this or parent directories **** */
+
+ fd = -1;
+
+ if (conf->headfile[0] == '/') /* try absolute */
+ {
+ fd = open(conf->headfile, O_RDONLY);
+ }
+ else /* try relative */
+ {
+ /* first make a buffer big enough to hold path names we want to try */
+ s = malloc(strlen(r->filename) + strlen(conf->headfile) + 1);
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+ p[1] = '\0';
+ strcat(p, conf->headfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+
+ free(s);
+ }
+
+ if (fd == -1) /* not found, so set up to output sensible default */
+ {
+ header_formatted = apr_pstrdup(r->pool, "<body bgcolor=white>");
+ }
+ else /* found a header file, so set up head and body to surround it */
+ {
+ fstat(fd, &statbuf);
+ header_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, header_formatted, statbuf.st_size);
+ header_formatted[statbuf.st_size] = '\0';
+ close(fd);
+ }
+ }
+ else header_formatted = apr_pstrdup(r->pool, "<body bgcolor=white>");
+
+ body = apr_psprintf(r->pool,
+ "<h1>Directory listing %s</h1>\n<table>", r->uri);
+
+ if ((r->uri)[1] != '\0')
+ body = apr_pstrcat(r->pool, body,
+ "<tr><td>[<a href=\"../\">Parent directory</a>]</td></tr>\n",
+ NULL);
+
+ while ((dirname = strsep(&dn_list_ptr, ":")) != NULL)
+ recurse4dirlist(dirname, &dirs_time, fulluri, fullurilen,
+ encfulluri, enclen, r, &body, 0);
+
+ p = (char *) apr_table_get(r->subprocess_env, "HTTPS");
+ if ((p != NULL) && (strcmp(p, "on") == 0))
+ {
+ oneline = apr_psprintf(r->pool,
+ "<form action=\"%s%s\" method=post>\n"
+ "<input type=hidden name=cmd value=managednlists>"
+ "<tr><td colspan=4 align=center><small><input type=submit "
+ "value=\"Manage DN lists\"></small></td></tr></form>\n",
+ r->uri, conf->adminfile);
+
+ body = apr_pstrcat(r->pool, body, oneline, NULL);
+ }
+
+ body = apr_pstrcat(r->pool, body, "</table>\n", NULL);
+
+ free(encfulluri); /* libgridsite doesnt use pools */
+
+ if (conf->format)
+ {
+ /* **** try to find a footer file in this or parent directories **** */
+
+ fd = -1;
+
+ if (conf->headfile[0] == '/') /* try absolute */
+ {
+ fd = open(conf->headfile, O_RDONLY);
+ }
+ else /* try relative */
+ {
+ /* first make a buffer big enough to hold path names we want to try */
+ s = malloc(strlen(r->filename) + strlen(conf->footfile));
+ strcpy(s, r->filename);
+
+ for (;;)
+ {
+ p = rindex(s, '/');
+ if (p == NULL) break; /* failed to find one */
+
+ p[1] = '\0';
+ strcat(p, conf->footfile);
+
+ fd = open(s, O_RDONLY);
+ if (fd != -1) break; /* found one */
+
+ *p = '\0';
+ }
+
+ free(s);
+ }
+
+ if (fd == -1) /* failed to find a footer, so use standard default */
+ {
+ footer_formatted = apr_pstrdup(r->pool, "</body>");
+ }
+ else /* found a footer, so set up to use it */
+ {
+ fstat(fd, &statbuf);
+ footer_formatted = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, footer_formatted, statbuf.st_size);
+ footer_formatted[statbuf.st_size] = '\0';
+ close(fd);
+ }
+ }
+ else footer_formatted = apr_pstrdup(r->pool, "</body>");
+
+ /* **** can now calculate the Content-Length and output headers **** */
+
+ length = strlen(head_formatted) + strlen(header_formatted) +
+ strlen(body) + strlen(footer_formatted);
+
+ ap_set_content_length(r, length);
+ r->mtime = apr_time_from_sec(dirs_time);
+ ap_set_last_modified(r);
+ ap_set_content_type(r, "text/html");
+
+ /* ** output the HTTP body (HTML Head+Body) ** */
+ ap_rputs(head_formatted, r);
+ ap_rputs(header_formatted, r);
+ ap_rputs(body, r);
+ ap_rputs(footer_formatted, r);
+
+ return OK;
+}
+
+static char *recurse4file(char *dir, char *file, apr_pool_t *pool,
+ int recurse_level)
+/* try to find file[] in dir[]. try subdirs if not found.
+ return full path to first found version or NULL on failure */
+{
+ char *fullfilename, *fulldirname;
+ struct stat statbuf;
+ DIR *dirDIR;
+ struct dirent *file_ent;
+
+ /* try to find in current directory */
+
+ fullfilename = apr_psprintf(pool, "%s/%s", dir, file);
+
+ if (stat(fullfilename, &statbuf) == 0) return fullfilename;
+
+ /* maybe search in subdirectories */
+
+ if (recurse_level >= GRST_RECURS_LIMIT) return NULL;
+
+ dirDIR = opendir(dir);
+
+ if (dirDIR == NULL) return NULL;
+
+ while ((file_ent = readdir(dirDIR)) != NULL)
+ {
+ if (file_ent->d_name[0] == '.') continue;
+
+ fulldirname = apr_psprintf(pool, "%s/%s", dir, file_ent->d_name);
+ if ((stat(fulldirname, &statbuf) == 0) &&
+ S_ISDIR(statbuf.st_mode) &&
+ ((fullfilename = recurse4file(fulldirname, file,
+ pool, recurse_level + 1)) != NULL))
+ {
+ closedir(dirDIR);
+ return fullfilename;
+ }
+ }
+
+ closedir(dirDIR);
+
+ return NULL;
+}
+
+static int mod_gridsite_dnlistsuri_handler(request_rec *r,
+ mod_gridsite_dir_cfg *conf)
+/*
+ virtual DN-list file generator
+*/
+{
+ int fd;
+ char *fulluri, *encfulluri, *dn_list_ptr, *filename, *dirname, *p,
+ *buf;
+ struct stat statbuf;
+
+ /* *** check if a special ghost admin CGI *** */
+
+ if (conf->adminfile && conf->adminuri &&
+ (strlen(r->filename) > strlen(conf->adminfile) + 1) &&
+ (strcmp(&(r->filename[strlen(r->filename) - strlen(conf->adminfile)]),
+ conf->adminfile) == 0) &&
+ (r->filename[strlen(r->filename)-strlen(conf->adminfile)-1] == '/') &&
+ ((r->method_number == M_POST) ||
+ (r->method_number == M_GET)))
+ {
+ ap_internal_redirect(conf->adminuri, r);
+ return OK;
+ }
+
+ if (r->uri[strlen(r->uri) - 1] == '/')
+ {
+ apr_table_setn(r->headers_out, apr_pstrdup(r->pool, "Location"),
+ apr_pstrdup(r->pool, conf->dnlistsuri));
+
+ r->status = HTTP_MOVED_TEMPORARILY;
+ return OK;
+ }
+
+ fulluri = apr_psprintf(r->pool, "https://%s%s",
+ r->hostname, r->uri);
+
+ encfulluri = GRSThttpUrlEncode(fulluri);
+
+ if (conf->dnlists != NULL) p = conf->dnlists;
+ else p = getenv("GRST_DN_LISTS");
+
+ if (p == NULL) p = GRST_DN_LISTS;
+ dn_list_ptr = apr_pstrdup(r->pool, p);
+
+ while ((dirname = strsep(&dn_list_ptr, ":")) != NULL)
+ {
+ filename = recurse4file(dirname, encfulluri, r->pool, 0);
+
+ if (filename == NULL) continue;
+
+ fd = open(filename, O_RDONLY);
+
+ if (fd == -1) continue;
+
+ fstat(fd, &statbuf);
+ ap_set_content_length(r, (apr_off_t) statbuf.st_size);
+ r->mtime = apr_time_from_sec(statbuf.st_mtime);
+ ap_set_content_type(r, "text/plain");
+ ap_set_last_modified(r);
+
+ buf = apr_palloc(r->pool, statbuf.st_size + 1);
+ read(fd, buf, statbuf.st_size);
+ buf[statbuf.st_size] = '\0';
+
+ ap_rputs(buf, r);
+
+ close(fd);
+
+ return OK;
+ }
+
+ return HTTP_NOT_FOUND;
+}
+
+static void *create_gridsite_srv_config(apr_pool_t *p, server_rec *s)
+{
+ int i;
+
+ /* only run once (in base server) */
+ if (!(s->is_virtual) && (gridhttpport == 0))
+ {
+ gridhttpport = GRST_HTTP_PORT;
+
+ sessionsdir = apr_pstrdup(p, GRST_SESSIONS_DIR);
+ /* GridSiteSessionsDir dir-path */
+
+ sitecastdnlists = NULL;
+
+ sitecastgroups[0].port = GRST_HTCP_PORT;
+ /* GridSiteCastUniPort udp-port */
+
+ for (i=1; i <= GRST_SITECAST_GROUPS; ++i)
+ {
+ sitecastgroups[i].port = 0; /* GridSiteCastGroup mcast-list */
+ }
+
+ for (i=0; i <= GRST_SITECAST_ALIASES; ++i)
+ {
+ sitecastaliases[i].sitecast_url = NULL;
+ sitecastaliases[i].port = 0;
+ sitecastaliases[i].scheme = NULL;
+ sitecastaliases[i].local_path = NULL;
+ sitecastaliases[i].local_hostname = NULL;
+ } /* GridSiteCastAlias url path */
+ }
+
+ return NULL;
+}
+
+static void *create_gridsite_dir_config(apr_pool_t *p, char *path)
+{
+ mod_gridsite_dir_cfg *conf = apr_palloc(p, sizeof(*conf));
+
+ if (path == NULL) /* set up document root defaults */
+ {
+ conf->auth = 0; /* GridSiteAuth on/off */
+ conf->autopasscode = 1; /* GridSiteAutoPasscode on/off */
+ conf->requirepasscode = 0; /* GridSiteRequirePasscode on/off */
+ conf->zoneslashes = 1; /* GridSiteZoneSlashes number */
+ conf->envs = 1; /* GridSiteEnvs on/off */
+ conf->format = 0; /* GridSiteHtmlFormat on/off */
+ conf->indexes = 0; /* GridSiteIndexes on/off */
+ conf->indexheader = NULL; /* GridSiteIndexHeader File-value */
+ conf->gridsitelink = 1; /* GridSiteLink on/off */
+ conf->adminfile = apr_pstrdup(p, GRST_ADMIN_FILE);
+ /* GridSiteAdminFile File-value */
+ conf->adminuri = NULL; /* GridSiteAdminURI URI-value */
+ conf->helpuri = NULL; /* GridSiteHelpURI URI-value */
+ conf->loginuri = NULL; /* GridSiteLoginURI URI-value */
+ conf->dnlists = NULL; /* GridSiteDNlists Search-path */
+ conf->dnlistsuri = NULL; /* GridSiteDNlistsURI URI-value */
+ conf->adminlist = NULL; /* GridSiteAdminList URI-value */
+ conf->gsiproxylimit = 1000; /* GridSiteGSIProxyLimit number */
+ conf->unzip = NULL; /* GridSiteUnzip file-path */
+
+ conf->methods = apr_pstrdup(p, " GET ");
+ /* GridSiteMethods methods */
+
+ conf->editable = apr_pstrdup(p, " txt shtml html htm css js php jsp ");
+ /* GridSiteEditable types */
+
+ conf->headfile = apr_pstrdup(p, GRST_HEADFILE);
+ conf->footfile = apr_pstrdup(p, GRST_FOOTFILE);
+ /* GridSiteHeadFile and GridSiteFootFile file name */
+
+ conf->gridhttp = 0; /* GridSiteGridHTTP on/off */
+ conf->aclformat = apr_pstrdup(p, "GACL");
+ /* GridSiteACLFormat gacl/xacml */
+ conf->aclpath = NULL; /* GridSiteACLPath acl-path */
+ conf->delegationuri = NULL; /* GridSiteDelegationURI URI-value */
+ conf->execmethod = NULL;
+ /* GridSiteExecMethod nosetuid/suexec/X509DN/directory */
+
+ conf->execugid.uid = 0; /* GridSiteUserGroup User Group */
+ conf->execugid.gid = 0; /* ditto */
+ conf->execugid.userdir = 0; /* ditto */
+
+ conf->diskmode = APR_UREAD | APR_UWRITE;
+ /* GridSiteDiskMode group-mode world-mode
+ GroupNone | GroupRead | GroupWrite WorldNone | WorldRead */
+ }
+ else
+ {
+ conf->auth = UNSET; /* GridSiteAuth on/off */
+ conf->autopasscode = UNSET; /* GridSiteAutoPasscode on/off */
+ conf->requirepasscode = UNSET; /* GridSiteRequirePasscode on/off */
+ conf->zoneslashes = UNSET; /* GridSiteZoneSlashes number */
+ conf->envs = UNSET; /* GridSiteEnvs on/off */
+ conf->format = UNSET; /* GridSiteHtmlFormat on/off */
+ conf->indexes = UNSET; /* GridSiteIndexes on/off */
+ conf->indexheader = NULL; /* GridSiteIndexHeader File-value */
+ conf->gridsitelink = UNSET; /* GridSiteLink on/off */
+ conf->adminfile = NULL; /* GridSiteAdminFile File-value */
+ conf->adminuri = NULL; /* GridSiteAdminURI URI-value */
+ conf->helpuri = NULL; /* GridSiteHelpURI URI-value */
+ conf->loginuri = NULL; /* GridSiteLoginURI URI-value */
+ conf->dnlists = NULL; /* GridSiteDNlists Search-path */
+ conf->dnlistsuri = NULL; /* GridSiteDNlistsURI URI-value */
+ conf->adminlist = NULL; /* GridSiteAdminList URI-value */
+ conf->gsiproxylimit = UNSET; /* GridSiteGSIProxyLimit number */
+ conf->unzip = NULL; /* GridSiteUnzip file-path */
+ conf->methods = NULL; /* GridSiteMethods methods */
+ conf->editable = NULL; /* GridSiteEditable types */
+ conf->headfile = NULL; /* GridSiteHeadFile file name */
+ conf->footfile = NULL; /* GridSiteFootFile file name */
+ conf->gridhttp = UNSET; /* GridSiteGridHTTP on/off */
+ conf->aclformat = NULL; /* GridSiteACLFormat gacl/xacml */
+ conf->aclpath = NULL; /* GridSiteACLPath acl-path */
+ conf->delegationuri = NULL; /* GridSiteDelegationURI URI-value */
+ conf->execmethod = NULL; /* GridSiteExecMethod */
+ conf->execugid.uid = UNSET; /* GridSiteUserGroup User Group */
+ conf->execugid.gid = UNSET; /* ditto */
+ conf->execugid.userdir = UNSET; /* ditto */
+ conf->diskmode = UNSET; /* GridSiteDiskMode group world */
+ }
+
+ return conf;
+}
+
+static void *merge_gridsite_dir_config(apr_pool_t *p, void *vserver,
+ void *vdirect)
+/* merge directory with server-wide directory configs */
+{
+ mod_gridsite_dir_cfg *conf, *server, *direct;
+
+ server = (mod_gridsite_dir_cfg *) vserver;
+ direct = (mod_gridsite_dir_cfg *) vdirect;
+ conf = apr_palloc(p, sizeof(*conf));
+
+ if (direct->auth != UNSET) conf->auth = direct->auth;
+ else conf->auth = server->auth;
+
+ if (direct->autopasscode != UNSET) conf->autopasscode = direct->autopasscode;
+ else conf->autopasscode = server->autopasscode;
+
+ if (direct->requirepasscode != UNSET) conf->requirepasscode = direct->requirepasscode;
+ else conf->requirepasscode = server->requirepasscode;
+
+ if (direct->zoneslashes != UNSET) conf->zoneslashes = direct->zoneslashes;
+ else conf->zoneslashes = server->zoneslashes;
+
+ if (direct->envs != UNSET) conf->envs = direct->envs;
+ else conf->envs = server->envs;
+
+ if (direct->format != UNSET) conf->format = direct->format;
+ else conf->format = server->format;
+
+ if (direct->indexes != UNSET) conf->indexes = direct->indexes;
+ else conf->indexes = server->indexes;
+
+ if (direct->gridsitelink != UNSET) conf->gridsitelink=direct->gridsitelink;
+ else conf->gridsitelink=server->gridsitelink;
+
+ if (direct->indexheader != NULL) conf->indexheader = direct->indexheader;
+ else conf->indexheader = server->indexheader;
+
+ if (direct->adminfile != NULL) conf->adminfile = direct->adminfile;
+ else conf->adminfile = server->adminfile;
+
+ if (direct->adminuri != NULL) conf->adminuri = direct->adminuri;
+ else conf->adminuri = server->adminuri;
+
+ if (direct->helpuri != NULL) conf->helpuri = direct->helpuri;
+ else conf->helpuri = server->helpuri;
+
+ if (direct->loginuri != NULL) conf->loginuri = direct->loginuri;
+ else conf->loginuri = server->loginuri;
+
+ if (direct->dnlists != NULL) conf->dnlists = direct->dnlists;
+ else conf->dnlists = server->dnlists;
+
+ if (direct->dnlistsuri != NULL) conf->dnlistsuri = direct->dnlistsuri;
+ else conf->dnlistsuri = server->dnlistsuri;
+
+ if (direct->adminlist != NULL) conf->adminlist = direct->adminlist;
+ else conf->adminlist = server->adminlist;
+
+ if (direct->gsiproxylimit != UNSET)
+ conf->gsiproxylimit = direct->gsiproxylimit;
+ else conf->gsiproxylimit = server->gsiproxylimit;
+
+ if (direct->unzip != NULL) conf->unzip = direct->unzip;
+ else conf->unzip = server->unzip;
+
+ if (direct->methods != NULL) conf->methods = direct->methods;
+ else conf->methods = server->methods;
+
+ if (direct->editable != NULL) conf->editable = direct->editable;
+ else conf->editable = server->editable;
+
+ if (direct->headfile != NULL) conf->headfile = direct->headfile;
+ else conf->headfile = server->headfile;
+
+ if (direct->footfile != NULL) conf->footfile = direct->footfile;
+ else conf->footfile = server->footfile;
+
+ if (direct->gridhttp != UNSET) conf->gridhttp = direct->gridhttp;
+ else conf->gridhttp = server->gridhttp;
+
+ if (direct->aclformat != NULL) conf->aclformat = direct->aclformat;
+ else conf->aclformat = server->aclformat;
+
+ if (direct->aclpath != NULL) conf->aclpath = direct->aclpath;
+ else conf->aclpath = server->aclpath;
+
+ if (direct->delegationuri != NULL) conf->delegationuri = direct->delegationuri;
+ else conf->delegationuri = server->delegationuri;
+
+ if (direct->execmethod != NULL) conf->execmethod = direct->execmethod;
+ else conf->execmethod = server->execmethod;
+
+ if (direct->execugid.uid != UNSET)
+ { conf->execugid.uid = direct->execugid.uid;
+ conf->execugid.gid = direct->execugid.gid;
+ conf->execugid.userdir = direct->execugid.userdir; }
+ else
+ { conf->execugid.uid = server->execugid.uid;
+ conf->execugid.gid = server->execugid.gid;
+ conf->execugid.userdir = server->execugid.userdir; }
+
+ if (direct->diskmode != UNSET) conf->diskmode = direct->diskmode;
+ else conf->diskmode = server->diskmode;
+
+ return conf;
+}
+
+static const char *mod_gridsite_take1_cmds(cmd_parms *a, void *cfg,
+ const char *parm)
+{
+ int n, i;
+ char *p;
+
+ if (strcasecmp(a->cmd->name, "GridSiteSessionsDir") == 0)
+ {
+ if (a->server->is_virtual)
+ return "GridSiteSessionsDir cannot be used inside a virtual server";
+
+ sessionsdir = apr_pstrdup(a->pool, parm);
+ }
+/* GridSiteOnetimesDir is deprecated in favour of GridSiteSessionsDir */
+ else if (strcasecmp(a->cmd->name, "GridSiteOnetimesDir") == 0)
+ {
+ if (a->server->is_virtual)
+ return "GridSiteOnetimesDir cannot be used inside a virtual server";
+
+ sessionsdir = apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteZoneSlashes") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->zoneslashes = atoi(parm);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->zoneslashes < 1)
+ return "GridSiteZoneSlashes must be greater than 0";
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteGridHTTPport") == 0)
+ {
+ gridhttpport = atoi(parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteCastDNlists") == 0)
+ {
+ if (a->server->is_virtual)
+ return "GridSiteDNlists cannot be used inside a virtual server";
+
+ sitecastdnlists = apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteCastUniPort") == 0)
+ {
+ if (a->server->is_virtual)
+ return "GridSiteCastUniPort cannot be used inside a virtual server";
+
+ if (sscanf(parm, "%d", &(sitecastgroups[0].port)) != 1)
+ return "Failed parsing GridSiteCastUniPort numeric value";
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteCastGroup") == 0)
+ {
+ if (a->server->is_virtual)
+ return "GridSiteCastGroup cannot be used inside a virtual server";
+
+ for (i=1; i <= GRST_SITECAST_GROUPS; ++i)
+ {
+ if (sitecastgroups[i].port == 0) /* a free slot */
+ {
+ sitecastgroups[i].port = GRST_HTCP_PORT;
+
+ if (sscanf(parm, "%s:%d",
+ &(sitecastgroups[i].address),
+ &(sitecastgroups[i].port)) < 1)
+ return "Failed parsing GridSiteCastGroup";
+
+ break;
+ }
+ }
+
+ if (i > GRST_SITECAST_GROUPS)
+ return "Maximum GridSiteCastGroup groups reached";
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteAdminFile") == 0)
+ {
+ if (index(parm, '/') != NULL)
+ return "/ not permitted in GridSiteAdminFile";
+
+ ((mod_gridsite_dir_cfg *) cfg)->adminfile =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteAdminURI") == 0)
+ {
+ if (*parm != '/') return "GridSiteAdminURI must begin with /";
+
+ ((mod_gridsite_dir_cfg *) cfg)->adminuri =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteHelpURI") == 0)
+ {
+ if (*parm != '/') return "GridSiteHelpURI must begin with /";
+
+ ((mod_gridsite_dir_cfg *) cfg)->helpuri =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteDNlists") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->dnlists =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteDNlistsURI") == 0)
+ {
+ if (*parm != '/') return "GridSiteDNlistsURI must begin with /";
+
+ if ((*parm != '\0') && (parm[strlen(parm) - 1] == '/'))
+ ((mod_gridsite_dir_cfg *) cfg)->dnlistsuri =
+ apr_pstrdup(a->pool, parm);
+ else
+ ((mod_gridsite_dir_cfg *) cfg)->dnlistsuri =
+ apr_pstrcat(a->pool, parm, "/", NULL);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteAdminList") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->adminlist =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteGSIProxyLimit") == 0)
+ {
+ n = -1;
+
+ if ((sscanf(parm, "%d", &n) == 1) && (n >= 0)) {
+ if (n == 0)
+ n = 1000; /* thousand is an African for "unlimited" */
+ ((mod_gridsite_dir_cfg *) cfg)->gsiproxylimit = n;
+ }
+ else return "GridSiteGSIProxyLimit must be a number >= 0";
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteUnzip") == 0)
+ {
+ if (*parm != '/') return "GridSiteUnzip must begin with /";
+
+ ((mod_gridsite_dir_cfg *) cfg)->unzip =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteMethods") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->methods =
+ apr_psprintf(a->pool, " %s ", parm);
+
+ for (p = ((mod_gridsite_dir_cfg *) cfg)->methods;
+ *p != '\0';
+ ++p) if (*p == '\t') *p = ' ';
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteOCSP") == 0)
+ {
+ ocspmodes = apr_psprintf(a->pool, " %s ", parm);
+
+ for (p = ocspmodes; *p != '\0'; ++p)
+ if (*p == '\t') *p = ' ';
+ else *p = tolower(*p);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteEditable") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->editable =
+ apr_psprintf(a->pool, " %s ", parm);
+
+ for (p = ((mod_gridsite_dir_cfg *) cfg)->editable;
+ *p != '\0';
+ ++p) if (*p == '\t') *p = ' ';
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteHeadFile") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->headfile =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteFootFile") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->footfile =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteIndexHeader") == 0)
+ {
+ if (index(parm, '/') != NULL)
+ return "/ not permitted in GridSiteIndexHeader";
+
+ ((mod_gridsite_dir_cfg *) cfg)->indexheader =
+ apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteACLFormat") == 0)
+ {
+ if ((strcasecmp(parm,"GACL") != 0) &&
+ (strcasecmp(parm,"XACML") != 0))
+ return "GridsiteACLFormat must be either GACL or XACML";
+
+ ((mod_gridsite_dir_cfg *) cfg)->aclformat = apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteACLPath") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->aclpath = apr_pstrdup(a->pool, parm);
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteDelegationURI") == 0)
+ {
+ if (*parm != '/') return "GridSiteDelegationURI must begin with /";
+
+ if (*parm != '\0')
+ ((mod_gridsite_dir_cfg *) cfg)->delegationuri =
+ apr_pstrdup(a->pool, parm);
+
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteExecMethod") == 0)
+ {
+ if (strcasecmp(parm, "nosetuid") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->execmethod = NULL;
+ return NULL;
+ }
+
+ if ((strcasecmp(parm, "suexec") != 0) &&
+ (strcasecmp(parm, "X509DN") != 0) &&
+ (strcasecmp(parm, "directory") != 0))
+ return "GridsiteExecMethod must be nosetuid, suexec, X509DN or directory";
+
+ ((mod_gridsite_dir_cfg *) cfg)->execmethod = apr_pstrdup(a->pool, parm);
+ }
+
+ return NULL;
+}
+
+static const char *mod_gridsite_take2_cmds(cmd_parms *a, void *cfg,
+ const char *parm1, const char *parm2)
+{
+ int i;
+ char *p, *q, buf[APRMAXHOSTLEN + 1] = "localhost";
+
+ if (strcasecmp(a->cmd->name, "GridSiteUserGroup") == 0)
+ {
+ if (!(unixd_config.suexec_enabled))
+ return "Using GridSiteUserGroup will "
+ "require rebuilding Apache with suexec support!";
+
+ /* NB ap_uname2id/ap_gname2id are NOT thread safe - but OK
+ as long as not used in .htaccess, just at server start time */
+
+ ((mod_gridsite_dir_cfg *) cfg)->execugid.uid = ap_uname2id(parm1);
+ ((mod_gridsite_dir_cfg *) cfg)->execugid.gid = ap_gname2id(parm2);
+ ((mod_gridsite_dir_cfg *) cfg)->execugid.userdir = 0;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteDiskMode") == 0)
+ {
+ if ((strcasecmp(parm1, "GroupNone" ) != 0) &&
+ (strcasecmp(parm1, "GroupRead" ) != 0) &&
+ (strcasecmp(parm1, "GroupWrite") != 0))
+ return "First parameter of GridSiteDiskMode must be "
+ "GroupNone, GroupRead or GroupWrite!";
+
+ if ((strcasecmp(parm2, "WorldNone" ) != 0) &&
+ (strcasecmp(parm2, "WorldRead" ) != 0))
+ return "Second parameter of GridSiteDiskMode must be "
+ "WorldNone or WorldRead!";
+
+ ((mod_gridsite_dir_cfg *) cfg)->diskmode =
+ APR_UREAD | APR_UWRITE
+ | ( APR_GREAD * (strcasecmp(parm1, "GroupRead") == 0))
+ | ((APR_GREAD | APR_GWRITE) * (strcasecmp(parm1, "GroupWrite") == 0))
+ | ((APR_GREAD | APR_WREAD) * (strcasecmp(parm2, "WorldRead") == 0));
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteCastAlias") == 0)
+ {
+ if ((parm1[strlen(parm1)-1] != '/') || (parm2[strlen(parm2)-1] != '/'))
+ return "GridSiteCastAlias URL and path must end with /";
+
+ for (i=0; i < GRST_SITECAST_ALIASES; ++i) /* look for free slot */
+ {
+ if (sitecastaliases[i].sitecast_url == NULL)
+ {
+ sitecastaliases[i].scheme = apr_pstrdup(a->pool, parm1);
+
+ if (((p = index(sitecastaliases[i].scheme, ':')) == NULL)
+ || (p[1] != '/') || (p[2] != '/'))
+ return "GridSiteCastAlias URL must begin with scheme (http/https/gsiftp/...) and ://";
+
+ *p = '\0';
+ ++p;
+ while (*p == '/') ++p;
+
+ if ((q = index(p, '/')) == NULL)
+ return "GridSiteCastAlias URL must be of form scheme://domain:port/dirs";
+
+ *q = '\0';
+
+ p = index(p, ':');
+ if (p == NULL)
+ {
+ return "GridSiteCastAlias URL must include the port number";
+ }
+
+ if (sscanf(p, ":%d", &(sitecastaliases[i].port)) != 1)
+ return "Unable to parse numeric port number in GridSiteCastAlias";
+
+ sitecastaliases[i].sitecast_url = apr_pstrdup(a->pool, parm1);
+ sitecastaliases[i].local_path = apr_pstrdup(a->pool, parm2);
+
+ if (a->server->server_hostname == NULL)
+ {
+ apr_gethostname(buf, APRMAXHOSTLEN + 1, a->pool);
+ sitecastaliases[i].local_hostname = apr_pstrdup(a->pool, buf);
+ }
+ else sitecastaliases[i].local_hostname = apr_pstrdup(a->pool,
+ a->server->server_hostname);
+
+ break;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static const char *mod_gridsite_flag_cmds(cmd_parms *a, void *cfg,
+ int flag)
+{
+ if (strcasecmp(a->cmd->name, "GridSiteAuth") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->auth = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteAutoPasscode") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->autopasscode = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteRequirePasscode") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->requirepasscode = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteEnvs") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->envs = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteHtmlFormat") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->format = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteIndexes") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->indexes = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteLink") == 0)
+ {
+ ((mod_gridsite_dir_cfg *) cfg)->gridsitelink = flag;
+ }
+ else if (strcasecmp(a->cmd->name, "GridSiteGridHTTP") == 0)
+ {
+// TODO: return error if try this on non-HTTPS virtual server
+
+ ((mod_gridsite_dir_cfg *) cfg)->gridhttp = flag;
+ }
+
+ return NULL;
+}
+
+static const command_rec mod_gridsite_cmds[] =
+{
+// TODO: need to check and document valid contexts for each command!
+
+ AP_INIT_FLAG("GridSiteAuth", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteAutoPasscode", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteRequirePasscode", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteEnvs", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteHtmlFormat", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteIndexes", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_FLAG("GridSiteLink", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+
+ AP_INIT_TAKE1("GridSiteAdminFile", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "Ghost per-directory admin CGI"),
+ AP_INIT_TAKE1("GridSiteAdminURI", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI of real gridsite-admin.cgi"),
+ AP_INIT_TAKE1("GridSiteHelpURI", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI of Website Help pages"),
+ AP_INIT_TAKE1("GridSiteLoginURI", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI prefix of login/logout page"),
+ AP_INIT_TAKE1("GridSiteDNlists", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "DN Lists directories search path"),
+ AP_INIT_TAKE1("GridSiteDNlistsURI", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI of published DN lists"),
+ AP_INIT_TAKE1("GridSiteAdminList", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI of admin DN List"),
+ AP_INIT_TAKE1("GridSiteGSIProxyLimit", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "Max level of GSI proxy validity"),
+ AP_INIT_TAKE1("GridSiteUnzip", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "Absolute path to unzip command"),
+
+ AP_INIT_RAW_ARGS("GridSiteMethods", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "permitted HTTP methods"),
+ AP_INIT_RAW_ARGS("GridSiteOCSP", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "Set OCSP lookups"),
+ AP_INIT_RAW_ARGS("GridSiteEditable", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "editable file extensions"),
+ AP_INIT_TAKE1("GridSiteHeadFile", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "filename of HTML header"),
+ AP_INIT_TAKE1("GridSiteFootFile", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "filename of HTML footer"),
+ AP_INIT_TAKE1("GridSiteIndexHeader", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "filename of directory header"),
+
+ AP_INIT_FLAG("GridSiteGridHTTP", mod_gridsite_flag_cmds,
+ NULL, OR_FILEINFO, "on or off"),
+ AP_INIT_TAKE1("GridSiteGridHTTPport", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "GridHTTP port"),
+ AP_INIT_TAKE1("GridSiteSessionsDir", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "directory with GridHTTP passcodes and SSL session creds"),
+/* GridSiteOnetimesDir is deprecated in favour of GridSiteSessionsDir */
+ AP_INIT_TAKE1("GridSiteOnetimesDir", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "directory with GridHTTP passcodes"),
+ AP_INIT_TAKE1("GridSiteZoneSlashes", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "number of slashes in passcode cookie paths"),
+
+ AP_INIT_TAKE1("GridSiteCastDNlists", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "DN Lists directories search path for SiteCast"),
+ AP_INIT_TAKE1("GridSiteCastUniPort", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "UDP port for unicast/replies"),
+ AP_INIT_TAKE1("GridSiteCastGroup", mod_gridsite_take1_cmds,
+ NULL, RSRC_CONF, "multicast group[:port] to listen for HTCP on"),
+ AP_INIT_TAKE2("GridSiteCastAlias", mod_gridsite_take2_cmds,
+ NULL, RSRC_CONF, "URL and local path mapping"),
+
+ AP_INIT_TAKE1("GridSiteACLFormat", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "format to save access control lists in"),
+ AP_INIT_TAKE1("GridSiteACLPath", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "explicit location of access control file"),
+
+ AP_INIT_TAKE1("GridSiteDelegationURI", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "URI of the delegation service CGI"),
+
+ AP_INIT_TAKE1("GridSiteExecMethod", mod_gridsite_take1_cmds,
+ NULL, OR_FILEINFO, "execution strategy used by gsexec"),
+
+ AP_INIT_TAKE2("GridSiteUserGroup", mod_gridsite_take2_cmds,
+ NULL, OR_FILEINFO,
+ "user and group of gsexec processes in suexec mode"),
+
+ AP_INIT_TAKE2("GridSiteDiskMode", mod_gridsite_take2_cmds,
+ NULL, OR_FILEINFO,
+ "group and world file modes for new files/directories"),
+
+ {NULL}
+};
+
+/* Blank unset these HTTP headers, to prevent injection attacks.
+ This is run before mod_shib's check_user_id hook, which may
+ legitimately create such headers. */
+
+static int mod_gridsite_check_user_id(request_rec *r)
+{
+ apr_table_unset(r->headers_in, "User-Distinguished-Name");
+#if 0
+ apr_table_unset(r->headers_in, "User-Distinguished-Name-2");
+#endif
+ apr_table_unset(r->headers_in, "Nist-LoA");
+ apr_table_unset(r->headers_in, "LoA");
+ apr_table_unset(r->headers_in, "VOMS-Attribute");
+
+ return DECLINED; /* ie carry on processing request */
+}
+
+static int mod_gridsite_first_fixups(request_rec *r)
+{
+ mod_gridsite_dir_cfg *conf;
+
+ if (r->finfo.filetype != APR_DIR) return DECLINED;
+
+ conf = (mod_gridsite_dir_cfg *)
+ ap_get_module_config(r->per_dir_config, &gridsite_module);
+
+ /* we handle DN Lists as regular files, even if they also match
+ directory names */
+
+ if ((conf != NULL) &&
+ (conf->dnlistsuri != NULL) &&
+ (strncmp(r->uri, conf->dnlistsuri, strlen(conf->dnlistsuri)) == 0) &&
+ (strcmp(r->uri, conf->dnlistsuri) != 0))
+ {
+ r->finfo.filetype = APR_REG;
+ }
+
+ return DECLINED;
+}
+
+
+int GRST_get_session_id(SSL *ssl, char *session_id, size_t len)
+{
+ int i;
+ SSL_SESSION *session;
+
+ if (((session = SSL_get_session(ssl)) == NULL) ||
+ (session->session_id_length == 0)) return GRST_RET_FAILED;
+
+ if (2 * session->session_id_length + 1 > len) return GRST_RET_FAILED;
+
+ for (i=0; i < (int) session->session_id_length; ++i)
+ sprintf(&(session_id[i*2]), "%02X", (unsigned char) session->session_id[i]);
+
+ session_id[i*2] = '\0';
+
+ return GRST_RET_OK;
+}
+
+int GRST_load_ssl_creds(SSL *ssl, conn_rec *conn)
+{
+ char session_id[(SSL_MAX_SSL_SESSION_ID_LENGTH+1)*2+1], *sessionfile = NULL,
+ line[512], *p;
+ apr_file_t *fp = NULL;
+ int i;
+
+ if (GRST_get_session_id(ssl, session_id, sizeof(session_id)) != GRST_RET_OK)
+ return GRST_RET_FAILED;
+
+ sessionfile = apr_psprintf(conn->pool, "%s/sslcreds-%s",
+ ap_server_root_relative(conn->pool, sessionsdir),
+ session_id);
+
+ if (apr_file_open(&fp, sessionfile, APR_READ, 0, conn->pool) != APR_SUCCESS)
+ return GRST_RET_FAILED;
+
+ while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS)
+ {
+ if (sscanf(line, "GRST_CRED_AURI_%d=", &i) == 1)
+ {
+ if ((p = index(line, '\n')) != NULL) *p = '\0';
+ p = index(line, '=');
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_AURI_%d", i),
+ apr_pstrdup(conn->pool, &p[1]));
+ }
+ else if (sscanf(line, "GRST_CRED_VALID_%d=", &i) == 1)
+ {
+ if ((p = index(line, '\n')) != NULL) *p = '\0';
+ p = index(line, '=');
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_VALID_%d", i),
+ apr_pstrdup(conn->pool, &p[1]));
+ }
+ else if (sscanf(line, "GRST_OCSP_URL_%d=", &i) == 1)
+ {
+ if ((p = index(line, '\n')) != NULL) *p = '\0';
+ p = index(line, '=');
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_OCSP_URL_%d", i),
+ apr_pstrdup(conn->pool, &p[1]));
+ }
+ }
+
+ apr_file_close(fp);
+
+ /* connection notes created by GRST_save_ssl_creds() are now reloaded */
+ apr_table_set(conn->notes, "GRST_save_ssl_creds", "yes");
+
+ return GRST_RET_OK;
+}
+
+/*
+ Save result of AURIs and validity info from chain into connection notes,
+ and write out in an SSL session creds file.
+*/
+
+void GRST_save_ssl_creds(conn_rec *conn, GRSTx509Chain *grst_chain)
+{
+ int i, lowest_voms_delegation = 65535;
+ char *tempfile = NULL, *encoded,
+ *sessionfile, session_id[(SSL_MAX_SSL_SESSION_ID_LENGTH+1)*2];
+ apr_file_t *fp = NULL;
+ SSL *ssl;
+ SSLConnRec *sslconn;
+ GRSTx509Cert *grst_cert = NULL;
+
+ /* check if already done */
+
+ if ((grst_chain != NULL) && (conn->notes != NULL) &&
+ (apr_table_get(conn->notes, "GRST_save_ssl_creds") != NULL)) return;
+
+ /* we at least need to say we've been run - even if creds not save-able*/
+
+ apr_table_set(conn->notes, "GRST_save_ssl_creds", "yes");
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, conn->base_server,
+ "set GRST_save_ssl_creds");
+
+ sslconn = (SSLConnRec *)ap_get_module_config(conn->conn_config,&ssl_module);
+
+ if ((sslconn != NULL) &&
+ ((ssl = sslconn->ssl) != NULL) &&
+ (GRST_get_session_id(ssl,session_id,sizeof(session_id)) == GRST_RET_OK))
+ {
+ sessionfile = apr_psprintf(conn->pool, "%s/sslcreds-%s",
+ ap_server_root_relative(conn->pool, sessionsdir),
+ session_id);
+
+ tempfile = apr_pstrcat(conn->pool,
+ ap_server_root_relative(conn->pool, sessionsdir),
+ "/tmp-XXXXXX", NULL);
+
+ if ((tempfile != NULL) && (tempfile[0] != '\0'))
+ apr_file_mktemp(&fp, tempfile,
+ APR_CREATE | APR_WRITE | APR_EXCL, conn->pool);
+ }
+
+ i=0;
+
+ for (grst_cert = grst_chain->firstcert;
+ grst_cert != NULL; grst_cert = grst_cert->next)
+ {
+ if (grst_cert->errors) continue;
+
+ if (grst_cert->type == GRST_CERT_TYPE_VOMS)
+ {
+ /* want to record the delegation level
+ of the last proxy with VOMS attributes */
+
+ lowest_voms_delegation = grst_cert->delegation;
+ }
+ else if ((grst_cert->type == GRST_CERT_TYPE_EEC) ||
+ (grst_cert->type == GRST_CERT_TYPE_PROXY))
+ {
+ encoded = GRSThttpUrlMildencode(grst_cert->dn);
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_AURI_%d", i),
+ apr_pstrcat(conn->pool, "dn:", encoded, NULL));
+
+ if (fp != NULL) apr_file_printf(fp, "GRST_CRED_AURI_%d=dn:%s\n",
+ i, encoded);
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_VALID_%d", i),
+ apr_psprintf(conn->pool,
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ grst_cert->notbefore,
+ grst_cert->notafter,
+ grst_cert->delegation, 0));
+
+ if (fp != NULL) apr_file_printf(fp,
+ "GRST_CRED_VALID_%d=notbefore=%ld notafter=%ld delegation=%d nist-loa=%d\n",
+ i, grst_cert->notbefore,
+ grst_cert->notafter,
+ grst_cert->delegation, 0);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, conn->base_server,
+ "store GRST_CRED_AURI_%d=dn:%s", i, encoded);
+
+ free(encoded);
+
+ ++i;
+ }
+ }
+
+ for (grst_cert = grst_chain->firstcert;
+ grst_cert != NULL; grst_cert = grst_cert->next)
+ {
+ if (grst_cert->errors) continue;
+
+ if ((grst_cert->type == GRST_CERT_TYPE_VOMS) &&
+ (grst_cert->delegation == lowest_voms_delegation))
+ {
+ /* only export attributes from the last proxy to contain them */
+
+ encoded = GRSThttpUrlMildencode(grst_cert->value);
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_AURI_%d", i),
+ apr_pstrcat(conn->pool, "fqan:", encoded, NULL));
+
+ if (fp != NULL) apr_file_printf(fp, "GRST_CRED_AURI_%d=fqan:%s\n",
+ i, encoded);
+
+ apr_table_setn(conn->notes,
+ apr_psprintf(conn->pool, "GRST_CRED_VALID_%d", i),
+ apr_psprintf(conn->pool,
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ grst_cert->notbefore,
+ grst_cert->notafter,
+ grst_cert->delegation, 0));
+
+ if (fp != NULL) apr_file_printf(fp,
+ "GRST_CRED_VALID_%d=notbefore=%ld notafter=%ld delegation=%d nist-loa=%d\n",
+ i, grst_cert->notbefore,
+ grst_cert->notafter,
+ grst_cert->delegation, 0);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, conn->base_server,
+ "store GRST_CRED_AURI_%d=fqan:%s", i, encoded);
+
+ free(encoded);
+
+ ++i;
+ }
+ }
+
+ /* this needs to be merged into grst_x509? */
+#if 0
+ if (ocspmodes != NULL)
+ {
+ int j;
+ const char *ex_sn;
+ char s[80];
+ X509 *cert;
+ X509_EXTENSION *ex;
+
+ for (j=sk_X509_num(certstack)-1; j >= 0; --j)
+ {
+ cert = sk_X509_value(certstack, j);
+
+ for (i=0; i < X509_get_ext_count(cert); ++i)
+ {
+ ex = X509_get_ext(cert, i);
+
+ OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 0);
+
+ if (strcmp(s, "authorityInfoAccess") == 0) /* OCSP */
+ {
+ apr_table_setn(conn->notes, "GRST_OCSP_URL",
+ (const char *) X509_EXTENSION_get_data(ex));
+
+ /* strategy is to remove what has been checked,
+ for this connnection */
+ apr_table_set(conn->notes, "GRST_OCSP_UNCHECKED",
+ ocspmodes);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, conn->base_server,
+ "store GRST_OCSP_URL_%d=%s", i, X509_EXTENSION_get_data(ex));
+
+ if (fp != NULL) apr_file_printf(fp, "GRST_OCSP_URL_%d=%s\n",
+ i, X509_EXTENSION_get_data(ex));
+ }
+ }
+ }
+ }
+#endif
+ /* end of bit that needs to go into grst_x509 */
+
+ if (fp != NULL)
+ {
+ apr_file_close(fp);
+ apr_file_rename(tempfile, sessionfile, conn->pool);
+ }
+}
+
+static char *get_aclpath_component(request_rec *r, int n)
+/*
+ Get the nth component of REQUEST_URI, or component 0
+ which is the server name.
+
+*/
+{
+ int ii, i, nn;
+
+ if (n == 0) return r->server->server_hostname;
+
+ if (r->uri == NULL) return NULL; /* some kind of internal error? */
+
+ i = 1; /* start of first component */
+ nn = 1;
+
+ for (ii=1; r->uri[ii] != '\0'; ++ii) /* look for this component */
+ {
+ if (r->uri[ii] == '/') /* end of a component */
+ {
+ if (nn == n) break;
+
+ ++nn;
+ i = ii + 1;
+ }
+ else if ((r->uri[ii] == '.') && (r->uri[ii+1] == '.'))
+ {
+ return NULL; /* can this happen? dont allow anyway */
+ }
+ }
+
+ if (nn != n) return NULL; /* no component for this number */
+
+ return apr_psprintf(r->pool, "%.*s", ii - i, &(r->uri[i]));
+}
+
+static char *make_aclpath(request_rec *r, char *format)
+{
+ int i, n;
+ char *formatted, *p;
+
+ formatted = apr_pstrdup(r->pool, format);
+
+ while (1)
+ {
+ for (i=0; (formatted[i] != '\0') && (formatted[i] != '%'); ++i) ;
+
+ if (formatted[i] == '\0') break;
+
+ if ((formatted[i] == '%') && (formatted[i+1] == '%'))
+ {
+ ++i;
+ continue;
+ }
+
+ if (sscanf(&formatted[i+1], "%d", &n) != 1)
+ {
+ return NULL; /* not %% or %0,%1,... */
+ }
+
+ formatted[i] = '\0';
+
+ for (i++; isdigit(formatted[i]); ++i) ;
+
+ if ((p = get_aclpath_component(r, n)) == NULL) return NULL;
+
+ formatted = apr_pstrcat(r->pool, formatted, p, &formatted[i],NULL);
+ i += strlen(p);
+ }
+
+ return ap_server_root_relative(r->pool, formatted);
+}
+
+static int mod_gridsite_perm_handler(request_rec *r)
+/*
+ Do authentication/authorization here rather than in the normal module
+ auth functions since the results of mod_ssl are available.
+
+ We also publish environment variables here if requested by GridSiteEnv.
+*/
+{
+ int retcode = DECLINED, i, j, n, file_is_acl = 0, cc_delegation,
+ destination_is_acl = 0, ishttps = 0, nist_loa, delegation,
+ from_cookie = 0;
+ char *p, *q, envname1[30], envname2[30],
+ *grst_cred_auri_0 = NULL, *dir_path,
+ *remotehost, *grst_cred_auri_i, *cookies, *file,
+ *cookiefile, oneline[1025], *decoded,
+ *destination = NULL, *destination_uri = NULL, *querytmp,
+ *destination_prefix = NULL, *destination_translated = NULL,
+ *aclpath = NULL, *grst_cred_valid_0 = NULL, *grst_cred_valid_i,
+ *gridauthpasscode = NULL;
+ const char *content_type;
+ time_t notbefore, notafter;
+ apr_table_t *env;
+ apr_finfo_t cookiefile_info;
+ apr_file_t *fp;
+ request_rec *destreq;
+ GRSTgaclCred *cred = NULL, *cred_0 = NULL;
+ GRSTgaclUser *user = NULL;
+ GRSTgaclPerm perm = GRST_PERM_NONE, destination_perm = GRST_PERM_NONE;
+ GRSTgaclAcl *acl = NULL;
+ mod_gridsite_dir_cfg *cfg;
+ SSLConnRec *sslconn;
+
+ cfg = (mod_gridsite_dir_cfg *)
+ ap_get_module_config(r->per_dir_config, &gridsite_module);
+
+ if (cfg == NULL) return DECLINED;
+
+ if ((cfg->auth == 0) && (cfg->envs == 0))
+ return DECLINED; /* if not turned on, look invisible */
+
+ env = r->subprocess_env;
+
+ p = (char *) apr_table_get(env, "HTTPS");
+ if ((p != NULL) && (strcmp(p, "on") == 0)) ishttps = 1;
+
+ delegation = ((mod_gridsite_dir_cfg *) cfg)->gsiproxylimit + 1;
+
+ /* reload per-connection (SSL) cred variables? (TO CONNECTION) */
+
+ sslconn = (SSLConnRec *) ap_get_module_config(r->connection->conn_config,
+ &ssl_module);
+ if ((user == NULL) &&
+ (sslconn != NULL) &&
+ (sslconn->ssl != NULL) &&
+ (sslconn->ssl->session != NULL) &&
+ (r->connection->notes != NULL) &&
+ (apr_table_get(r->connection->notes, "GRST_save_ssl_creds") == NULL))
+ {
+ if (GRST_load_ssl_creds(sslconn->ssl, r->connection) == GRST_RET_OK)
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Restored SSL session data from session cache file");
+ }
+
+ /* look for GRIDHTTP_PASSCODE in QUERY_STRING ie after ? */
+
+ if ((r->parsed_uri.query != NULL) && (r->parsed_uri.query[0] != '\0'))
+ {
+ querytmp = apr_pstrcat(r->pool,"&",r->parsed_uri.query,"&",NULL);
+
+ gridauthpasscode = strstr(querytmp, "&GRIDHTTP_PASSCODE=");
+ if (gridauthpasscode != NULL)
+ {
+ gridauthpasscode = &gridauthpasscode[19];
+
+ for (p = gridauthpasscode; (*p != '\0') && (*p != '&'); ++p)
+ if (!isalnum(*p)) *p = '\0';
+ }
+ }
+
+ /* then look for GRIDHTTP_PASSCODE cookie */
+
+ if ((gridauthpasscode == NULL) &&
+ ((q = (char *) apr_table_get(r->headers_in, "Cookie")) != NULL))
+ {
+ cookies = apr_pstrcat(r->pool, " ", q, NULL);
+ gridauthpasscode = strstr(cookies, " GRIDHTTP_PASSCODE=");
+
+ if (gridauthpasscode != NULL)
+ {
+ gridauthpasscode = &gridauthpasscode[19];
+
+ for (p = gridauthpasscode;
+ (*p != '\0') && (*p != ';'); ++p)
+ if (!isalnum(*p)) *p = '\0';
+
+ if (gridauthpasscode[0] != '\0') from_cookie = 1;
+ }
+ }
+
+ /* try to load user structure from passcode file */
+
+ if ((user == NULL) &&
+ (gridauthpasscode != NULL) &&
+ (gridauthpasscode[0] != '\0'))
+ {
+ cookiefile = apr_psprintf(r->pool, "%s/passcode-%s",
+ ap_server_root_relative(r->pool,
+ sessionsdir),
+ gridauthpasscode);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Opening GridHTTP passcode file %s", cookiefile);
+
+ if ((apr_stat(&cookiefile_info, cookiefile,
+ APR_FINFO_TYPE, r->pool) == APR_SUCCESS) &&
+ (cookiefile_info.filetype == APR_REG) &&
+ (apr_file_open(&fp, cookiefile, APR_READ, 0, r->pool)
+ == APR_SUCCESS))
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Reading GridHTTP passcode file %s", cookiefile);
+
+ i = -1;
+ cred = NULL;
+
+ while (apr_file_gets(oneline,
+ sizeof(oneline), fp) == APR_SUCCESS)
+ {
+ p = index(oneline, '\n');
+ if (p != NULL) *p = '\0';
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "%s: %s", cookiefile, oneline);
+
+ if ((strncmp(oneline, "expires=", 8) == 0) &&
+ (apr_time_from_sec(atoll(&oneline[8])) <
+ apr_time_now()))
+ {
+ if (user != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "Bad expires");
+ GRSTgaclUserFree(user);
+ user = NULL;
+ }
+ break;
+ }
+ else if ((strncmp(oneline, "domain=", 7) == 0) &&
+ (strcmp(&oneline[7], r->hostname) != 0))
+ {
+ if (user != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "Bad domain/host");
+ GRSTgaclUserFree(user);
+ user = NULL;
+ }
+ break;
+ }
+ else if (strncmp(oneline, "path=", 5) == 0)
+ {
+ /* count number of slashes in Request URI */
+
+ for (n=0,p=r->uri; *p != '\0'; ++p)
+ if (*p == '/') ++n;
+
+ /* if too few slashes or path mismatch, then stop */
+
+ if ((n < ((mod_gridsite_dir_cfg *) cfg)->zoneslashes) ||
+ (strncmp(&oneline[5], r->uri, strlen(&oneline[5])) != 0))
+ {
+ if (user != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "Bad path");
+ GRSTgaclUserFree(user);
+ user = NULL;
+ }
+
+ break;
+ }
+ }
+ else if ((sscanf(oneline,"GRST_CRED_AURI_%d=",&j) == 1)
+ && (j == i+1)
+ && ((p = index(oneline, '=')) != NULL))
+ {
+ cred = GRSTgaclCredCreate(&p[1], NULL);
+
+ if (cred != NULL) ++i;
+
+ if (user == NULL) user = GRSTgaclUserNew(cred);
+ else GRSTgaclUserAddCred(user, cred);
+ }
+ else if ((sscanf(oneline,"GRST_CRED_VALID_%d=",&j) == 1)
+ && (j == i)
+ && ((p = index(oneline, '=')) != NULL)
+ && (sscanf(&p[1],
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ ¬before, ¬after, &delegation,
+ &nist_loa) == 4))
+ {
+ if ((i == 0) &&
+ (delegation > ((mod_gridsite_dir_cfg *) cfg)->gsiproxylimit))
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0,
+ r->server, "Bad delegation");
+ if (user != NULL) GRSTgaclUserFree(user);
+ user = NULL;
+ break;
+ }
+
+ GRSTgaclCredSetNotBefore( cred, notbefore);
+ GRSTgaclCredSetNotAfter( cred, notafter);
+ GRSTgaclCredSetDelegation(cred, delegation);
+
+ if (delegation == 0) GRSTgaclCredSetNistLoa(cred, 3);
+ else GRSTgaclCredSetNistLoa(cred, 2);
+ }
+ }
+
+ apr_file_close(fp);
+
+ /* delete passcode file if used over HTTP not HTTPS */
+ if (!ishttps) remove(cookiefile);
+
+ /* if successful and we got passcode from a cookie, then
+ we put cookie value into environment variables, so
+ can be used for double-submit cookie CSRF protection */
+
+ if ((user != NULL) &&
+ from_cookie &&
+ ((mod_gridsite_dir_cfg *) cfg)->envs)
+ apr_table_setn(env, "GRST_PASSCODE_COOKIE",
+ gridauthpasscode);
+ }
+ }
+
+ /*
+ if not succeeded from passcode file, try from connection notes
+ if a GSI Proxy or have GridSiteAutoPasscode on (the default)
+ If GridSiteAutoPasscode off and GridSiteRequirePasscode on
+ then interactive websites must use a login script to make passcode
+ and file instead.
+ */
+
+ if ((user == NULL) &&
+ (r->connection->notes != NULL) &&
+ ((grst_cred_auri_0 = (char *)
+ apr_table_get(r->connection->notes, "GRST_CRED_AURI_0")) != NULL) &&
+ (strncmp(grst_cred_auri_0, "dn:", 3) == 0) &&
+ ((grst_cred_valid_0 = (char *)
+ apr_table_get(r->connection->notes, "GRST_CRED_VALID_0")) != NULL) &&
+ (sscanf(grst_cred_valid_0,
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ ¬before, ¬after, &delegation, &nist_loa) == 4) &&
+ (delegation <= ((mod_gridsite_dir_cfg *) cfg)->gsiproxylimit) &&
+ ((delegation > 0) ||
+ ((mod_gridsite_dir_cfg *) cfg)->autopasscode ||
+ !(((mod_gridsite_dir_cfg *) cfg)->requirepasscode)))
+ {
+ cred_0 = GRSTgaclCredCreate(grst_cred_auri_0, NULL);
+ if (cred_0 != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Using identity %s from SSL/TLS", grst_cred_auri_0);
+
+ GRSTgaclCredSetNotBefore( cred_0, notbefore);
+ GRSTgaclCredSetNotAfter( cred_0, notafter);
+ GRSTgaclCredSetDelegation(cred_0, delegation);
+
+ if (delegation == 0) GRSTgaclCredSetNistLoa(cred_0, 3);
+ else GRSTgaclCredSetNistLoa(cred_0, 2);
+
+ user = GRSTgaclUserNew(cred_0);
+
+ /* check for VOMS etc in GRST_CRED_AURI_i too */
+
+ for (i=1; ; ++i)
+ {
+ snprintf(envname1, sizeof(envname1), "GRST_CRED_AURI_%d", i);
+ snprintf(envname2, sizeof(envname2), "GRST_CRED_VALID_%d", i);
+
+ if ((grst_cred_auri_i = (char *)
+ apr_table_get(r->connection->notes,envname1)) &&
+ (grst_cred_valid_i = (char *)
+ apr_table_get(r->connection->notes,envname2)))
+ {
+ cred = GRSTgaclCredCreate(grst_cred_auri_i, NULL);
+ if (cred != NULL)
+ {
+ notbefore = 0;
+ notafter = 0;
+ delegation = 0;
+ nist_loa = 0;
+
+ sscanf(grst_cred_valid_i,
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ ¬before, ¬after, &delegation, &nist_loa);
+
+ GRSTgaclCredSetNotBefore( cred, notbefore);
+ GRSTgaclCredSetNotAfter( cred, notafter);
+ GRSTgaclCredSetDelegation(cred, delegation);
+ GRSTgaclCredSetDelegation(cred, nist_loa);
+
+ GRSTgaclUserAddCred(user, cred);
+ }
+ }
+ else break; /* GRST_CRED_AURI_i are numbered consecutively */
+ }
+ }
+
+ /* if user from SSL ok and not a GSI Proxy and have
+ GridSiteAutoPasscode on we create passcode and file
+ automatically, and return cookie to client.
+ (if GridSiteAutoPasscode off then the site must use
+ a login script to make passcode and file instead.) */
+
+ if (((mod_gridsite_dir_cfg *) cfg)->autopasscode &&
+ (user != NULL) &&
+ (GRSTgaclCredGetDelegation(cred_0) == 0))
+ {
+ n = 0; /* number of slashes seen */
+
+ for (i=0; r->uri[i] != '\0'; ++i)
+ {
+ if (n >= ((mod_gridsite_dir_cfg *) cfg)->zoneslashes) break;
+
+ if (r->uri[i] == '/') ++n;
+ }
+
+ if ((n >= ((mod_gridsite_dir_cfg *) cfg)->zoneslashes)
+ && (i > 0))
+ {
+ p = apr_pstrdup(r->pool, r->uri);
+ p[i] = '\0';
+
+ /* try to generate passcode and make passcode file */
+ gridauthpasscode = make_passcode_file(r, cfg, p, 0);
+
+ if (gridauthpasscode != NULL)
+ {
+ apr_table_add(r->headers_out,
+ apr_pstrdup(r->pool, "Set-Cookie"),
+ apr_psprintf(r->pool,
+ "GRIDHTTP_PASSCODE=%s; "
+ "domain=%s; "
+ "path=%s; "
+ "secure", gridauthpasscode, r->hostname, p));
+ }
+ }
+ }
+ }
+
+ /*
+ GridSite passcode files don't include groups, IP or DNS so we add
+ them last so they're not written to passcode files by GridSite.
+
+ (site-supplied login scripts might create passcode files with
+ optional or additional AURIs. for example, valid roles selected by
+ the user on the login page.)
+
+ */
+
+ /* first add groups from DN lists - ie non-optional attributes */
+
+ if ((user != NULL) && ((mod_gridsite_dir_cfg *) cfg)->dnlists)
+ GRSTgaclUserLoadDNlists(user, ((mod_gridsite_dir_cfg *) cfg)->dnlists);
+
+ /* then add DNS credential */
+
+ remotehost = (char *) ap_get_remote_host(r->connection,
+ r->per_dir_config, REMOTE_DOUBLE_REV, NULL);
+ if ((remotehost != NULL) && (*remotehost != '\0'))
+ {
+ cred = GRSTgaclCredCreate("dns:", remotehost);
+ GRSTgaclCredSetNotAfter(cred, GRST_MAX_TIME_T);
+
+ if (user == NULL) user = GRSTgaclUserNew(cred);
+ else GRSTgaclUserAddCred(user, cred);
+ }
+
+ /* finally add IP credential */
+
+ if (r->connection->remote_ip)
+ {
+ cred = GRSTgaclCredCreate("ip:", r->connection->remote_ip);
+ GRSTgaclCredSetNotAfter(cred, GRST_MAX_TIME_T);
+
+ if (user == NULL) user = GRSTgaclUserNew(cred);
+ else GRSTgaclUserAddCred(user, cred);
+ }
+
+ /* write contents of user to per-request environment variables */
+
+ if (((mod_gridsite_dir_cfg *) cfg)->envs && (user != NULL))
+ {
+ cred = user->firstcred;
+
+ /* old-style Compact Credentials have the same delegation level
+ for all credentials. eg Using EEC delegation=0; using 1st GSI
+ Proxy then delegation=1 for X509USER _and_ GSIPROXY credentials.
+ So we remember the delegation level of any X509USER here */
+ if (cred != NULL) cc_delegation = cred->delegation;
+
+ for (i=0; (cred != NULL) && (cred->auri != NULL); ++i)
+ {
+ if (strncmp(cred->auri, "dn:", 3) == 0)
+ {
+ decoded = GRSThttpUrlDecode(&(cred->auri[3]));
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CRED_%d", i),
+ apr_psprintf(r->pool,
+ "%s %ld %ld %d %s",
+ (i==0) ? "X509USER" : "GSIPROXY",
+ cred->notbefore,
+ cred->notafter,
+ cc_delegation,
+ decoded));
+ free(decoded);
+ }
+ else if (strncmp(cred->auri, "fqan:", 5) == 0)
+ {
+ decoded = GRSThttpUrlDecode(&(cred->auri[5]));
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CRED_%d", i),
+ apr_psprintf(r->pool,
+ "VOMS %ld %ld 0 %s",
+ notbefore, notafter,
+ decoded));
+ free(decoded);
+ }
+
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CRED_AURI_%d", i),
+ apr_pstrdup(r->pool, cred->auri));
+
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CRED_VALID_%d", i),
+ apr_psprintf(r->pool,
+ "notbefore=%ld notafter=%ld delegation=%d nist-loa=%d",
+ cred->notbefore,
+ cred->notafter,
+ cred->delegation,
+ cred->nist_loa));
+
+ cred = cred->next;
+ }
+ }
+
+ /* check for Destination: header and evaluate if present */
+
+ if ((destination = (char *) apr_table_get(r->headers_in,
+ "Destination")) != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Destination header found, value=%s", destination);
+
+ destination_prefix = apr_psprintf(r->pool, "https://%s:%d/",
+ r->server->server_hostname, (int) r->server->port);
+
+ if (strncmp(destination_prefix, destination,
+ strlen(destination_prefix)) == 0)
+ destination_uri = &destination[strlen(destination_prefix)-1];
+ else if ((int) r->server->port == 443)
+ {
+ destination_prefix = apr_psprintf(r->pool, "https://%s/",
+ r->server->server_hostname);
+
+ if (strncmp(destination_prefix, destination,
+ strlen(destination_prefix)) == 0)
+ destination_uri = &destination[strlen(destination_prefix)-1];
+ }
+
+ if (destination_uri != NULL)
+ {
+ destreq = ap_sub_req_method_uri("GET", destination_uri, r, NULL);
+
+ if ((destreq != NULL) && (destreq->filename != NULL)
+ && (destreq->path_info != NULL))
+ {
+ destination_translated = apr_pstrcat(r->pool,
+ destreq->filename, destreq->path_info, NULL);
+
+ apr_table_setn(r->notes, "GRST_DESTINATION_TRANSLATED",
+ destination_translated);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->envs)
+ apr_table_setn(env, "GRST_DESTINATION_TRANSLATED",
+ destination_translated);
+
+ p = rindex(destination_translated, '/');
+ if ((p != NULL) && (strcmp(&p[1], GRST_ACL_FILE) == 0))
+ destination_is_acl = 1;
+ }
+ }
+ }
+
+ if ((((mod_gridsite_dir_cfg *) cfg)->adminlist != NULL) && (user != NULL))
+ {
+ cred = user->firstcred;
+
+ while ((cred != NULL) && (cred->auri != NULL))
+ {
+ if (strcmp(((mod_gridsite_dir_cfg *) cfg)->adminlist,
+ cred->auri) == 0)
+ {
+ perm = GRST_PERM_ALL;
+ if (destination_translated != NULL)
+ destination_perm = GRST_PERM_ALL;
+ break;
+ }
+
+ cred = cred->next;
+ }
+ }
+
+ if (perm != GRST_PERM_ALL) /* cannot improve on perfection... */
+ {
+ if (((mod_gridsite_dir_cfg *) cfg)->aclpath != NULL)
+ {
+ aclpath = make_aclpath(r,((mod_gridsite_dir_cfg *) cfg)->aclpath);
+
+ if (aclpath != NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Examine ACL file %s (from ACL path %s)",
+ aclpath, ((mod_gridsite_dir_cfg *) cfg)->aclpath);
+
+ acl = GRSTgaclAclLoadFile(aclpath);
+ }
+ else ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "Failed to make ACL file from ACL path %s, URI %s)",
+ ((mod_gridsite_dir_cfg *) cfg)->aclpath, r->uri);
+ }
+ else if ((((mod_gridsite_dir_cfg *) cfg)->dnlistsuri == NULL) ||
+ (strncmp(r->uri,
+ ((mod_gridsite_dir_cfg *) cfg)->dnlistsuri,
+ strlen(((mod_gridsite_dir_cfg *) cfg)->dnlistsuri)) != 0) ||
+ (strlen(r->uri) <= strlen(((mod_gridsite_dir_cfg *) cfg)->dnlistsuri)))
+ {
+ acl = GRSTgaclAclLoadforFile(r->filename);
+ }
+
+ if (acl != NULL) perm = GRSTgaclAclTestUser(acl, user);
+ GRSTgaclAclFree(acl);
+
+ if (destination_translated != NULL)
+ {
+ acl = GRSTgaclAclLoadforFile(destination_translated);
+ if (acl != NULL) destination_perm = GRSTgaclAclTestUser(acl, user);
+ GRSTgaclAclFree(acl);
+
+ apr_table_setn(r->notes, "GRST_DESTINATION_PERM",
+ apr_psprintf(r->pool, "%d", destination_perm));
+
+ if (((mod_gridsite_dir_cfg *) cfg)->envs)
+ apr_table_setn(env, "GRST_DESTINATION_PERM",
+ apr_psprintf(r->pool, "%d", destination_perm));
+ }
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
+ "After GACL/Onetime evaluation, GRST_PERM=%d", perm);
+
+ /* add permission and first AURI to request notes */
+
+ apr_table_setn(r->notes, "GRST_PERM", apr_psprintf(r->pool, "%d", perm));
+
+ cred = NULL;
+ if (user)
+ cred = user->firstcred;
+ if ((cred != NULL) && (strncmp(cred->auri, "dn:", 3) == 0))
+ {
+ apr_table_setn(r->notes, "GRST_CRED_AURI_0",
+ apr_psprintf(r->pool, "%s", cred->auri));
+ }
+
+
+ if (((mod_gridsite_dir_cfg *) cfg)->envs)
+ {
+ /* copy any credentials from (SSL) connection to environment */
+
+ for (i=0; ; ++i)
+ {
+ snprintf(envname1, sizeof(envname1), "GRST_CRED_AURI_%d", i);
+ snprintf(envname2, sizeof(envname2), "GRST_CRED_VALID_%d", i);
+
+ if ((grst_cred_auri_i = (char *)
+ apr_table_get(r->connection->notes,envname1)) &&
+ (grst_cred_valid_i = (char *)
+ apr_table_get(r->connection->notes,envname2)))
+ {
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CONN_AURI_%d", i),
+ apr_pstrdup(r->pool, grst_cred_auri_i));
+
+ apr_table_setn(env,
+ apr_psprintf(r->pool, "GRST_CONN_VALID_%d", i),
+ apr_pstrdup(r->pool, grst_cred_valid_i));
+ }
+ else break;
+ }
+
+ apr_table_setn(env, "GRST_PERM", apr_psprintf(r->pool, "%d", perm));
+
+ if (((mod_gridsite_dir_cfg *) cfg)->requirepasscode == 0)
+ apr_table_set(env, "GRST_REQUIRE_PASSCODE", "off");
+ else apr_table_set(env, "GRST_REQUIRE_PASSCODE", "on");
+
+ if (((dir_path = apr_pstrdup(r->pool, r->filename)) != NULL) &&
+ ((p = rindex(dir_path, '/')) != NULL))
+ {
+ *p = '\0';
+ apr_table_setn(env, "GRST_DIR_PATH", dir_path);
+ }
+
+ if (((mod_gridsite_dir_cfg *) cfg)->helpuri != NULL)
+ apr_table_setn(env, "GRST_HELP_URI",
+ ((mod_gridsite_dir_cfg *) cfg)->helpuri);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->loginuri != NULL)
+ apr_table_setn(env, "GRST_LOGIN_URI",
+ ((mod_gridsite_dir_cfg *) cfg)->loginuri);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->adminfile != NULL)
+ apr_table_setn(env, "GRST_ADMIN_FILE",
+ ((mod_gridsite_dir_cfg *) cfg)->adminfile);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->editable != NULL)
+ apr_table_setn(env, "GRST_EDITABLE",
+ ((mod_gridsite_dir_cfg *) cfg)->editable);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->headfile != NULL)
+ apr_table_setn(env, "GRST_HEAD_FILE",
+ ((mod_gridsite_dir_cfg *) cfg)->headfile);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->footfile != NULL)
+ apr_table_setn(env, "GRST_FOOT_FILE",
+ ((mod_gridsite_dir_cfg *) cfg)->footfile);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->dnlists != NULL)
+ apr_table_setn(env, "GRST_DN_LISTS",
+ ((mod_gridsite_dir_cfg *) cfg)->dnlists);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->dnlistsuri != NULL)
+ apr_table_setn(env, "GRST_DN_LISTS_URI",
+ ((mod_gridsite_dir_cfg *) cfg)->dnlistsuri);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->adminlist != NULL)
+ apr_table_setn(env, "GRST_ADMIN_LIST",
+ ((mod_gridsite_dir_cfg *) cfg)->adminlist);
+
+ apr_table_setn(env, "GRST_GSIPROXY_LIMIT",
+ apr_psprintf(r->pool, "%d",
+ ((mod_gridsite_dir_cfg *)cfg)->gsiproxylimit));
+
+ if (((mod_gridsite_dir_cfg *) cfg)->unzip != NULL)
+ apr_table_setn(env, "GRST_UNZIP",
+ ((mod_gridsite_dir_cfg *) cfg)->unzip);
+
+ if (!(((mod_gridsite_dir_cfg *) cfg)->gridsitelink))
+ apr_table_setn(env, "GRST_NO_LINK", "1");
+
+ if (((mod_gridsite_dir_cfg *) cfg)->aclformat != NULL)
+ apr_table_setn(env, "GRST_ACL_FORMAT",
+ ((mod_gridsite_dir_cfg *) cfg)->aclformat);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->aclpath != NULL)
+ apr_table_setn(env, "GRST_ACL_PATH",
+ ((mod_gridsite_dir_cfg *) cfg)->aclpath);
+
+ if (((mod_gridsite_dir_cfg *) cfg)->delegationuri != NULL)
+ apr_table_setn(env, "GRST_DELEGATION_URI",
+ ((mod_gridsite_dir_cfg *) cfg)->delegationuri);
+
+
+ if (((mod_gridsite_dir_cfg *) cfg)->execmethod != NULL)
+ {
+ apr_table_setn(env, "GRST_EXEC_METHOD",
+ ((mod_gridsite_dir_cfg *) cfg)->execmethod);
+
+ if ((strcasecmp(((mod_gridsite_dir_cfg *) cfg)->execmethod,
+ "directory") == 0) && (r->filename != NULL))
+ {
+ if ((r->content_type != NULL) &&
+ (strcmp(r->content_type, DIR_MAGIC_TYPE) == 0))
+ apr_table_setn(env, "GRST_EXEC_DIRECTORY", r->filename);
+ else
+ {
+ file = apr_pstrdup(r->pool, r->filename);
+ p = rindex(file, '/');
+ if (p != NULL)
+ {
+ *p = '\0';
+ apr_table_setn(env, "GRST_EXEC_DIRECTORY", file);
+ }
+ }
+ }
+ }
+
+ apr_table_setn(env, "GRST_DISK_MODE",
+ apr_psprintf(r->pool, "0x%04x",
+ ((mod_gridsite_dir_cfg *)cfg)->diskmode));
+ }
+
+ if (((mod_gridsite_dir_cfg *) cfg)->auth)
+ {
+ /* *** Check HTTP method to decide which perm bits to check *** */
+
+ if ((r->filename != NULL) &&
+ ((p = rindex(r->filename, '/')) != NULL) &&
+ (strcmp(&p[1], GRST_ACL_FILE) == 0)) file_is_acl = 1;
+
+ content_type = r->content_type;
+ if ((content_type != NULL) &&
+ (strcmp(content_type, DIR_MAGIC_TYPE) == 0) &&
+ (((mod_gridsite_dir_cfg *) cfg)->dnlistsuri != NULL) &&
+ (strncmp(r->uri,
+ ((mod_gridsite_dir_cfg *) cfg)->dnlistsuri,
+ strlen(((mod_gridsite_dir_cfg *) cfg)->dnlistsuri)) == 0) &&
+ (strlen(r->uri) > strlen(((mod_gridsite_dir_cfg *) cfg)->dnlistsuri)))
+ content_type = "text/html";
+
+ if ( GRSTgaclPermHasNone(perm) ||
+
+ /* first two M_GET conditions make the subtle distinction
+ between .../ that maps to .../index.html (governed by
+ Read perm) or to dir list (governed by List perm);
+ third M_GET condition deals with typeless CGI requests */
+
+ ((r->method_number == M_GET) &&
+ !GRSTgaclPermHasRead(perm) &&
+ (content_type != NULL) &&
+ (strcmp(content_type, DIR_MAGIC_TYPE) != 0)) ||
+
+ ((r->method_number == M_GET) &&
+ !GRSTgaclPermHasList(perm) &&
+ (content_type != NULL) &&
+ (strcmp(content_type, DIR_MAGIC_TYPE) == 0)) ||
+
+ ((r->method_number == M_GET) &&
+ !GRSTgaclPermHasRead(perm) &&
+ (content_type == NULL)) ||
+
+ ((r->method_number == M_POST) && !GRSTgaclPermHasRead(perm) ) ||
+
+ (((r->method_number == M_PUT) ||
+ (r->method_number == M_DELETE)) &&
+ !GRSTgaclPermHasWrite(perm) && !file_is_acl) ||
+
+ ((r->method_number == M_MOVE) &&
+ ((!GRSTgaclPermHasWrite(perm) && !file_is_acl) ||
+ (!GRSTgaclPermHasAdmin(perm) && file_is_acl) ||
+ (!GRSTgaclPermHasWrite(destination_perm)
+ && !destination_is_acl) ||
+ (!GRSTgaclPermHasAdmin(destination_perm)
+ && destination_is_acl)) ) ||
+
+ (((r->method_number == M_PUT) ||
+ (r->method_number == M_DELETE)) &&
+ !GRSTgaclPermHasAdmin(perm) && file_is_acl) ||
+
+ /* for WebDAV/Subversion */
+
+ (((r->method_number == M_PROPFIND) ||
+ (r->method_number == M_REPORT)) &&
+ !GRSTgaclPermHasRead(perm)) ||
+
+ (((r->method_number == M_CHECKOUT) ||
+ (r->method_number == M_MERGE) ||
+ (r->method_number == M_MKACTIVITY) ||
+ (r->method_number == M_MKCOL) ||
+ (r->method_number == M_LOCK) ||
+ (r->method_number == M_UNLOCK)) &&
+ !GRSTgaclPermHasWrite(perm))
+
+ ) retcode = HTTP_FORBIDDEN;
+ }
+
+ if (user != NULL) GRSTgaclUserFree(user);
+
+ return retcode;
+}
+
+int GRST_callback_SSLVerify_wrapper(int ok, X509_STORE_CTX *ctx)
+{
+ SSL *ssl = (SSL *) X509_STORE_CTX_get_app_data(ctx);
+ conn_rec *conn = (conn_rec *) SSL_get_app_data(ssl);
+ int errnum = X509_STORE_CTX_get_error(ctx);
+ int errdepth = X509_STORE_CTX_get_error_depth(ctx);
+ int returned_ok;
+ STACK_OF(X509) *certstack;
+ GRSTx509Chain *grst_chain;
+
+ /* Call caNl callback directly */
+ returned_ok = canl_direct_pv_clb(NULL, ctx, ok);
+
+ /* in case ssl_callback_SSLVerify changed it */
+ errnum = X509_STORE_CTX_get_error(ctx);
+
+ if ((errdepth == 0) && (errnum == X509_V_OK))
+ /*
+ * We've now got the last certificate - the identity being used for
+ * this connection. At this point we check the whole chain for valid
+ * CAs or, failing that, GSI-proxy validity using GRSTx509CheckChain.
+ */
+ {
+ certstack = (STACK_OF(X509) *) X509_STORE_CTX_get_chain(ctx);
+
+ errnum = GRSTx509ChainLoad(&grst_chain, certstack, NULL,
+ "/etc/grid-security/certificates",
+ "/etc/grid-security/vomsdir");
+
+ if (returned_ok)
+ /* Put result of GRSTx509ChainLoadCheck into connection notes */
+ GRST_save_ssl_creds(conn, grst_chain);
+ if (grst_chain)
+ GRSTx509ChainFree(grst_chain);
+ }
+
+ return returned_ok;
+}
+
+void sitecast_handle_NOP_request(server_rec *main_server,
+ GRSThtcpMessage *htcp_mesg, int s,
+ struct sockaddr *client_addr_ptr,
+ socklen_t client_addr_len)
+{
+ int outbuf_len;
+ char *outbuf;
+ char host[INET6_ADDRSTRLEN];
+ char serv[8];
+
+ if (GRSThtcpNOPresponseMake(&outbuf, &outbuf_len,
+ htcp_mesg->trans_id) == GRST_RET_OK)
+ {
+ getnameinfo(client_addr_ptr, client_addr_len,
+ host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast sends NOP response to %s:%s",
+ host, serv);
+
+ sendto(s, outbuf, outbuf_len, 0,
+ client_addr_ptr, client_addr_len);
+
+ free(outbuf);
+ }
+}
+
+void sitecast_handle_TST_GET(server_rec *main_server,
+ GRSThtcpMessage *htcp_mesg, int s,
+ struct sockaddr *client_addr_ptr,
+ socklen_t client_addr_len)
+{
+ int outbuf_len, ialias;
+ char *filename, *outbuf, *location;
+ struct stat statbuf;
+ char host[INET6_ADDRSTRLEN];
+ char serv[8];
+
+ getnameinfo(client_addr_ptr, client_addr_len,
+ host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast responder received TST GET with uri %s",
+ htcp_mesg->uri->text, GRSThtcpCountstrLen(htcp_mesg->uri));
+
+ /* find if any GridSiteCastAlias lines match */
+
+ for (ialias=0; ialias < GRST_SITECAST_ALIASES ; ++ialias)
+ {
+ if (sitecastaliases[ialias].sitecast_url == NULL)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast responder does not handle %*s requested by %s:%s",
+ GRSThtcpCountstrLen(htcp_mesg->uri),
+ htcp_mesg->uri->text,
+ host, serv);
+
+ return; /* no match */
+ }
+
+ if ((strlen(sitecastaliases[ialias].sitecast_url)
+ <= GRSThtcpCountstrLen(htcp_mesg->uri)) &&
+ (strncmp(sitecastaliases[ialias].sitecast_url,
+ htcp_mesg->uri->text,
+ strlen(sitecastaliases[ialias].sitecast_url))==0)) break;
+ }
+
+ if (ialias == GRST_SITECAST_ALIASES)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast responder does not handle %*s requested by %s:%s",
+ GRSThtcpCountstrLen(htcp_mesg->uri),
+ htcp_mesg->uri->text,
+ host, serv);
+
+ return; /* no match */
+ }
+
+ /* convert URL to filename, using alias mapping */
+
+ asprintf(&filename, "%s%*s",
+ sitecastaliases[ialias].local_path,
+ GRSThtcpCountstrLen(htcp_mesg->uri)
+ - strlen(sitecastaliases[ialias].sitecast_url),
+ &(htcp_mesg->uri->text[strlen(sitecastaliases[ialias].sitecast_url)]) );
+
+ if (stat(filename, &statbuf) == 0) /* found file */
+ {
+ asprintf(&location, "Location: %s://%s:%d/%s\r\n",
+ sitecastaliases[ialias].scheme,
+ sitecastaliases[ialias].local_hostname,
+ sitecastaliases[ialias].port,
+ &(htcp_mesg->uri->text[strlen(sitecastaliases[ialias].sitecast_url)]) );
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast finds %*s at %s, redirects with %s",
+ GRSThtcpCountstrLen(htcp_mesg->uri),
+ htcp_mesg->uri->text, filename, location);
+
+ if (GRSThtcpTSTresponseMake(&outbuf, &outbuf_len,
+ htcp_mesg->trans_id,
+ location, "", "") == GRST_RET_OK)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast sends TST response to %s:%s",
+ host, serv);
+
+ sendto(s, outbuf, outbuf_len, 0,
+ client_addr_ptr, client_addr_len);
+
+ free(outbuf);
+ }
+
+ free(location);
+ }
+ else ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast does not find %*s (would be at %s)",
+ GRSThtcpCountstrLen(htcp_mesg->uri),
+ htcp_mesg->uri->text, filename);
+
+
+ free(filename);
+}
+
+void sitecast_handle_request(server_rec *main_server,
+ char *reqbuf, int reqbuf_len,
+ int s,
+ struct sockaddr *client_addr_ptr,
+ socklen_t client_addr_len)
+{
+ GRSThtcpMessage htcp_mesg;
+ char host[INET6_ADDRSTRLEN];
+ char serv[8];
+
+ getnameinfo(client_addr_ptr, client_addr_len,
+ host, sizeof(host),
+ serv, sizeof(serv), NI_NUMERICHOST);
+ if (GRSThtcpMessageParse(&htcp_mesg,reqbuf,reqbuf_len) != GRST_RET_OK)
+ {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "SiteCast responder rejects format of UDP message from %s:%s",
+ host, serv);
+ return;
+ }
+
+ if (htcp_mesg.rr != 0) /* ignore HTCP responses: we just do requests */
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast responder ignores HTCP response from %s:%s",
+ host, serv);
+ return;
+ }
+
+ if (htcp_mesg.opcode == GRSThtcpNOPop)
+ {
+ sitecast_handle_NOP_request(main_server, &htcp_mesg, s,
+ client_addr_ptr, client_addr_len);
+ return;
+ }
+
+ if (htcp_mesg.opcode == GRSThtcpTSTop)
+ {
+ if (((GRSThtcpCountstrLen(htcp_mesg.method) == 3) &&
+ (strncmp(htcp_mesg.method->text, "GET", 3) == 0)) ||
+ ((GRSThtcpCountstrLen(htcp_mesg.method) == 4) &&
+ (strncmp(htcp_mesg.method->text, "HEAD", 4) == 0)))
+ {
+ sitecast_handle_TST_GET(main_server, &htcp_mesg, s,
+ client_addr_ptr, client_addr_len);
+ return;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "SiteCast responder rejects method %*s in TST message from %s:%s",
+ GRSThtcpCountstrLen(htcp_mesg.method), htcp_mesg.method->text,
+ host, serv);
+ return;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "SiteCast does not implement HTCP op-code %d in message from %s:%s",
+ htcp_mesg.opcode,
+ host, serv);
+}
+
+static int
+bind_sitecast_sockets(server_rec *main_server, const char *node,
+ unsigned int port, int is_unicast)
+{
+ int s, open, ret;
+ struct addrinfo *ai;
+ struct addrinfo hints;
+ struct ipv6_mreq mreq6;
+ struct ip_mreq mreq;
+ char serv[8];
+ struct addrinfo *a;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
+ if (!is_unicast)
+ hints.ai_flags |= AI_NUMERICHOST;
+
+ snprintf(serv, sizeof(serv), "%u", port);
+
+ ret = getaddrinfo(node, serv, &hints, &ai);
+ if (ret) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "%s UDP Responder fails to look up %s",
+ (is_unicast) ? "Unicast" : "Multicast", node);
+ return -1;
+ }
+
+ open = 0;
+ for (a = ai; a != NULL; a = a->ai_next) {
+ s = socket(a->ai_family, a->ai_socktype, a->ai_protocol);
+ if (s < 0)
+ continue;
+
+ ret = bind(s, a->ai_addr, a->ai_addrlen);
+ if (ret < 0) {
+ close (s);
+ continue;
+ }
+
+ if (!is_unicast) {
+ switch (a->ai_family) {
+ case AF_INET:
+ bzero(&mreq, sizeof(mreq));
+ mreq.imr_multiaddr = ((struct sockaddr_in *)(a->ai_addr))->sin_addr;
+ mreq.imr_interface.s_addr = htonl(INADDR_ANY);
+ ret = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ break;
+ case AF_INET6:
+ mreq6.ipv6mr_multiaddr =
+ ((struct sockaddr_in6 *)a->ai_addr)->sin6_addr;
+ mreq6.ipv6mr_interface =
+ ((struct sockaddr_in6 *)a->ai_addr)->sin6_scope_id;
+ ret = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
+ &mreq6, sizeof(mreq6));
+ break;
+ default:
+ continue;
+ }
+ if (ret < 0) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "SiteCast UDP Responder fails on setting multicast (%s)",
+ strerror(errno));
+ continue;
+ }
+ }
+
+ FD_SET(s, &sitecast_sockets.fds);
+ if (s > sitecast_sockets.max_fd)
+ sitecast_sockets.max_fd = s;
+ open = 1;
+ }
+ freeaddrinfo(ai);
+
+ if (!open) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, 0, main_server,
+ "mod_gridsite: sitecast responder fails on unicast");
+ return -1;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast UDP %s responder on %s:%s",
+ (is_unicast) ? "unicast" : "multicast",
+ node, serv);
+ return 0;
+}
+
+void sitecast_responder(server_rec *main_server)
+{
+#define GRST_SITECAST_MAXBUF 8192
+ char reqbuf[GRST_SITECAST_MAXBUF];
+ int reqbuf_len, ret, retval, i;
+ struct sockaddr client_addr;
+ socklen_t client_addr_len;
+ fd_set readsckts;
+ int s;
+ char host[INET6_ADDRSTRLEN];
+ char serv[8];
+
+ strcpy((char *) main_server->process->argv[0], "GridSiteCast UDP responder");
+
+ FD_ZERO(&sitecast_sockets.fds);
+ sitecast_sockets.max_fd = -1;
+
+ /* initialise unicast/replies socket first */
+ ret = bind_sitecast_sockets(main_server, main_server->server_hostname, sitecastgroups[0].port, 1);
+ if (ret)
+ return;
+
+ /* initialise multicast listener sockets next */
+
+ for (i=1; (i <= GRST_SITECAST_GROUPS) &&
+ (sitecastgroups[i].port != 0); ++i)
+ {
+ ret = bind_sitecast_sockets(main_server, sitecastgroups[i].address, sitecastgroups[i].port, 0);
+ if (ret)
+ continue;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast UDP Responder listening on %s:%d",
+ sitecastgroups[i].address,
+ sitecastgroups[i].port);
+ }
+
+ for (i=0; (i < GRST_SITECAST_ALIASES) &&
+ (sitecastaliases[i].sitecast_url != NULL) ; ++i)
+ {
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast alias for %s (%s,%d) to %s (%s)",
+ sitecastaliases[i].sitecast_url,
+ sitecastaliases[i].scheme,
+ sitecastaliases[i].port,
+ sitecastaliases[i].local_path,
+ sitecastaliases[i].local_hostname);
+ }
+
+ while (1) /* **** main listening loop **** */
+ {
+ /* set up bitmasks for select */
+
+ readsckts = sitecast_sockets.fds;
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast UDP Responder waiting for requests");
+
+ if ((retval = select(sitecast_sockets.max_fd + 1, &readsckts, NULL, NULL, NULL)) < 1)
+ continue; /* < 1 on timeout or error */
+
+ for (s = 0; s <= sitecast_sockets.max_fd; s++) {
+ if (FD_ISSET(s, &readsckts))
+ break;
+ }
+ if (s > sitecast_sockets.max_fd)
+ continue;
+
+ client_addr_len = sizeof(client_addr);
+ reqbuf_len = recvfrom(s, reqbuf, GRST_SITECAST_MAXBUF, 0,
+ &client_addr, &client_addr_len);
+ if (reqbuf_len < 0)
+ continue;
+
+ getnameinfo(&client_addr, client_addr_len,
+ host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST);
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "SiteCast receives UDP message from %s:%s",
+ host, serv);
+
+ sitecast_handle_request(main_server, reqbuf, reqbuf_len, s,
+ &client_addr, client_addr_len);
+ } /* **** end of main listening loop **** */
+}
+
+static int mod_gridsite_server_post_config(apr_pool_t *pPool,
+ apr_pool_t *pLog, apr_pool_t *pTemp, server_rec *main_server)
+{
+ SSL_CTX *ctx;
+ SSLSrvConfigRec *sc;
+ int i = 0;
+ server_rec *this_server;
+ apr_proc_t *procnew = NULL;
+ apr_status_t status;
+ char *path;
+ const char *userdata_key = "sitecast_init";
+ const char *insecure_reneg = "SSLInsecureRenegotiation";
+ canl_ctx c_ctx = NULL;
+
+ c_ctx = canl_create_ctx();
+ if (!c_ctx){
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, main_server,
+ "mod_gridsite: Failed to create caNl context.");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ apr_pool_userdata_get((void **) &procnew, userdata_key,
+ main_server->process->pool);
+
+ /* we only fork responder if one not already forked and we have at
+ least one GridSiteCastAlias defined. This means it is possible
+ to run a responder with no groups - listening on unicast only! */
+
+ if ((procnew == NULL) &&
+ (sitecastaliases[0].sitecast_url != NULL))
+ {
+ /* UDP multicast responder required but not yet started */
+
+ procnew = apr_pcalloc(main_server->process->pool, sizeof(*procnew));
+ apr_pool_userdata_set((const void *) procnew, userdata_key,
+ apr_pool_cleanup_null, main_server->process->pool);
+
+ status = apr_proc_fork(procnew, pPool);
+
+ if (status < 0)
+ {
+ ap_log_error(APLOG_MARK, APLOG_CRIT, status, main_server,
+ "mod_gridsite: Failed to spawn SiteCast responder process");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else if (status == APR_INCHILD)
+ {
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, status, main_server,
+ "mod_gridsite: Spawning SiteCast responder process");
+ sitecast_responder(main_server);
+ exit(-1);
+ }
+
+ apr_pool_note_subprocess(main_server->process->pool,
+ procnew, APR_KILL_AFTER_TIMEOUT);
+ }
+
+ /* continue with normal HTTP/HTTPS servers */
+
+ ap_add_version_component(pPool,
+ apr_psprintf(pPool, "mod_gridsite/%s", VERSION));
+
+ /* look for a SSLInsecureRenegotiation flag - if it exists then the mod_ssl
+ internal variable 'SSLSrvConfigRec' is different */
+ while ( ssl_module.cmds[i].name && !mod_ssl_with_insecure_reneg)
+ {
+ mod_ssl_with_insecure_reneg = (strncmp( ssl_module.cmds[i].name,
+ insecure_reneg, sizeof(insecure_reneg) ) == 0);
+ i++;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_NOTICE, status, main_server,
+ "mod_gridsite: mod_ssl_with_insecure_reneg = %d", mod_ssl_with_insecure_reneg);
+
+ for (this_server = main_server;
+ this_server != NULL;
+ this_server = this_server->next)
+ {
+ /* we do some GridSite OpenSSL magic for HTTPS servers */
+
+ sc = ap_get_module_config(this_server->module_config, &ssl_module);
+
+ if ((sc != NULL) &&
+ (sc->enabled) &&
+ (SSLSrvConfigRec_server(sc) != NULL) &&
+ (SSLSrvConfigRec_server(sc)->ssl_ctx != NULL))
+ {
+ ctx = SSLSrvConfigRec_server(sc)->ssl_ctx;
+
+ /* Use default caNl callbacks to verify certificates*/
+ canl_ssl_ctx_set_clb(c_ctx, ctx, ctx->verify_mode,
+ GRST_callback_SSLVerify_wrapper);
+
+ if (main_server->loglevel >= APLOG_DEBUG)
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, main_server,
+ "Set mod_ssl verify callbacks to GridSite wrappers: %s",
+ canl_get_error_message(c_ctx));
+ }
+ }
+
+ /* create sessions directory if necessary */
+
+ path = ap_server_root_relative(pPool, sessionsdir);
+ apr_dir_make_recursive(path, APR_UREAD | APR_UWRITE | APR_UEXECUTE, pPool);
+ chown(path, unixd_config.user_id, unixd_config.group_id);
+
+ canl_free_ctx(c_ctx);
+ return OK;
+}
+
+static server_rec *mod_gridsite_log_func_server;
+static int mod_gridsite_log_func(char *file, int line, int level,
+ char *fmt, ...)
+{
+ char *mesg;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vasprintf(&mesg, fmt, ap);
+ va_end(ap);
+
+ ap_log_error(file, line, level,
+ 0, mod_gridsite_log_func_server, "%s", mesg);
+
+ free(mesg);
+ return 0;
+}
+
+static void mod_gridsite_child_init(apr_pool_t *pPool, server_rec *pServer)
+{
+ apr_time_t cutoff_time;
+ apr_dir_t *dir;
+ char *filename;
+ apr_finfo_t finfo;
+ SSLSrvConfigRec *sc = ap_get_module_config(pServer->module_config,
+ &ssl_module);
+ GRSTgaclInit();
+ mod_gridsite_log_func_server = pServer;
+ GRSTerrorLogFunc = mod_gridsite_log_func;
+
+ /* expire old ssl creds files */
+
+ if (sc != NULL)
+ {
+ cutoff_time = apr_time_now()
+ - apr_time_from_sec(sc->session_cache_timeout);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, pServer,
+ "Cutoff time for ssl creds cache: %ld",
+ (long) apr_time_sec(cutoff_time));
+
+ if (apr_dir_open(&dir,
+ ap_server_root_relative(pPool, sessionsdir), pPool) == APR_SUCCESS)
+ {
+ while (apr_dir_read(&finfo,
+ APR_FINFO_CTIME | APR_FINFO_NAME, dir) == APR_SUCCESS)
+ {
+ if ((finfo.ctime < cutoff_time) &&
+ (strncmp(finfo.name, "sslcreds-", 9) == 0))
+ {
+ filename = apr_pstrcat(pPool,
+ ap_server_root_relative(pPool, sessionsdir),
+ "/", finfo.name, NULL);
+
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, pServer,
+ "Remove %s from ssl creds cache", filename);
+
+ apr_file_remove(filename, pPool);
+ }
+ }
+
+ apr_dir_close(dir);
+ }
+ }
+}
+
+static int mod_gridsite_handler(request_rec *r)
+{
+ mod_gridsite_dir_cfg *conf;
+
+ conf = (mod_gridsite_dir_cfg *)
+ ap_get_module_config(r->per_dir_config, &gridsite_module);
+
+ if (conf->dnlistsuri != NULL)
+ {
+ if (strcmp(r->uri, conf->dnlistsuri) == 0)
+ return mod_gridsite_dnlistsuri_dir_handler(r, conf);
+
+ if (strncmp(r->uri, conf->dnlistsuri, strlen(conf->dnlistsuri)) == 0)
+ return mod_gridsite_dnlistsuri_handler(r, conf);
+
+ if ((strncmp(r->uri, conf->dnlistsuri, strlen(r->uri)) == 0)
+ && (strlen(r->uri) == strlen(conf->dnlistsuri) - 1))
+ {
+ apr_table_setn(r->headers_out, apr_pstrdup(r->pool, "Location"),
+ apr_pstrcat(r->pool, r->uri, "/", NULL));
+
+ r->status = HTTP_MOVED_TEMPORARILY;
+ return OK;
+ }
+ }
+
+ if (strcmp(r->handler, DIR_MAGIC_TYPE) == 0)
+ return mod_gridsite_dir_handler(r, conf);
+
+ return mod_gridsite_nondir_handler(r, conf);
+}
+
+static ap_unix_identity_t *mod_gridsite_get_suexec_id_doer(const request_rec *r)
+{
+ mod_gridsite_dir_cfg *conf;
+
+ conf = (mod_gridsite_dir_cfg *)
+ ap_get_module_config(r->per_dir_config, &gridsite_module);
+
+ if ((conf->execugid.uid != UNSET) &&
+ (conf->execmethod != NULL))
+ {
+
+ /* also push GRST_EXEC_DIRECTORY into request environment here too */
+
+ return &(conf->execugid);
+ }
+
+ return NULL;
+}
+
+static void register_hooks(apr_pool_t *p)
+{
+ /* config and handler stuff */
+ static const char * const aszPre[] = { "mod_ssl.c", NULL };
+
+ ap_hook_post_config(mod_gridsite_server_post_config, NULL, NULL,
+ APR_HOOK_LAST);
+ ap_hook_child_init(mod_gridsite_child_init, aszPre, NULL, APR_HOOK_MIDDLE);
+
+ ap_hook_check_user_id(mod_gridsite_check_user_id, NULL, NULL,
+ APR_HOOK_REALLY_FIRST);
+
+ ap_hook_fixups(mod_gridsite_first_fixups,NULL,NULL,APR_HOOK_FIRST);
+
+ ap_hook_fixups(mod_gridsite_perm_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
+
+ ap_hook_handler(mod_gridsite_handler, NULL, NULL, APR_HOOK_FIRST);
+
+ ap_hook_get_suexec_identity(mod_gridsite_get_suexec_id_doer,
+ NULL, NULL, APR_HOOK_MIDDLE);
+}
+
+module AP_MODULE_DECLARE_DATA gridsite_module =
+{
+ STANDARD20_MODULE_STUFF,
+ create_gridsite_dir_config, /* dir config creater */
+ merge_gridsite_dir_config, /* dir merger */
+ create_gridsite_srv_config, /* create server config */
+ NULL, /* merge server config */
+ mod_gridsite_cmds, /* command apr_table_t */
+ register_hooks /* register hooks */
+};
--- /dev/null
+/*
+ Copyright (c) 2002-10, Andrew McNab, University of Manchester
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or
+ without modification, are permitted provided that the following
+ conditions are met:
+
+ o Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the following
+ disclaimer.
+ o Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+ ---------------------------------------------------------------
+ For more information about GridSite: http://www.gridsite.org/
+ ---------------------------------------------------------------
+*/
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <stdarg.h>
+#include <dirent.h>
+#include <string.h>
+#include <pwd.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifndef GRST_NO_OPENSSL
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/bio.h>
+#include <openssl/des.h>
+#include <openssl/rand.h>
+#endif
+
+#include <canl.h>
+#include <canl_cred.h>
+
+#include "gridsite.h"
+
+#define GRST_KEYSIZE 512
+#define GRST_PROXYCACHE "/../proxycache/"
+#define GRST_MAX_CHAIN_LEN 9
+#define GRST_BACKDATE_SECONDS 300
+
+int
+GRSTasn1FindField(const char *oid, char *coords,
+ char *asn1string,
+ struct GRSTasn1TagList taglist[], int lasttag,
+ int *result);
+
+/// Compare X509 Distinguished Name strings
+int GRSTx509NameCmp(char *a, char *b)
+///
+/// This function attempts to do with string representations what
+/// would ideally be done with OIDs/values. In particular, we equate
+/// "/Email=" == "/emailAddress=" to deal with this important change
+/// between OpenSSL 0.9.6 and 0.9.7.
+/// Other than that, it is currently the same as ordinary strcasecmp(3)
+/// (for consistency with EDG/LCG/EGEE gridmapdir case insensitivity.)
+{
+ int ret;
+ char *aa, *bb, *p;
+
+ if ((a == NULL) || (b == NULL)) return 1; /* NULL never matches */
+
+ aa = strdup(a);
+ while ((p = strstr(aa, "/emailAddress=")) != NULL)
+ {
+ memmove(&p[6], &p[13], strlen(&p[13]) + 1);
+ p[1] = 'E';
+ }
+
+ bb = strdup(b);
+ while ((p = strstr(bb, "/emailAddress=")) != NULL)
+ {
+ memmove(&p[6], &p[13], strlen(&p[13]) + 1);
+ p[1] = 'E';
+ }
+
+ ret = strcasecmp(aa, bb);
+
+ free(aa);
+ free(bb);
+
+ return ret;
+}
+
+
+/// Check critical extensions
+/*TODO MBD*/
+int GRSTx509KnownCriticalExts(X509 *cert)
+///
+/// Returning GRST_RET_OK if all of extensions are known to us or
+/// OpenSSL; GRST_REF_FAILED otherwise.
+///
+/// Since this function relies on functionality (X509_supported_extension)
+/// introduced in 0.9.7, then we do nothing and report an error
+/// (GRST_RET_FAILED) if one of the associated defines
+/// (X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) is absent.
+{
+ int i;
+ char s[80];
+ X509_EXTENSION *ex;
+
+#ifdef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION
+ for (i = 0; i < X509_get_ext_count(cert); ++i)
+ {
+ ex = X509_get_ext(cert, i);
+
+ if (X509_EXTENSION_get_critical(ex) &&
+ !X509_supported_extension(ex))
+ {
+ OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1);
+
+ if ((strcmp(s, GRST_PROXYCERTINFO_OID) != 0) &&
+ (strcmp(s, GRST_PROXYCERTINFO_OLD_OID) != 0))
+ return GRST_RET_FAILED;
+ }
+ }
+
+ return GRST_RET_OK;
+#else
+ return GRST_RET_FAILED;
+#endif
+}
+
+/// Check if certificate can be used as a CA to sign standard X509 certs
+int GRSTx509IsCA(X509 *cert)
+///
+/// Return GRST_RET_OK if true; GRST_RET_FAILED if not.
+{
+ int purpose_id;
+
+ purpose_id = X509_PURPOSE_get_by_sname("sslclient");
+
+ /* final argument to X509_check_purpose() is whether to check for CAness */
+
+ if (X509_check_purpose(cert, purpose_id + X509_PURPOSE_MIN, 1))
+ return GRST_RET_OK;
+ else return GRST_RET_FAILED;
+}
+
+int GRSTx509ChainFree(GRSTx509Chain *chain)
+{
+ GRSTx509Cert *grst_cert, *next_grst_cert;
+
+ if (chain == NULL) return GRST_RET_OK;
+
+ next_grst_cert = chain->firstcert;
+
+ while (next_grst_cert != NULL)
+ {
+ grst_cert = next_grst_cert;
+
+ if (grst_cert->issuer != NULL) free(grst_cert->issuer);
+ if (grst_cert->dn != NULL) free(grst_cert->dn);
+ if (grst_cert->value != NULL) free(grst_cert->value);
+ if (grst_cert->ocsp != NULL) free(grst_cert->ocsp);
+
+ next_grst_cert = grst_cert->next;
+ free(grst_cert);
+ }
+
+ free(chain);
+
+ return GRST_RET_OK;
+}
+
+/// Check a specific signature against a specific (VOMS) cert
+static int GRSTx509VerifySig(time_t *time1_time, time_t *time2_time,
+ unsigned char *txt, int txt_len,
+ unsigned char *sig, int sig_len,
+ X509 *cert, EVP_MD *md_type)
+///
+/// Returns GRST_RET_OK if signature is ok, other values if not.
+{
+ int ret;
+ EVP_PKEY *prvkey;
+ EVP_MD_CTX ctx;
+ time_t voms_service_time1, voms_service_time2;
+
+ prvkey = X509_extract_key(cert);
+ if (prvkey == NULL) return GRST_RET_FAILED;
+
+ OpenSSL_add_all_digests();
+#if OPENSSL_VERSION_NUMBER >= 0x0090701fL
+ EVP_MD_CTX_init(&ctx);
+ EVP_VerifyInit_ex(&ctx, md_type, NULL);
+#else
+ EVP_VerifyInit(&ctx, md_type);
+#endif
+
+ EVP_VerifyUpdate(&ctx, txt, txt_len);
+
+ ret = EVP_VerifyFinal(&ctx, sig, sig_len, prvkey);
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090701fL
+ EVP_MD_CTX_cleanup(&ctx);
+#endif
+ EVP_PKEY_free(prvkey);
+
+ if (ret != 1) return GRST_RET_FAILED;
+
+ voms_service_time1 =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0);
+ if (voms_service_time1 > *time1_time)
+ *time1_time = voms_service_time1;
+
+ voms_service_time2 =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0);
+ if (voms_service_time2 < *time1_time)
+ *time2_time = voms_service_time2;
+
+ return GRST_RET_OK ; /* verified */
+}
+
+/// Check the signature of the VOMS attributes
+static int GRSTx509VerifyVomsSig(time_t *time1_time, time_t *time2_time,
+ unsigned char *asn1string,
+ struct GRSTasn1TagList taglist[],
+ int lasttag,
+ char *vomsdir, int acnumber)
+///
+/// Returns GRST_RET_OK if signature is ok, other values if not.
+{
+#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d"
+#define GRST_ASN1_COORDS_VOMS_INFO "-1-1-%d-1"
+#define GRST_ASN1_COORDS_VOMS_HASH "-1-1-%d-2-1"
+#define GRST_ASN1_COORDS_VOMS_SIG "-1-1-%d-3"
+ int ihash, isig, iinfo;
+ char *certpath, *certpath2, acvomsdn[200], dn_coords[200],
+ info_coords[200], sig_coords[200], hash_coords[200];
+ unsigned char *p;
+ DIR *vomsDIR, *vomsDIR2;
+ struct dirent *vomsdirent, *vomsdirent2;
+ X509 *cert;
+ FILE *fp;
+ EVP_MD *md_type = NULL;
+ struct stat statbuf;
+ time_t voms_service_time1 = GRST_MAX_TIME_T, voms_service_time2 = 0,
+ tmp_time1, tmp_time2;
+ ASN1_OBJECT *hash_obj = NULL;
+
+ if ((vomsdir == NULL) || (vomsdir[0] == '\0')) return GRST_RET_FAILED;
+
+ snprintf(dn_coords, sizeof(dn_coords),
+ GRST_ASN1_COORDS_VOMS_DN, acnumber);
+
+ if (GRSTasn1GetX509Name(acvomsdn, sizeof(acvomsdn), dn_coords,
+ asn1string, taglist, lasttag) != GRST_RET_OK) return GRST_RET_FAILED;
+
+ snprintf(info_coords, sizeof(info_coords),
+ GRST_ASN1_COORDS_VOMS_INFO, acnumber);
+ iinfo = GRSTasn1SearchTaglist(taglist, lasttag, info_coords);
+
+ snprintf(hash_coords, sizeof(hash_coords),
+ GRST_ASN1_COORDS_VOMS_HASH, acnumber);
+ ihash = GRSTasn1SearchTaglist(taglist, lasttag, hash_coords);
+
+ snprintf(sig_coords, sizeof(sig_coords),
+ GRST_ASN1_COORDS_VOMS_SIG, acnumber);
+ isig = GRSTasn1SearchTaglist(taglist, lasttag, sig_coords);
+
+ if ((iinfo < 0) || (ihash < 0) || (isig < 0)) return GRST_RET_FAILED;
+
+ /* determine hash algorithm's type */
+
+ p = &asn1string[taglist[ihash].start];
+
+ d2i_ASN1_OBJECT(&hash_obj, &p,
+ taglist[ihash].length+taglist[ihash].headerlength);
+
+ md_type = EVP_get_digestbyname(OBJ_nid2sn(OBJ_obj2nid(hash_obj)));
+
+ if (md_type == NULL) return GRST_RET_FAILED;
+
+
+ vomsDIR = opendir(vomsdir);
+ if (vomsDIR == NULL) return GRST_RET_FAILED;
+
+ while ((vomsdirent = readdir(vomsDIR)) != NULL)
+ {
+ if (vomsdirent->d_name[0] == '.') continue;
+
+ asprintf(&certpath, "%s/%s", vomsdir, vomsdirent->d_name);
+ stat(certpath, &statbuf);
+
+ if (S_ISDIR(statbuf.st_mode))
+ {
+ vomsDIR2 = opendir(certpath);
+ GRSTerrorLog(GRST_LOG_DEBUG,
+ "Descend VOMS subdirectory %s", certpath);
+ free(certpath);
+
+ if (vomsDIR2 == NULL) continue;
+
+ while ((vomsdirent2 = readdir(vomsDIR2)) != NULL)
+ {
+ if (vomsdirent2->d_name[0] == '.') continue;
+
+ asprintf(&certpath2, "%s/%s/%s",
+ vomsdir, vomsdirent->d_name, vomsdirent2->d_name);
+
+ fp = fopen(certpath2, "r");
+ GRSTerrorLog(GRST_LOG_DEBUG,
+ "Examine VOMS cert %s", certpath2);
+ free(certpath2);
+ if (fp == NULL) continue;
+
+ cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ fclose(fp);
+ if (cert == NULL) continue;
+
+ tmp_time1 = 0;
+ tmp_time2 = GRST_MAX_TIME_T;
+
+ if (GRSTx509VerifySig(&tmp_time1, &tmp_time2,
+ &asn1string[taglist[iinfo].start],
+ taglist[iinfo].length+taglist[iinfo].headerlength,
+ &asn1string[taglist[isig].start+
+ taglist[isig].headerlength+1],
+ taglist[isig].length - 1,
+ cert, md_type) == GRST_RET_OK)
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Matched VOMS cert file %s", vomsdirent2->d_name);
+
+ /* Store more permissive time ranges for now */
+
+ if (tmp_time1 < voms_service_time1)
+ voms_service_time1 = tmp_time1;
+
+ if (tmp_time2 > voms_service_time2)
+ voms_service_time2 = tmp_time2;
+ }
+
+ X509_free(cert);
+ }
+
+ closedir(vomsDIR2);
+ }
+ else
+ {
+ fp = fopen(certpath, "r");
+ GRSTerrorLog(GRST_LOG_DEBUG, "Examine VOMS cert %s", certpath);
+ free(certpath);
+ if (fp == NULL) continue;
+
+ cert = PEM_read_X509(fp, NULL, NULL, NULL);
+ fclose(fp);
+ if (cert == NULL) continue;
+
+ tmp_time1 = 0;
+ tmp_time2 = GRST_MAX_TIME_T;
+
+ if (GRSTx509VerifySig(&tmp_time1, &tmp_time2,
+ &asn1string[taglist[iinfo].start],
+ taglist[iinfo].length+taglist[iinfo].headerlength,
+ &asn1string[taglist[isig].start+
+ taglist[isig].headerlength+1],
+ taglist[isig].length - 1,
+ cert, md_type) == GRST_RET_OK)
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Matched VOMS cert file %s", vomsdirent->d_name);
+
+ /* Store more permissive time ranges for now */
+
+ if (tmp_time1 < voms_service_time1)
+ voms_service_time1 = tmp_time1;
+
+ if (tmp_time2 > voms_service_time2)
+ voms_service_time2 = tmp_time2;
+ }
+
+ X509_free(cert);
+ }
+ }
+
+ closedir(vomsDIR);
+
+ if ((voms_service_time1 == GRST_MAX_TIME_T) || (voms_service_time2 == 0))
+ return GRST_RET_FAILED;
+
+ /* now we tighten up the VOMS AC time range using the most permissive
+ pair of VOMS certificate ranges (in case of multiple, possibly
+ overlapping, matching certificates in the VOMS certs store) */
+
+ if (voms_service_time1 > *time1_time) *time1_time = voms_service_time1;
+
+ if (voms_service_time2 < *time2_time) *time2_time = voms_service_time2;
+
+ return GRST_RET_OK;
+}
+
+/// Check the signature of the VOMS attributes using the LSC file cert
+static int GRSTx509VerifyVomsSigCert(time_t *time1_time, time_t *time2_time,
+ unsigned char *asn1string,
+ struct GRSTasn1TagList taglist[],
+ int lasttag,
+ char *vomsdir, int acnumber,
+ int ivomscert,
+ char *capath,
+ char *acvomsdn,
+ char *voname)
+///
+/// Returns GRST_RET_OK if signature is ok, other values if not.
+{
+#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d"
+#define GRST_ASN1_COORDS_VOMS_INFO "-1-1-%d-1"
+#define GRST_ASN1_COORDS_VOMS_SIG "-1-1-%d-3"
+ int ret, isig, iinfo, chain_errors = GRST_RET_OK,
+ cadn_len, vomsdn_len, lsc_found = 0, ihash;
+ char *lscpath, *p, *cacertpath,
+ *vodir, *vomscert_cadn, *vomscert_vomsdn,
+ *lsc_cadn, *lsc_vomsdn;
+ unsigned char *q;
+ unsigned long issuerhash = 0;
+ DIR *voDIR;
+ struct dirent *vodirent;
+ X509 *cacert = NULL, *vomscert = NULL;
+ FILE *fp;
+ struct stat statbuf;
+ time_t tmp_time;
+ ASN1_OBJECT *hash_obj = NULL;
+ char coords[200];
+ EVP_MD *md_type = NULL;
+ time_t voms_service_time1, voms_service_time2;
+
+ if ((vomsdir == NULL) || (vomsdir[0] == '\0')) return GRST_RET_FAILED;
+
+ q = &asn1string[taglist[ivomscert].start + 12];
+
+ vomscert = d2i_X509(NULL, (const unsigned char **) &q,
+ taglist[ivomscert].length - 8);
+
+ if (vomscert == NULL)
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Failed to read included VOMS cert in GRSTx509VerifyVomsSigCert()");
+ return GRST_RET_FAILED;
+ }
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "Found included VOMS cert in GRSTx509VerifyVomsSigCert()");
+
+ snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_INFO, acnumber);
+ iinfo = GRSTasn1SearchTaglist(taglist, lasttag, coords);
+
+ snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_HASH, acnumber);
+ ihash = GRSTasn1SearchTaglist(taglist, lasttag, coords);
+
+ snprintf(coords, sizeof(coords), GRST_ASN1_COORDS_VOMS_SIG, acnumber);
+ isig = GRSTasn1SearchTaglist(taglist, lasttag, coords);
+
+ if ((iinfo < 0) || (ihash < 0) || (isig < 0)) return GRST_RET_FAILED;
+
+ q = &asn1string[taglist[ihash].start];
+ d2i_ASN1_OBJECT(&hash_obj, &q,
+ taglist[ihash].length+taglist[ihash].headerlength);
+
+ md_type = EVP_get_digestbyname(OBJ_nid2sn(OBJ_obj2nid(hash_obj)));
+ if (md_type == NULL) return GRST_RET_FAILED;
+
+ /* check issuer CA certificate */
+
+ issuerhash = X509_NAME_hash(X509_get_issuer_name(vomscert));
+ asprintf(&cacertpath, "%s/%.8x.0", capath, issuerhash);
+
+ /* check voms cert DN matches DN from AC */
+
+ vomscert_vomsdn = X509_NAME_oneline(X509_get_subject_name(vomscert),NULL,0);
+
+ if (GRSTx509NameCmp(vomscert_vomsdn, acvomsdn) != 0)
+ {
+ free(vomscert_vomsdn);
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "Included VOMS cert DN does not match AC issuer DN!");
+ return GRST_RET_FAILED;
+ }
+
+ free(vomscert_vomsdn);
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "Look for CA root file %s", cacertpath);
+
+ fp = fopen(cacertpath, "r");
+ free(cacertpath);
+
+ if (fp == NULL) return GRST_RET_FAILED;
+ else
+ {
+ cacert = PEM_read_X509(fp, NULL, NULL, NULL);
+ fclose(fp);
+ if (cacert != NULL)
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, " Loaded CA root cert from file");
+ }
+ else
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, " Failed to load CA root cert file");
+ return GRST_RET_FAILED;
+ }
+ }
+
+ /* check times CA cert times, and reject if necessary */
+
+ tmp_time = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notBefore(cacert)), 0);
+ if (tmp_time > *time1_time) chain_errors |= GRST_CERT_BAD_TIME;
+
+ tmp_time = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notAfter(cacert)), 0);
+ if (tmp_time < *time2_time) chain_errors |= GRST_CERT_BAD_TIME;
+
+ /* check times VOMS cert times, and tighten if necessary */
+
+ tmp_time = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notBefore(vomscert)), 0);
+ if (tmp_time > *time1_time) chain_errors |= GRST_CERT_BAD_TIME;
+
+ tmp_time = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notAfter(vomscert)), 0);
+ if (tmp_time < *time2_time) chain_errors |= GRST_CERT_BAD_TIME;
+
+ ret = X509_check_issued(cacert, vomscert);
+ GRSTerrorLog(GRST_LOG_DEBUG, "X509_check_issued returns %d", ret);
+
+ vomscert_cadn = X509_NAME_oneline(X509_get_issuer_name(vomscert),NULL,0);
+
+
+ if (ret != X509_V_OK) return (chain_errors | GRST_CERT_BAD_SIG);
+
+ asprintf(&vodir, "%s/%s", vomsdir, voname);
+
+ voDIR = opendir(vodir);
+ if (voDIR == NULL) return GRST_RET_FAILED;
+
+ cadn_len = strlen(vomscert_cadn);
+ vomsdn_len = strlen(acvomsdn);
+
+ lsc_cadn = malloc(cadn_len + 2);
+ lsc_vomsdn = malloc(vomsdn_len + 2);
+
+ while (((vodirent = readdir(voDIR)) != NULL) && !lsc_found)
+ {
+ if (vodirent->d_name[0] == '.') continue;
+
+ if (strlen(vodirent->d_name) < 5) continue;
+
+ if (strcmp(&(vodirent->d_name[strlen(vodirent->d_name)-4]), ".lsc") != 0) continue;
+
+ asprintf(&lscpath, "%s/%s", vodir, vodirent->d_name);
+ stat(lscpath, &statbuf);
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "Check LSC file %s for %s,%s",
+ lscpath, acvomsdn, vomscert_cadn);
+
+ if ((fp = fopen(lscpath, "r")) != NULL)
+ {
+ lsc_cadn[0] = '\0';
+ lsc_vomsdn[0] = '\0';
+
+ if ((fgets(lsc_vomsdn, vomsdn_len + 2, fp) != NULL)
+ && (fgets(lsc_cadn, cadn_len + 2, fp) != NULL))
+ {
+ if ((p = index(lsc_cadn, '\n')) != NULL) *p = '\0';
+
+ if ((p = index(lsc_vomsdn, '\n')) != NULL) *p = '\0';
+
+ if ((GRSTx509NameCmp(lsc_cadn, vomscert_cadn) == 0) &&
+ (GRSTx509NameCmp(lsc_vomsdn, acvomsdn) == 0))
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Matched LSC file %s", lscpath);
+ lsc_found = 1;
+ }
+ }
+
+ fclose(fp);
+ }
+
+ free(lscpath);
+ }
+
+ closedir(voDIR);
+ free(vodir);
+ free(vomscert_cadn);
+ free(lsc_cadn);
+ free(lsc_vomsdn);
+
+ if (!lsc_found) {
+ chain_errors |= GRST_CERT_BAD_SIG;
+ goto end;
+ }
+
+ ret = GRSTx509VerifySig(&voms_service_time1, &voms_service_time2,
+ &asn1string[taglist[iinfo].start],
+ taglist[iinfo].length+taglist[iinfo].headerlength,
+ &asn1string[taglist[isig].start+
+ taglist[isig].headerlength+1],
+ taglist[isig].length - 1,
+ vomscert, md_type);
+ if (ret != GRST_RET_OK) {
+ chain_errors |= GRST_CERT_BAD_SIG;
+ goto end;
+ }
+
+ if (voms_service_time1 > *time1_time) *time1_time = voms_service_time1;
+ if (voms_service_time2 < *time2_time) *time2_time = voms_service_time2;
+
+end:
+ if (cacert)
+ X509_free(cacert);
+ if (vomscert)
+ X509_free(vomscert);
+
+ return (chain_errors ? GRST_RET_FAILED : GRST_RET_OK);
+}
+
+/// Get the VOMS attributes in the given extension
+static int GRSTx509ChainVomsAdd(GRSTx509Cert **grst_cert,
+ time_t time1_time, time_t time2_time,
+ int delegation,
+ X509_EXTENSION *ex,
+ GRSTx509Cert *user_cert, char *vomsdir, char *capath)
+///
+/// Add any VOMS credentials found into the chain. Always returns GRST_RET_OK
+/// - even for invalid credentials, which are flagged in errors field
+{
+#define MAXTAG 500
+#define GRST_ASN1_COORDS_FQAN "-1-1-%d-1-7-1-2-1-2-%d"
+#define GRST_ASN1_COORDS_ISSUER_DN "-1-1-%d-1-2-1-1-1-1-%%d-1-%%d"
+#define GRST_ASN1_COORDS_ISSUER_SERIAL "-1-1-%d-1-2-1-2"
+#define GRST_ASN1_COORDS_VOMS_DN "-1-1-%d-1-3-1-1-1-%%d-1-%%d"
+#define GRST_ASN1_COORDS_TIME1 "-1-1-%d-1-6-1"
+#define GRST_ASN1_COORDS_TIME2 "-1-1-%d-1-6-2"
+#define GRST_ASN1_COORDS_VOMSCERT "-1-1-%d-1-8-%%d-%%d"
+
+ ASN1_OCTET_STRING *asn1data;
+ char *asn1string, acissuerdn[200], acvomsdn[200],
+ dn_coords[200], fqan_coords[200], time1_coords[200],
+ time2_coords[200], vomscert_coords[200], *voname = NULL,
+ serial_coords[200];
+ long asn1length;
+ int lasttag=-1, itag, i, j, acnumber = 1, chain_errors = 0,
+ ivomscert, tmp_chain_errors, ret;
+ char *acissuerserial = NULL;
+ struct GRSTasn1TagList taglist[MAXTAG+1];
+ time_t actime1 = 0, actime2 = 0, time_now,
+ tmp_time1, tmp_time2;
+ ASN1_INTEGER acissuerserialASN1;
+
+ asn1data = X509_EXTENSION_get_data(ex);
+ asn1string = ASN1_STRING_data(asn1data);
+ asn1length = ASN1_STRING_length(asn1data);
+
+ GRSTasn1ParseDump(NULL, asn1string, asn1length, taglist, MAXTAG, &lasttag);
+
+ for (acnumber = 1; ; ++acnumber) /* go through ACs one by one */
+ {
+ chain_errors = 0;
+
+ snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_ISSUER_DN, acnumber);
+ if (GRSTasn1GetX509Name(acissuerdn, sizeof(acissuerdn), dn_coords,
+ asn1string, taglist, lasttag) != GRST_RET_OK)
+ break;
+
+ snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_VOMS_DN, acnumber);
+ if (GRSTasn1GetX509Name(acvomsdn, sizeof(acvomsdn), dn_coords,
+ asn1string, taglist, lasttag) != GRST_RET_OK)
+ break;
+
+ if ((GRSTx509NameCmp(user_cert->dn, acissuerdn) != 0) && /* old */
+ (GRSTx509NameCmp(user_cert->issuer, acissuerdn) != 0)) /* new */
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+
+ /* check serial numbers */
+
+ snprintf(serial_coords, sizeof(serial_coords),
+ GRST_ASN1_COORDS_ISSUER_SERIAL, acnumber);
+
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, serial_coords);
+
+ if (itag > -1)
+ {
+ acissuerserialASN1.length = taglist[itag].length;
+ acissuerserialASN1.type = V_ASN1_INTEGER;
+ acissuerserialASN1.data = &asn1string[taglist[itag].start+taglist[itag].headerlength];
+
+ acissuerserial = i2s_ASN1_INTEGER(NULL, &acissuerserialASN1);
+/*
+ p = &asn1string[taglist[itag].start+taglist[itag].headerlength];
+
+ if (taglist[itag].length == 2)
+ acissuerserial = p[1] + p[0] * 0x100;
+ else if (taglist[itag].length == 3)
+ acissuerserial = p[2] + p[1] * 0x100 + p[0] * 0x10000;
+ else if (taglist[itag].length == 4)
+ acissuerserial = p[3] + p[2] * 0x100 + p[1] * 0x10000 +
+ p[0] * 0x1000000;
+*/
+ }
+
+ if (strcmp(acissuerserial, user_cert->serial) != 0)
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+
+ /* get times */
+
+ snprintf(time1_coords, sizeof(time1_coords), GRST_ASN1_COORDS_TIME1, acnumber);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, time1_coords);
+
+ if (itag > -1) actime1 = GRSTasn1TimeToTimeT(
+ &asn1string[taglist[itag].start+
+ taglist[itag].headerlength],
+ taglist[itag].length);
+ else actime1 = 0;
+
+ snprintf(time2_coords, sizeof(time2_coords), GRST_ASN1_COORDS_TIME2, acnumber);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, time2_coords);
+
+ if (itag > -1) actime2 = GRSTasn1TimeToTimeT(
+ &asn1string[taglist[itag].start+
+ taglist[itag].headerlength],
+ taglist[itag].length);
+ else actime2 = 0;
+
+ if (actime1 > time1_time) time1_time = actime1;
+ if (actime2 < time2_time) time2_time = actime2;
+
+ time(&time_now);
+ if ((time1_time > time_now + 300) || (time2_time < time_now))
+ chain_errors |= GRST_CERT_BAD_TIME;
+
+ /* get first FQAN and use to get VO name */
+
+ snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, 1);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords);
+
+ if ((itag > -1) &&
+ (asn1string[taglist[itag].start+taglist[itag].headerlength] == '/'))
+ {
+ for (j=1;
+ (asn1string[taglist[itag].start+taglist[itag].headerlength+j] != '/') &&
+ (j < taglist[itag].length);
+ ++j) ;
+
+ asprintf(&voname, "%.*s", j-1,
+ &(asn1string[taglist[itag].start+taglist[itag].headerlength+1]));
+ }
+
+ snprintf(vomscert_coords, sizeof(vomscert_coords),
+ GRST_ASN1_COORDS_VOMSCERT, acnumber);
+ ret = GRSTasn1FindField(GRST_VOMS_PK_CERT_LIST_OID, vomscert_coords, asn1string,
+ taglist, lasttag, &ivomscert);
+
+ /* try using internal VOMS issuer cert */
+ tmp_chain_errors = GRST_CERT_BAD_SIG;
+ tmp_time1 = time1_time;
+ tmp_time2 = time2_time;
+ if ((ivomscert > -1) &&
+ (voname != NULL) &&
+ (GRSTx509VerifyVomsSigCert(&tmp_time1, &tmp_time2,
+ asn1string, taglist, lasttag, vomsdir, acnumber,
+ ivomscert, capath, acvomsdn,
+ voname) == GRST_RET_OK))
+ {
+ tmp_chain_errors = 0;
+ time1_time = tmp_time1;
+ time2_time = tmp_time2;
+ }
+
+ if (voname != NULL)
+ {
+ free(voname);
+ voname = NULL;
+ }
+
+ if ((tmp_chain_errors != 0) &&
+ (GRSTx509VerifyVomsSig(&time1_time, &time2_time,
+ asn1string, taglist, lasttag, vomsdir, acnumber)
+ != GRST_RET_OK))
+ chain_errors |= GRST_CERT_BAD_SIG;
+
+ for (i=1; ; ++i) /* now go through FQANs */
+ {
+ snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, i);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords);
+
+ if (itag > -1)
+ {
+ (*grst_cert)->next = malloc(sizeof(GRSTx509Cert));
+ *grst_cert = (*grst_cert)->next;
+ bzero(*grst_cert, sizeof(GRSTx509Cert));
+
+ (*grst_cert)->notbefore = time1_time;
+ (*grst_cert)->notafter = time2_time;
+ asprintf(&((*grst_cert)->value), "%.*s",
+ taglist[itag].length,
+ &asn1string[taglist[itag].start+
+ taglist[itag].headerlength]);
+
+ (*grst_cert)->errors = chain_errors; /* ie may be invalid */
+ (*grst_cert)->type = GRST_CERT_TYPE_VOMS;
+ (*grst_cert)->issuer = strdup(acvomsdn);
+ (*grst_cert)->dn = strdup(user_cert->dn);
+ (*grst_cert)->delegation = delegation;
+ }
+ else break;
+ }
+ }
+
+ return GRST_RET_OK;
+}
+int GRSTx509ChainLoad(GRSTx509Chain **chain,
+ STACK_OF(X509) *certstack, X509 *lastcert,
+ char *capath, char *vomsdir)
+{
+ X509 *cert; /* Points to the current cert in the loop */
+ X509 *cacert = NULL; /* The CA root cert */
+ int depth = 0; /* Depth of cert chain */
+ int chain_errors = 0; /* records previous errors */
+ int first_non_ca; /* number of the EEC issued to user by CA */
+ size_t len,len2; /* Lengths of issuer and cert DN */
+ int IsCA; /* Holds whether cert is allowed to sign */
+ int prevIsCA; /* Holds whether previous cert in chain is
+ allowed to sign */
+ int prevIsLimited; /* previous cert was proxy and limited */
+ int i,j,ret; /* Iteration/temp variables */
+ char *proxy_part_DN; /* Pointer to end part of current-cert-in-chain
+ maybe eg "/CN=proxy" */
+ char s[80], *p;
+ char *cacertpath;
+ unsigned long subjecthash = 0; /* hash of the name of first cert */
+ unsigned long issuerhash = 0; /* hash of issuer name of first cert */
+ FILE *fp;
+ X509_EXTENSION *ex;
+ time_t now;
+ GRSTx509Cert *grst_cert, *new_grst_cert, *user_cert = NULL;
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "GRSTx509ChainLoadCheck() starts");
+
+ time(&now);
+
+ first_non_ca = 0; /* set to something predictable if things fail */
+
+ /* Set necessary preliminary values */
+ IsCA = TRUE; /* =prevIsCA - start from a CA */
+ prevIsLimited = 0;
+
+ /* Get the client cert chain */
+ if (certstack != NULL)
+ depth = sk_X509_num(certstack); /* How deep is that chain? */
+
+ if ((depth == 0) && (lastcert == NULL))
+ {
+ *chain = NULL;
+ return GRST_RET_FAILED;
+ }
+
+ cert = sk_X509_value(certstack, depth - 1);
+ subjecthash = X509_NAME_hash(X509_get_subject_name(cert));
+ issuerhash = X509_NAME_hash(X509_get_issuer_name(cert));
+ asprintf(&cacertpath, "%s/%.8x.0", capath, issuerhash);
+
+ GRSTerrorLog(GRST_LOG_DEBUG, "Look for CA root file %s", cacertpath);
+
+ fp = fopen(cacertpath, "r");
+ free(cacertpath);
+
+ if (fp == NULL) chain_errors |= GRST_CERT_BAD_CHAIN;
+ else
+ {
+ cacert = PEM_read_X509(fp, NULL, NULL, NULL);
+ fclose(fp);
+ if (cacert != NULL)
+ GRSTerrorLog(GRST_LOG_DEBUG, " Loaded CA root cert from file");
+ else
+ GRSTerrorLog(GRST_LOG_DEBUG, " Failed to load CA root cert file");
+ }
+
+ *chain = malloc(sizeof(GRSTx509Chain));
+ bzero(*chain, sizeof(GRSTx509Chain));
+
+ /* Check the client chain */
+ for (i = depth - ((subjecthash == issuerhash) ? 1 : 0);
+ i >= ((lastcert == NULL) ? 0 : -1);
+ --i)
+ /* loop through client-presented chain starting at CA end */
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Process cert at depth %d in chain", i);
+
+ prevIsCA=IsCA;
+
+ new_grst_cert = malloc(sizeof(GRSTx509Cert));
+ bzero(new_grst_cert, sizeof(GRSTx509Cert));
+ new_grst_cert->errors = chain_errors;
+
+ if ((*chain)->firstcert == NULL)
+ {
+ GRSTerrorLog(GRST_LOG_DEBUG, "Initialise chain");
+ (*chain)->firstcert = new_grst_cert;
+ }
+ else grst_cert->next = new_grst_cert;
+
+ grst_cert = new_grst_cert;
+
+ /* Choose X509 certificate and point to it with 'cert' */
+ if (i < 0) cert = lastcert;
+ else if (i == depth)
+ cert = cacert; /* the self-signed CA from the store*/
+ else if ((i == depth - 1) && (subjecthash == issuerhash))
+ cert = cacert; /* ie claims to be a copy of a self-signed CA */
+ else cert = sk_X509_value(certstack, i);
+
+ if (cert != NULL)
+ {
+ if ((i == depth - 1) && (subjecthash != issuerhash))
+ {
+ /* if first cert does not claim to be a self-signed copy
+ of a CA root cert in the store, we check the signature */
+
+ if (cacert == NULL)
+ {
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ ret = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
+ }
+ else
+ {
+ ret = X509_check_issued(cacert, cert);
+
+ GRSTerrorLog(GRST_LOG_DEBUG,
+ "Cert sig check %d returns %d", i, ret);
+
+ if (ret != X509_V_OK)
+ new_grst_cert->errors |= GRST_CERT_BAD_SIG;
+ }
+ }
+ else if ((i == depth - 2) && (subjecthash == issuerhash))
+ {
+ /* first cert claimed to be a self-signed copy of a CA root
+ cert in the store, we check the signature of the second
+ cert, using OUR copy of the CA cert DIRECT from the store */
+
+ if (cacert == NULL)
+ {
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ ret = X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT;
+ }
+ else
+ {
+ ret = X509_check_issued(cacert, cert);
+
+ GRSTerrorLog(GRST_LOG_DEBUG,
+ "Cert sig check %d returns %d", i, ret);
+
+ if (ret != X509_V_OK)
+ new_grst_cert->errors |= GRST_CERT_BAD_SIG;
+ }
+ }
+ else if (i < depth - 1)
+ {
+ /* otherwise a normal part of the chain: note that if the
+ first cert claims to be a self-signed copy of a CA root
+ cert in the store, we never use it for sig checking */
+
+ ret = X509_check_issued(sk_X509_value(certstack, i + 1), cert);
+
+ GRSTerrorLog(GRST_LOG_DEBUG,
+ "Cert sig check %d returns %d", i, ret);
+
+ if ((ret != X509_V_OK) &&
+ (ret != X509_V_ERR_KEYUSAGE_NO_CERTSIGN))
+ new_grst_cert->errors |= GRST_CERT_BAD_SIG;
+
+ /* NO_CERTSIGN can still be ok due to Proxy Certificates */
+ }
+
+ p = i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(cert));
+ strncpy(new_grst_cert->serial, p, GRST_X509_SERIAL_DIGITS);
+ new_grst_cert->serial[GRST_X509_SERIAL_DIGITS] = '\0';
+ free(p);
+
+ new_grst_cert->notbefore = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notBefore(cert)), 0);
+ new_grst_cert->notafter = GRSTasn1TimeToTimeT(
+ ASN1_STRING_data(X509_get_notAfter(cert)), 0);
+
+ /* we check times and record if invalid */
+
+ if (now < new_grst_cert->notbefore)
+ new_grst_cert->errors |= GRST_CERT_BAD_TIME;
+
+ if (now > new_grst_cert->notafter)
+ new_grst_cert->errors |= GRST_CERT_BAD_TIME;
+
+ new_grst_cert->dn = X509_NAME_oneline(X509_get_subject_name(cert),NULL,0);
+ new_grst_cert->issuer = X509_NAME_oneline(X509_get_issuer_name(cert),NULL,0);
+ len = strlen(new_grst_cert->dn);
+ len2 = strlen(new_grst_cert->issuer);
+
+ /* always treat a first cert from the CA files as a
+ CA: this is really for lousy CAs that dont create
+ proper v3 root certificates */
+
+ if (i == depth) IsCA = TRUE;
+ else IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK);
+
+ /* If any forebear certificate is not allowed to sign we must
+ assume all decendents are proxies and cannot sign either */
+ if (prevIsCA)
+ {
+ if (IsCA)
+ {
+ new_grst_cert->type = GRST_CERT_TYPE_CA;
+ }
+ else
+ {
+ new_grst_cert->type = GRST_CERT_TYPE_EEC;
+ first_non_ca = i;
+ user_cert = new_grst_cert;
+ new_grst_cert->delegation
+ = (lastcert == NULL) ? i : i + 1;
+ }
+ }
+ else
+ {
+ new_grst_cert->type = GRST_CERT_TYPE_PROXY;
+
+ IsCA = FALSE;
+ /* Force proxy check next iteration. Important because I can
+ sign any CA I create! */
+
+ new_grst_cert->delegation = (lastcert == NULL) ? i : i + 1;
+ }
+
+ if (!prevIsCA)
+ {
+ /* issuer didn't have CA status, so this is (at best) a proxy:
+ check for bad proxy extension*/
+
+ if (prevIsLimited) /* we reject proxies of limited proxies! */
+ {
+ new_grst_cert->errors |= GRST_CERT_BAD_CHAIN;
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ }
+
+ /* User not allowed to sign shortened DN */
+ if (len2 > len)
+ {
+ new_grst_cert->errors |= GRST_CERT_BAD_CHAIN;
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ }
+
+ /* Proxy subject must begin with issuer. */
+ if (strncmp(new_grst_cert->dn, new_grst_cert->issuer, len2) != 0)
+ {
+ new_grst_cert->errors |= GRST_CERT_BAD_CHAIN;
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ }
+
+ /* Set pointer to end of base DN in cert_DN */
+ proxy_part_DN = &(new_grst_cert->dn[len2]);
+
+ /* First attempt at support for Old and New style GSI
+ proxies: /CN=anything is ok for now */
+ if (strncmp(proxy_part_DN, "/CN=", 4) != 0)
+ {
+ new_grst_cert->errors |= GRST_CERT_BAD_CHAIN;
+ chain_errors |= GRST_CERT_BAD_CHAIN;
+ }
+
+ if (strncmp(proxy_part_DN, "/CN=limited proxy", 17) == 0)
+ prevIsLimited = 1; /* ready for next cert ... */
+
+ for (j=0; j < X509_get_ext_count(cert); ++j)
+ {
+ ex = X509_get_ext(cert, j);
+ OBJ_obj2txt(s,sizeof(s),X509_EXTENSION_get_object(ex),1);
+
+ if (strcmp(s, GRST_VOMS_OID) == 0) /* a VOMS extension */
+ {
+ GRSTx509ChainVomsAdd(&grst_cert,
+ new_grst_cert->notbefore,
+ new_grst_cert->notafter,
+ (lastcert == NULL) ? i : i+1,
+ ex,
+ user_cert,
+ vomsdir,
+ capath);
+ }
+ }
+ }
+ }
+
+
+ } /* end of for loop */
+
+ if (cacert != NULL) X509_free(cacert);
+
+ return GRST_RET_OK;
+}
+
+/// Check certificate chain for GSI proxy acceptability.
+int GRSTx509ChainLoadCheck(GRSTx509Chain **chain,
+ STACK_OF(X509) *certstack, X509 *lastcert,
+ char *capath, char *vomsdir)
+///
+/// Returns GRST_RET_OK if valid; caNl errors otherwise.
+///
+/// The GridSite version handles old and new style Globus proxies, and
+/// proxies derived from user certificates issued with "X509v3 Basic
+/// Constraints: CA:FALSE" (eg UK e-Science CA)
+///
+/// TODO: we do not yet check ProxyCertInfo and ProxyCertPolicy extensions
+/// (although via GRSTx509KnownCriticalExts() we can accept them.)
+{
+ canl_ctx cctx = NULL;
+ int err = 0;
+
+ cctx = canl_create_ctx();
+ if (cctx == NULL)
+ return GRST_RET_FAILED;
+
+ /* Use caNl to check the certificate*/
+ err = canl_verify_chain(cctx, NULL, certstack, capath);
+
+ /* Then fetch info about chain into grst structures */
+ GRSTx509ChainLoad(chain, certstack, lastcert, capath, vomsdir);
+
+ canl_free_ctx(cctx);
+
+ return err;
+}
+
+/* Check certificate chain for GSI proxy acceptability. */
+int GRSTx509CheckChain(int *first_non_ca, X509_STORE_CTX *store_ctx)
+/* Returns X509_V_OK/GRST_RET_OK if valid; caNl errors otherwise.
+ We do not check chain links between certs here: this is done by
+ GRST_check_issued/X509_check_issued in mod_ssl's ssl_engine_init.c */
+{
+ canl_err_code ret = 0; /* Canl error code */
+
+ canl_ctx cctx = NULL;
+
+ /* Look for the store context */
+ if (!store_ctx)
+ return X509_V_ERR_INVALID_CA; /* TODO really not clever*/
+
+ cctx = canl_create_ctx();
+ if (cctx == NULL)
+ return GRST_RET_FAILED;
+
+ /* Verify chain using caNl, without openssl part */
+ ret = canl_verify_chain_wo_ossl(cctx, NULL, store_ctx);
+
+ canl_free_ctx(cctx);
+
+ return ret; /* this is also GRST_RET_OK, of course - by choice */
+}
+
+/// Example VerifyCallback routine
+int GRSTx509VerifyCallback (int ok, X509_STORE_CTX *ctx)
+{
+ int errnum = X509_STORE_CTX_get_error(ctx);
+ int errdepth = X509_STORE_CTX_get_error_depth(ctx);
+ int first_non_ca;
+
+#ifndef X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION
+#define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34
+#endif
+
+ if (errnum == X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION)
+ {
+ if (GRSTx509KnownCriticalExts(X509_STORE_CTX_get_current_cert(ctx))
+ == GRST_RET_OK)
+ {
+ ok = TRUE;
+ errnum = X509_V_OK;
+ X509_STORE_CTX_set_error(ctx, errnum);
+ }
+ }
+ else if ((errdepth == 0) &&
+ (errnum == X509_V_OK) &&
+ (GRSTx509CheckChain(&first_non_ca, ctx) != X509_V_OK)) ok = FALSE;
+
+
+ return ok;
+
+// check this
+
+// if (ok) return GRST_RET_OK;
+// else return GRST_RET_FAILED;
+}
+
+/// Get the VOMS attributes in the given extension
+int GRSTx509ParseVomsExt(int *lastcred, int maxcreds, size_t credlen,
+ char *creds, time_t time1_time, time_t time2_time,
+ X509_EXTENSION *ex,
+ char *ucuserdn, char *ucissuerdn, char *ucserial,
+ char *vomsdir)
+///
+/// Puts any VOMS credentials found into the Compact Creds string array
+/// starting at *creds. Always returns GRST_RET_OK - even for invalid
+/// credentials, which are just ignored.
+{
+#define MAXTAG 500
+#define GRST_ASN1_COORDS_FQAN "-1-1-%d-1-7-1-2-1-2-%d"
+#define GRST_ASN1_COORDS_ISSUER_DN "-1-1-%d-1-2-1-1-1-1-%%d-1-%%d"
+#define GRST_ASN1_COORDS_TIME1 "-1-1-%d-1-6-1"
+#define GRST_ASN1_COORDS_TIME2 "-1-1-%d-1-6-2"
+ ASN1_OCTET_STRING *asn1data;
+ char *asn1string, acissuerdn[200],
+ dn_coords[200], fqan_coords[200], time1_coords[200],
+ time2_coords[200], serial_coords[200];
+ long asn1length;
+ int lasttag=-1, itag, i, acnumber = 1;
+ char *acissuerserial = NULL;
+ struct GRSTasn1TagList taglist[MAXTAG+1];
+ time_t actime1, actime2, time_now;
+ ASN1_INTEGER acissuerserialASN1;
+
+ asn1data = X509_EXTENSION_get_data(ex);
+ asn1string = ASN1_STRING_data(asn1data);
+ asn1length = ASN1_STRING_length(asn1data);
+
+ GRSTasn1ParseDump(NULL, asn1string, asn1length, taglist, MAXTAG, &lasttag);
+
+ for (acnumber = 1; ; ++acnumber) /* go through ACs one by one */
+ {
+ /* check names */
+
+ snprintf(dn_coords, sizeof(dn_coords), GRST_ASN1_COORDS_ISSUER_DN, acnumber);
+ if (GRSTasn1GetX509Name(acissuerdn, sizeof(acissuerdn), dn_coords,
+ asn1string, taglist, lasttag) != GRST_RET_OK) break;
+
+ if ((GRSTx509NameCmp(ucuserdn, acissuerdn) != 0) && /* old */
+ (GRSTx509NameCmp(ucissuerdn, acissuerdn) != 0)) /* new */
+ continue;
+
+ /* check serial numbers */
+
+ snprintf(serial_coords, sizeof(serial_coords),
+ GRST_ASN1_COORDS_ISSUER_SERIAL, acnumber);
+
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, serial_coords);
+
+ if (itag > -1)
+ {
+ acissuerserialASN1.length = taglist[itag].length;
+ acissuerserialASN1.type = V_ASN1_INTEGER;
+ acissuerserialASN1.data = &asn1string[taglist[itag].start+taglist[itag].headerlength];
+
+ acissuerserial = i2s_ASN1_INTEGER(NULL, &acissuerserialASN1);
+/*
+ p = &asn1string[taglist[itag].start+taglist[itag].headerlength];
+
+ if (taglist[itag].length == 2)
+ acissuerserial = p[1] + p[0] * 0x100;
+ else if (taglist[itag].length == 3)
+ acissuerserial = p[2] + p[1] * 0x100 + p[0] * 0x10000;
+ else if (taglist[itag].length == 4)
+ acissuerserial = p[3] + p[2] * 0x100 + p[1] * 0x10000 +
+ p[0] * 0x1000000;
+*/
+ }
+
+ if (strcmp(acissuerserial, ucserial) != 0) continue;
+
+ if (GRSTx509VerifyVomsSig(&time1_time, &time2_time,
+ asn1string, taglist, lasttag, vomsdir, acnumber)
+ != GRST_RET_OK) continue;
+
+ snprintf(time1_coords, sizeof(time1_coords), GRST_ASN1_COORDS_TIME1, acnumber);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, time1_coords);
+ actime1 = GRSTasn1TimeToTimeT(&asn1string[taglist[itag].start+
+ taglist[itag].headerlength],
+ taglist[itag].length);
+ if (actime1 > time1_time) time1_time = actime1;
+
+ snprintf(time2_coords, sizeof(time2_coords), GRST_ASN1_COORDS_TIME2, acnumber);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, time2_coords);
+ actime2 = GRSTasn1TimeToTimeT(&asn1string[taglist[itag].start+
+ taglist[itag].headerlength],
+ taglist[itag].length);
+ if (actime2 < time2_time) time2_time = actime2;
+
+ time(&time_now);
+ if ((time1_time > time_now + 300) || (time2_time < time_now))
+ continue; /* expiration isnt invalidity ...? */
+
+ for (i=1; ; ++i)
+ {
+ snprintf(fqan_coords, sizeof(fqan_coords), GRST_ASN1_COORDS_FQAN, acnumber, i);
+ itag = GRSTasn1SearchTaglist(taglist, lasttag, fqan_coords);
+
+ if (itag > -1)
+ {
+ if (*lastcred < maxcreds - 1)
+ {
+ ++(*lastcred);
+ snprintf(&creds[*lastcred * (credlen + 1)], credlen+1,
+ "VOMS %010lu %010lu 0 %.*s",
+ time1_time, time2_time,
+ taglist[itag].length,
+ &asn1string[taglist[itag].start+
+ taglist[itag].headerlength]);
+ }
+ }
+ else break;
+ }
+ }
+
+ return GRST_RET_OK;
+}
+
+/// Get the VOMS attributes in the extensions to the given cert stack
+int GRSTx509GetVomsCreds(int *lastcred, int maxcreds, size_t credlen,
+ char *creds, X509 *usercert, STACK_OF(X509) *certstack,
+ char *vomsdir)
+///
+/// Puts any VOMS credentials found into the Compact Creds string array
+/// starting at *creds. Always returns GRST_RET_OK.
+{
+ int i, j;
+ char s[80], *ucserial;
+ unsigned char *ucuser, *ucissuer;
+ X509_EXTENSION *ex;
+ X509 *cert;
+ time_t time1_time = 0, time2_time = 0, uctime1_time, uctime2_time;
+
+ uctime1_time =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert)),0);
+ uctime2_time =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert)),0);
+ ucuser =
+ X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0);
+ ucissuer =
+ X509_NAME_oneline(X509_get_issuer_name(usercert), NULL, 0);
+ ucserial = i2s_ASN1_INTEGER(NULL, X509_get_serialNumber(usercert));
+
+ for (j=sk_X509_num(certstack)-1; j >= 0; --j)
+ {
+ cert = sk_X509_value(certstack, j);
+
+ time1_time =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0);
+ uctime1_time = (time1_time > uctime1_time) ? time1_time:uctime1_time;
+
+ time2_time =
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0);
+ uctime2_time = (time2_time < uctime2_time) ? time2_time:uctime2_time;
+
+ for (i=0; i < X509_get_ext_count(cert); ++i)
+ {
+ ex = X509_get_ext(cert, i);
+ OBJ_obj2txt(s, sizeof(s), X509_EXTENSION_get_object(ex), 1);
+
+ if (strcmp(s, GRST_VOMS_OID) == 0) /* a VOMS extension */
+ {
+ GRSTx509ParseVomsExt(lastcred, maxcreds, credlen, creds,
+ uctime1_time, uctime2_time,
+ ex, ucuser, ucissuer, ucserial,
+ vomsdir);
+ }
+ }
+ }
+
+ return GRST_RET_OK;
+}
+
+/// Turn a Compact Cred line into a GRSTgaclCred object
+GRSTgaclCred *GRSTx509CompactToCred(char *grst_cred)
+///
+/// Returns pointer to created GRSTgaclCred or NULL or failure.
+{
+ int delegation;
+ char *p, *encoded;
+ time_t now, notbefore, notafter;
+ GRSTgaclCred *cred = NULL;
+
+ time(&now);
+
+ if (grst_cred == NULL) return NULL; /* just in case */
+
+ if (strncmp(grst_cred, "X509USER ", 9) == 0)
+ {
+ if ((sscanf(grst_cred, "X509USER %lu %lu %d",
+ ¬before, ¬after, &delegation) == 3)
+ && (now >= notbefore)
+ && (now <= notafter)
+ && (p = index(grst_cred, ' '))
+ && (p = index(++p, ' '))
+ && (p = index(++p, ' '))
+ && (p = index(++p, ' ')))
+ {
+ encoded = GRSThttpUrlMildencode(&p[1]);
+ cred = GRSTgaclCredCreate("dn:", encoded);
+ free(encoded);
+ GRSTgaclCredSetDelegation(cred, delegation);
+ }
+
+ return cred;
+ }
+
+ if (strncmp(grst_cred, "VOMS ", 5) == 0)
+ {
+ if ((sscanf(grst_cred, "VOMS %lu %lu %d",
+ ¬before, ¬after, &delegation) == 3)
+ && (now >= notbefore)
+ && (now <= notafter)
+ && (p = index(grst_cred, ' '))
+ && (p = index(++p, ' '))
+ && (p = index(++p, ' '))
+ && (p = index(++p, ' ')))
+ {
+ /* include /VO/group/subgroup/Role=role/Capability=cap */
+
+ if (p[1] != '/') return NULL; /* must begin with / */
+
+ encoded = GRSThttpUrlMildencode(&p[1]);
+ cred = GRSTgaclCredCreate("fqan:", encoded);
+ free(encoded);
+ GRSTgaclCredSetDelegation(cred, delegation);
+ }
+
+ return cred;
+ }
+
+ return NULL; /* dont recognise this credential type */
+}
+
+/// Get the credentials in an X509 cert/GSI proxy, including any VOMS
+int GRSTx509CompactCreds(int *lastcred, int maxcreds, size_t credlen,
+ char *creds, STACK_OF(X509) *certstack, char *vomsdir,
+ X509 *peercert)
+///
+/// Credentials are placed in Compact Creds string array at *creds.
+///
+/// Function returns GRST_RET_OK on success, or GRST_RET_FAILED if
+/// some inconsistency found in certificate.
+{
+ int i, delegation = 0;
+ char credtemp[credlen+1];
+ X509 *cert, *usercert = NULL, *gsiproxycert = NULL;
+
+ *lastcred = -1;
+
+ for (i = sk_X509_num(certstack) - 1; i >= 0; --i)
+ {
+ cert = sk_X509_value(certstack, i);
+
+ if (usercert != NULL)
+ { /* found a (GSI proxy) cert after the user cert */
+ gsiproxycert = cert;
+ ++delegation;
+ }
+
+ if ((usercert == NULL) &&
+ (i < sk_X509_num(certstack) - 1) &&
+ (GRSTx509IsCA(cert) != GRST_RET_OK)) usercert = cert;
+ /* found the 1st non-CA cert */
+ }
+
+ if (peercert != NULL)
+ {
+ if (usercert != NULL) /* found a (GSI proxy) cert after user cert */
+ {
+ gsiproxycert = peercert;
+ ++delegation;
+ }
+
+ if ((usercert == NULL) &&
+ (GRSTx509IsCA(peercert) != GRST_RET_OK)) usercert = peercert;
+ /* found the 1st non-CA cert */
+ }
+
+ if ((usercert == NULL) /* if no usercert ("EEC"), we're not interested */
+ ||
+ (snprintf(credtemp, credlen+1, "X509USER %010lu %010lu %d %s",
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(usercert)),0),
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(usercert)),0),
+ delegation,
+ X509_NAME_oneline(X509_get_subject_name(usercert), NULL, 0)) >= credlen+1)
+ ||
+ (*lastcred >= maxcreds-1))
+ {
+ *lastcred = -1; /* just in case the caller looks at it */
+ return GRST_RET_FAILED; /* tell caller that things didn't work out */
+ }
+
+ ++(*lastcred);
+ strcpy(&creds[*lastcred * (credlen + 1)], credtemp);
+
+ if ((gsiproxycert != NULL)
+ &&
+ (snprintf(credtemp, credlen+1, "GSIPROXY %010lu %010lu %d %s",
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(gsiproxycert)),0),
+ GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(gsiproxycert)),0),
+ delegation,
+ X509_NAME_oneline(X509_get_subject_name(gsiproxycert), NULL, 0)) < credlen+1)
+ &&
+ (*lastcred < maxcreds-1))
+ {
+ ++(*lastcred);
+ strcpy(&creds[*lastcred * (credlen + 1)], credtemp);
+
+ GRSTx509GetVomsCreds(lastcred, maxcreds, credlen, creds,
+ usercert, certstack, vomsdir);
+
+ }
+
+ return GRST_RET_OK;
+}
+
+/// Find proxy file name of the current user
+char *GRSTx509FindProxyFileName(void)
+///
+/// Return a string with the proxy file name or NULL if not present.
+/// This function does not check if the proxy has expired.
+{
+ char *p;
+
+ p = getenv("X509_USER_PROXY");
+
+ if (p != NULL) return strdup(p);
+
+ p = malloc(sizeof("/tmp/x509up_uXYYYXXXYYY"));
+
+ sprintf(p, "/tmp/x509up_u%d", getuid());
+
+ return p;
+}
+
+static void mpcerror(FILE *debugfp, char *msg)
+{
+ if (debugfp != NULL)
+ {
+ fputs(msg, debugfp);
+ ERR_print_errors_fp(debugfp);
+ }
+}
+
+/// Make a GSI Proxy chain from a request, certificate and private key
+int GRSTx509MakeProxyCert(char **proxychain, FILE *debugfp,
+ char *reqtxt, char *cert, char *key, int minutes)
+///
+/// The proxy chain is returned in *proxychain. If debugfp is non-NULL,
+/// errors are output to that file pointer. The proxy will expired in
+/// the given number of minutes starting from the current time.
+{
+ char *ptr = NULL, *certchain = NULL;
+ static unsigned char pci_str[] = { 0x30, 0x0c, 0x30, 0x0a, 0x06, 0x08,
+ 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x15, 0x01, 0 },
+ kyu_str[] = { 0x03, 0x02, 0x03,
+ X509v3_KU_DIGITAL_SIGNATURE |
+ X509v3_KU_KEY_ENCIPHERMENT |
+ X509v3_KU_KEY_AGREEMENT,
+ 0 };
+ int i = 0, ncerts = 0, any_rfc_proxies = 0;
+ long ptrlen = 0;
+ EVP_PKEY *pkey = NULL, *signer_pkey = NULL;
+ X509 *certs[GRST_MAX_CHAIN_LEN];
+ X509_REQ *req = NULL;
+ ASN1_OBJECT *pci_obj = NULL, *kyu_obj = NULL;
+ ASN1_OCTET_STRING *pci_oct = NULL, *kyu_oct = NULL;
+ FILE *fp = NULL;
+ BIO *reqmem = NULL, *certmem = NULL;
+ canl_ctx ctx = NULL;
+ int retval = 1, ret = 0;
+ canl_cred proxy_cert = NULL, signer = NULL;
+
+ ctx = canl_create_ctx();
+ if (ctx == NULL) {
+ fprintf(debugfp, "GRSTx509MakeProxyCert(): Failed to create"
+ " caNl library context\n");
+ return 1;
+ }
+
+ /* read in the request */
+ reqmem = BIO_new(BIO_s_mem());
+ BIO_puts(reqmem, reqtxt);
+
+ if (!(req = PEM_read_bio_X509_REQ(reqmem, NULL, NULL, NULL))) {
+ fprintf(debugfp, "GRSTx509MakeProxyCert(): error reading"
+ " request from BIO memory\n");
+ goto end;
+ }
+ BIO_free(reqmem);
+ reqmem = NULL;
+
+ /* load req into canl cred. context */
+ ret = canl_cred_new(ctx, &proxy_cert);
+ if (ret) {
+ fprintf(debugfp, "GRSTx509MakeProxyCert(): caNl cred. context"
+ " cannot be created: %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+ ret = canl_cred_load_req(ctx, proxy_cert, req);
+ if (ret) {
+ fprintf(stderr, "GRSTx509MakeProxyCert(): Failed to load certificate "
+ "request container: %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+
+ /*TODO MP Should proxy sognature verification be in caN???*/
+ /* verify signature on the request */
+ if (!(pkey = X509_REQ_get_pubkey(req))) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error getting public"
+ " key from request\n");
+ }
+
+ if (X509_REQ_verify(req, pkey) != 1) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error verifying signature"
+ " on certificate\n");
+ goto end;
+ }
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ X509_REQ_free(req);
+ req = NULL;
+
+ /* read in the signing certificate */
+ if (!(fp = fopen(cert, "r"))) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error opening"
+ " signing certificate file\n");
+ goto end;
+ }
+ /* load req signer cert into caNl cred. context*/
+ ret = canl_cred_new(ctx, &signer);
+ if (ret) {
+ fprintf(debugfp, "GRSTx509MakeProxyCert(): caNl cred. context"
+ " cannot be created: %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+
+ for (ncerts = 1; ncerts < GRST_MAX_CHAIN_LEN; ++ncerts)
+ if ((certs[ncerts] = PEM_read_X509(fp, NULL, NULL, NULL)) == NULL)
+ break;
+ /* zeroth cert will be new proxy cert */
+ if (ncerts == 1) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error reading"
+ " signing certificate file\n");
+ goto end;
+ }
+ fclose(fp);
+ fp = NULL;
+
+ /* read in the signer certificate*/
+ ret = canl_cred_load_cert_file(ctx, signer, cert);
+ if (ret){
+ fprintf(stderr, "[DELEGATION] Cannot load signer's certificate"
+ ": %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+
+
+ /* read in the signer private key */
+ if (!(fp = fopen(key, "r"))) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error reading"
+ " signing private key file\n");
+ goto end;
+ }
+
+ if (!(signer_pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL))) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error reading "
+ "signing private key in file\n");
+ goto end;
+ }
+ fclose(fp);
+ fp = NULL;
+ canl_cred_load_priv_key(ctx, signer, signer_pkey);
+ EVP_PKEY_free(signer_pkey);
+ signer_pkey = NULL;
+
+ /*TODO MP Compare with VOMS in caNl (orig gridsite proxyver = 2L)*/
+ /* set version number for the certificate (X509v3) and the serial number
+ We now use 2 = v3 for the GSI proxy, rather than the old Globus
+ behaviour of 3 = v4. See Savannah Bug #53721 */
+
+ /* TODO MP what about serial numbers? Should caNl provide API function for
+ * setting custom serial number?*/
+ /* ASN1_INTEGER_set(X509_get_serialNumber(certs[0]), (long) time(NULL));*/
+
+ /* TODO MP use some default timeout?*/
+ if (minutes >= 0) {
+ ret = canl_cred_set_lifetime(ctx, proxy_cert, 60 * minutes);
+ if (ret)
+ fprintf(debugfp, "[DELEGATION] Failed set new cert lifetime"
+ ": %s\n", canl_get_error_message(ctx));
+ }
+
+ /* go through chain making sure this proxy is not longer lived */
+
+ pci_obj = OBJ_txt2obj(GRST_PROXYCERTINFO_OID, 0);
+
+ /* TODO MP is this necessary? caNl test if new proxy timeout
+ * is longer than signer cert proxy timeout */
+ for (i=1; i < ncerts; ++i)
+ if (X509_get_ext_by_OBJ(certs[i], pci_obj, -1) > 0)
+ any_rfc_proxies = 1;
+
+ /* if any earlier proxies are RFC 3820, then new proxy must be
+ an RFC 3820 proxy too with the required extensions */
+ if (any_rfc_proxies) {
+ X509_EXTENSION *pci_ex = NULL, *kyu_ex = NULL;
+ /* key usage */
+ kyu_obj = OBJ_txt2obj(GRST_KEYUSAGE_OID, 0);
+ kyu_ex = X509_EXTENSION_new();
+
+ X509_EXTENSION_set_object(kyu_ex, kyu_obj);
+ ASN1_OBJECT_free(kyu_obj);
+ X509_EXTENSION_set_critical(kyu_ex, 1);
+
+ kyu_oct = ASN1_OCTET_STRING_new();
+ ASN1_OCTET_STRING_set(kyu_oct, kyu_str, strlen(kyu_str));
+ X509_EXTENSION_set_data(kyu_ex, kyu_oct);
+ ASN1_OCTET_STRING_free(kyu_oct);
+
+ canl_cred_set_extension(ctx, proxy_cert, kyu_ex);
+ X509_EXTENSION_free(kyu_ex);
+
+ /* proxy certificate info */
+ pci_ex = X509_EXTENSION_new();
+ X509_EXTENSION_set_object(pci_ex, pci_obj);
+ X509_EXTENSION_set_critical(pci_ex, 1);
+
+ pci_oct = ASN1_OCTET_STRING_new();
+ ASN1_OCTET_STRING_set(pci_oct, pci_str, strlen(pci_str));
+ X509_EXTENSION_set_data(pci_ex, pci_oct);
+ ASN1_OCTET_STRING_free(pci_oct);
+
+ canl_cred_set_extension(ctx, proxy_cert, kyu_ex);
+ X509_EXTENSION_free(pci_ex);
+ }
+ ASN1_OBJECT_free(pci_obj);
+ pci_obj = NULL;
+
+ /* Sign the proxy */
+ ret = canl_cred_sign_proxy(ctx, signer, proxy_cert);
+ if (ret){
+ fprintf(stderr, "[DELEGATION] Cannot sign new proxy"
+ ": %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+
+ ret = canl_cred_save_cert(ctx, proxy_cert, &certs[0]);
+ if (ret) {
+ fprintf(stderr, "GRSTx509MakeProxyCert(): Cannot save new cert file"
+ ": %s\n", canl_get_error_message(ctx));
+ goto end;
+ }
+ canl_free_ctx(ctx);
+ ctx = NULL;
+
+ /* store the completed certificate chain */
+ certchain = strdup("");
+ for (i=0; i < ncerts; ++i) {
+ certmem = BIO_new(BIO_s_mem());
+
+ if (PEM_write_bio_X509(certmem, certs[i]) != 1) {
+ mpcerror(debugfp, "GRSTx509MakeProxyCert(): error writing"
+ " certificate to memory BIO\n");
+ goto end;
+ }
+ ptrlen = BIO_get_mem_data(certmem, &ptr);
+ certchain = realloc(certchain, strlen(certchain) + ptrlen + 1);
+ strncat(certchain, ptr, ptrlen);
+
+ BIO_free(certmem);
+ certmem = NULL;
+ X509_free(certs[i]);
+ certs[i] = NULL;
+ }
+ ncerts = 0;
+ *proxychain = certchain;
+ retval = GRST_RET_OK;
+
+end:
+ if (reqmem)
+ BIO_free(reqmem);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (signer_pkey)
+ EVP_PKEY_free(signer_pkey);
+ if (req)
+ X509_REQ_free(req);
+ if (pci_obj)
+ ASN1_OBJECT_free(pci_obj);
+ if (fp) {
+ fclose(fp);
+ fp = NULL;
+ }
+ if (certmem)
+ BIO_free(certmem);
+ if (ctx)
+ canl_free_ctx(ctx);
+ for (i=0; i < ncerts; ++i) {
+ if (certs[i])
+ X509_free(certs[i]);
+ }
+ if (proxy_cert)
+ canl_cred_free(ctx, proxy_cert);
+ if (signer)
+ canl_cred_free(ctx, signer);
+ return retval;
+}
+
+/// Find a proxy file in the proxy cache
+char *GRSTx509CachedProxyFind(char *proxydir, char *delegation_id,
+ char *user_dn)
+///
+/// Returns the full path and file name of proxy file associated
+/// with given delegation ID and user DN.
+///
+/// Return a pointer to a malloc'd string with the full path of the
+/// proxy file corresponding to the given delegation_id, or NULL
+/// if not found.
+{
+ char *user_dn_enc, *proxyfile;
+ struct stat statbuf;
+
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ asprintf(&proxyfile, "%s/%s/%s/userproxy.pem",
+ proxydir, user_dn_enc, delegation_id);
+
+ free(user_dn_enc);
+
+ if ((stat(proxyfile, &statbuf) != 0) || !S_ISREG(statbuf.st_mode))
+ {
+ free(proxyfile);
+ return NULL;
+ }
+
+ return proxyfile;
+}
+
+/// Find a temporary proxy private key file in the proxy cache
+char *GRSTx509CachedProxyKeyFind(char *proxydir, char *delegation_id,
+ char *user_dn)
+///
+/// Returns the full path and file name of the private key file associated
+/// with given delegation ID and user DN.
+///
+/// Return a pointer to a malloc'd string with the full path of the
+/// private proxy key corresponding to the given delegation_id, or NULL
+/// if not found.
+{
+ char *user_dn_enc, *prvkeyfile;
+ struct stat statbuf;
+
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ asprintf(&prvkeyfile, "%s/cache/%s/%s/userkey.pem",
+ proxydir, user_dn_enc, delegation_id);
+
+ free(user_dn_enc);
+
+ if ((stat(prvkeyfile, &statbuf) != 0) || !S_ISREG(statbuf.st_mode))
+ {
+ free(prvkeyfile);
+ return NULL;
+ }
+
+ return prvkeyfile;
+}
+
+static void mkdir_printf(mode_t mode, char *fmt, ...)
+{
+ int ret;
+ char *path;
+ va_list ap;
+
+ va_start(ap, fmt);
+ vasprintf(&path, fmt, ap);
+ va_end(ap);
+
+ ret = mkdir(path, mode);
+
+ free(path);
+}
+
+/// Create a X.509 request for a GSI proxy and its private key
+int GRSTx509CreateProxyRequest(char **reqtxt, char **keytxt, char *ocspurl)
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise. Request string
+/// and private key are PEM encoded strings
+{
+ char *ptr = 0;
+ size_t ptrlen = 0;
+ EVP_PKEY *pkey = NULL;
+ X509_REQ *req = NULL;
+ BIO *reqmem = NULL, *keymem = NULL;
+ canl_cred proxy_bob = NULL;
+ int retval = 0;
+ canl_ctx c_ctx = NULL;
+ int ret = 0;
+
+ /*Make new canl_ctx, TODO MP how to initialize it only once?*/
+ c_ctx = canl_create_ctx();
+ if (c_ctx == NULL) {
+ return 10; /* TODO MP we can use caNl error codes now */
+ }
+
+ ret = canl_cred_new(c_ctx, &proxy_bob);
+ if (ret){
+ retval = 11;
+ goto end;
+ }
+
+ /*use caNl to generate a X509 request*/
+ ret = canl_cred_new_req(c_ctx, proxy_bob, GRST_KEYSIZE);
+ if (ret) {
+ retval = 12;
+ goto end;
+ }
+
+ ret = canl_cred_save_req(c_ctx, proxy_bob, &req);
+ if (ret) {
+ retval = 13;
+ goto end;
+ }
+
+ /*Convert request into a string*/
+ reqmem = BIO_new(BIO_s_mem());
+ PEM_write_bio_X509_REQ(reqmem, req);
+ ptrlen = BIO_get_mem_data(reqmem, &ptr);
+
+ *reqtxt = malloc(ptrlen + 1);
+ memcpy(*reqtxt, ptr, ptrlen);
+ (*reqtxt)[ptrlen] = '\0';
+
+ /* Put keypair in a PEM string */
+ ret = canl_cred_save_priv_key(c_ctx, proxy_bob, &pkey);
+ if (ret){
+ retval = 15;
+ goto end;
+ }
+ keymem = BIO_new(BIO_s_mem());
+ if (!PEM_write_bio_PrivateKey(keymem, pkey, NULL, NULL, 0, NULL, NULL))
+ {
+ BIO_free(keymem);
+ return 3;
+ }
+
+ ptrlen = BIO_get_mem_data(keymem, &ptr);
+ *keytxt = malloc(ptrlen + 1);
+ memcpy(*keytxt, ptr, ptrlen);
+ (*keytxt)[ptrlen] = '\0';
+
+end:
+ if (proxy_bob)
+ canl_cred_free(c_ctx, proxy_bob);
+ if (pkey)
+ EVP_PKEY_free(pkey);
+ if (reqmem)
+ BIO_free(reqmem);
+ if (keymem)
+ BIO_free(keymem);
+ if (req)
+ X509_REQ_free(req);
+ if (c_ctx)
+ canl_free_ctx(c_ctx);
+
+ return retval;
+}
+
+/// Make and store a X.509 request for a GSI proxy
+int GRSTx509MakeProxyRequest(char **reqtxt, char *proxydir,
+ char *delegation_id, char *user_dn)
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise. Request string
+/// is PEM encoded, and the key is stored in the temporary cache under
+/// proxydir
+{
+ char *prvkeyfile = NULL, *ptr = NULL, *user_dn_enc = NULL;
+ size_t ptrlen = 0;
+ FILE *fp = NULL;
+ EVP_PKEY *pkey = NULL;
+ BIO *reqmem = NULL;
+ canl_ctx c_ctx = NULL;
+ canl_cred proxy_bob = NULL;
+ X509_REQ *req = NULL;
+ int retval = GRST_RET_OK;
+ int ret = 0;
+
+ if (strcmp(user_dn, "cache") == 0)
+ return GRST_RET_FAILED;
+
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ /* create directories if necessary */
+
+ mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR,
+ "%s/cache", proxydir);
+ mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR,
+ "%s/cache/%s", proxydir, user_dn_enc);
+ mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR,
+ "%s/cache/%s/%s", proxydir, user_dn_enc, delegation_id);
+
+ /* make the new proxy private key */
+
+ asprintf(&prvkeyfile, "%s/cache/%s/%s/userkey.pem",
+ proxydir, user_dn_enc, delegation_id);
+
+ if (prvkeyfile == NULL){
+ free(user_dn_enc);
+ return GRST_RET_FAILED;
+ }
+
+ /*Make new canl_ctx, TODO MP how to initialize it only once?*/
+ c_ctx = canl_create_ctx();
+ if (c_ctx == NULL) {
+ free(prvkeyfile);
+ return 10; /* TODO MP we can use caNl error codes now */
+ }
+
+ ret = canl_cred_new(c_ctx, &proxy_bob);
+ if (ret){
+ retval = 11;
+ goto end;
+ }
+
+ /*use caNl to generate X509 request*/
+ ret = canl_cred_new_req(c_ctx, proxy_bob, GRST_KEYSIZE);
+ if (ret) {
+ retval = 12;
+ goto end;
+ }
+
+ ret = canl_cred_save_req(c_ctx, proxy_bob, &req);
+ if (ret) {
+ retval = 13;
+ goto end;
+ }
+
+ if ((fp = fopen(prvkeyfile, "w")) == NULL)
+ {
+ retval = 14;
+ goto end;
+ }
+
+ /*Save private key in cache*/
+ chmod(prvkeyfile, S_IRUSR | S_IWUSR);
+ free(prvkeyfile);
+ prvkeyfile = NULL;
+ free(user_dn_enc);
+ user_dn_enc = NULL;
+
+ ret = canl_cred_save_priv_key(c_ctx, proxy_bob, &pkey);
+ if (ret) {
+ retval = 15;
+ goto end;
+ }
+
+ if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) {
+ retval = 3;
+ goto end;
+ }
+
+ if (fclose(fp) != 0){
+ retval = 4;
+ goto end;
+ }
+
+ /* TODO MP ask. "Dummy" vs "proxy" in sslutils.c from Voms
+ ent=X509_NAME_ENTRY_create_by_NID(NULL, OBJ_txt2nid("organizationName"),
+ MBSTRING_ASC, "Dummy", -1);
+ TODO MD5 vs SHA1 defaults.
+ */
+
+ /*Convert request into string*/
+ reqmem = BIO_new(BIO_s_mem());
+ PEM_write_bio_X509_REQ(reqmem, req);
+ ptrlen = BIO_get_mem_data(reqmem, &ptr);
+
+ *reqtxt = malloc(ptrlen + 1);
+ memcpy(*reqtxt, ptr, ptrlen);
+ (*reqtxt)[ptrlen] = '\0';
+end:
+ if (proxy_bob)
+ canl_cred_free(c_ctx, proxy_bob);
+ if (reqmem)
+ BIO_free(reqmem);
+ if (req)
+ X509_REQ_free(req);
+ if (prvkeyfile)
+ free(prvkeyfile);
+ if (user_dn_enc)
+ free(user_dn_enc);
+ if (c_ctx)
+ canl_free_ctx(c_ctx);
+
+ return retval;
+}
+
+/// Destroy stored GSI proxy files
+int GRSTx509ProxyDestroy(char *proxydir, char *delegation_id, char *user_dn)
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise.
+/// (Including GRST_RET_NO_SUCH_FILE if the private key or cert chain
+/// were not found.)
+{
+ int ret = GRST_RET_OK;
+ char *filename, *user_dn_enc;
+
+ if (strcmp(user_dn, "cache") == 0) return GRST_RET_FAILED;
+
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ /* proxy file */
+
+ asprintf(&filename, "%s/%s/%s/userproxy.pem",
+ proxydir, user_dn_enc, delegation_id);
+
+ if (filename == NULL)
+ {
+ free(user_dn_enc);
+ return GRST_RET_FAILED;
+ }
+
+ if (unlink(filename) != 0) ret = GRST_RET_NO_SUCH_FILE;
+ free(filename);
+
+ /* voms file */
+
+ asprintf(&filename, "%s/%s/%s/voms.attributes",
+ proxydir, user_dn_enc, delegation_id);
+
+ if (filename == NULL)
+ {
+ free(user_dn_enc);
+ return GRST_RET_FAILED;
+ }
+
+ unlink(filename);
+ free(filename);
+
+ return ret;
+}
+
+/// Get start and finish validity times of stored GSI proxy file
+int GRSTx509ProxyGetTimes(char *proxydir, char *delegation_id, char *user_dn,
+ time_t *start, time_t *finish)
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise.
+/// (Including GRST_RET_NO_SUCH_FILE if the cert chain was not found.)
+{
+ char *docroot, *filename, *user_dn_enc;
+ FILE *fp;
+ X509 *cert;
+
+ if (strcmp(user_dn, "cache") == 0) return GRST_RET_FAILED;
+
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ asprintf(&filename, "%s/%s/%s/userproxy.pem",
+ proxydir, user_dn_enc, delegation_id);
+
+ free(user_dn_enc);
+
+ if (filename == NULL) return GRST_RET_FAILED;
+
+ fp = fopen(filename, "r");
+ free(filename);
+
+ if (fp == NULL) return GRST_RET_NO_SUCH_FILE;
+
+ cert = PEM_read_X509(fp, NULL, NULL, NULL); /* first cert is X.509 PC */
+
+ fclose(fp);
+
+ *start = GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notBefore(cert)),0);
+ *finish = GRSTasn1TimeToTimeT(ASN1_STRING_data(X509_get_notAfter(cert)),0);
+
+ X509_free(cert);
+
+ return GRST_RET_OK;
+}
+
+/// Create a stack of X509 certificate from a PEM-encoded string
+int GRSTx509StringToChain(STACK_OF(X509) **certstack, char *certstring)
+///
+/// Creates a dynamically allocated stack of X509 certificate objects
+/// by walking through the PEM-encoded X509 certificates.
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise.
+{
+ STACK_OF(X509_INFO) *sk=NULL;
+ BIO *certbio;
+ X509_INFO *xi;
+
+ *certstack = sk_X509_new_null();
+ if (*certstack == NULL) return GRST_RET_FAILED;
+
+ certbio = BIO_new_mem_buf(certstring, -1);
+
+ if (!(sk=PEM_X509_INFO_read_bio(certbio, NULL, NULL, NULL)))
+ {
+ BIO_free(certbio);
+ sk_X509_INFO_free(sk);
+ sk_X509_free(*certstack);
+ return GRST_RET_FAILED;
+ }
+
+ while (sk_X509_INFO_num(sk))
+ {
+ xi=sk_X509_INFO_shift(sk);
+ if (xi->x509 != NULL)
+ {
+ sk_X509_push(*certstack, xi->x509);
+ xi->x509=NULL;
+ }
+ X509_INFO_free(xi);
+ }
+
+ if (!sk_X509_num(*certstack))
+ {
+ BIO_free(certbio);
+ sk_X509_INFO_free(sk);
+ sk_X509_free(*certstack);
+ return GRST_RET_FAILED;
+ }
+
+ BIO_free(certbio);
+ sk_X509_INFO_free(sk);
+
+ return GRST_RET_OK;
+}
+
+/// Returns a Delegation ID based on hash of GRST_CRED_0, ...
+char *GRSTx509MakeDelegationID(void)
+///
+/// Returns a malloc'd string with Delegation ID made by SHA1-hashing the
+/// values of the compact credentials exported by mod_gridsite
+{
+ unsigned char hash_delegation_id[EVP_MAX_MD_SIZE];
+ int i, delegation_id_len;
+ char cred_name[14], *cred_value, *delegation_id;
+ const EVP_MD *m;
+ EVP_MD_CTX ctx;
+
+ OpenSSL_add_all_digests();
+
+ m = EVP_sha1();
+ if (m == NULL) return NULL;
+
+ EVP_DigestInit(&ctx, m);
+
+ for (i=0; i <= 999; ++i)
+ {
+ snprintf(cred_name, sizeof(cred_name), "GRST_CRED_%d", i);
+ if ((cred_value = getenv(cred_name)) == NULL) break;
+
+ EVP_DigestUpdate(&ctx, cred_value, strlen(cred_value));
+ }
+
+ EVP_DigestFinal(&ctx, hash_delegation_id, &delegation_id_len);
+
+ delegation_id = malloc(17);
+
+ for (i=0; i <=7; ++i)
+ sprintf(&delegation_id[i*2], "%02x", hash_delegation_id[i]);
+
+ delegation_id[16] = '\0';
+
+ return delegation_id;
+}
+
+#if 0
+/// Return the short file name for the given delegation_id and user_dn
+char *GRSTx509MakeProxyFileName(char *delegation_id,
+ STACK_OF(X509) *certstack)
+///
+/// Returns a malloc'd string with the short file name (no paths) that
+/// derived from the hashed delegation_id and user_dn
+///
+/// File name is SHA1_HASH(DelegationID)+"-"+SHA1_HASH(DN) where DN
+/// is DER encoded version of user_dn with any trailing CN=proxy removed
+/// Hashes are the most significant 8 bytes, in lowercase hexadecimal.
+{
+ int i, depth, prevIsCA = 1, IsCA, hash_name_len, delegation_id_len,
+ der_name_len;
+ unsigned char *der_name, *buf, hash_name[EVP_MAX_MD_SIZE],
+ hash_delegation_id[EVP_MAX_MD_SIZE],
+ filename[34];
+ X509_NAME *subject_name;
+ X509 *cert;
+ const EVP_MD *m;
+ EVP_MD_CTX ctx;
+
+ depth = sk_X509_num(certstack);
+
+ for (i=depth-1; i >= 0; --i)
+ /* loop through the proxy chain starting at CA end */
+ {
+ if (cert = sk_X509_value(certstack, i))
+ {
+ IsCA = (GRSTx509IsCA(cert) == GRST_RET_OK);
+
+ if (prevIsCA && !IsCA) /* the full certificate of the user */
+ {
+ break;
+ }
+ }
+ }
+
+ if (i < 0) return NULL; /* not found: something wrong with the chain */
+
+ if ((subject_name = X509_get_subject_name(cert)) == NULL) return NULL;
+
+ der_name_len = i2d_X509_NAME(X509_get_subject_name(cert), NULL);
+ if (der_name_len == 0) return NULL;
+
+ buf = OPENSSL_malloc(der_name_len);
+ der_name = buf;
+
+
+ if (!i2d_X509_NAME(X509_get_subject_name(cert), &der_name))
+ {
+ OPENSSL_free(der_name);
+ return NULL;
+ }
+
+ OpenSSL_add_all_digests();
+
+ m = EVP_sha1();
+ if (m == NULL)
+ {
+ OPENSSL_free(der_name);
+ return NULL;
+ }
+
+
+ EVP_DigestInit(&ctx, m);
+ EVP_DigestUpdate(&ctx, delegation_id, strlen(delegation_id));
+ EVP_DigestFinal(&ctx, hash_delegation_id, &delegation_id_len);
+
+ /* lots of nasty hard coded numbers:
+ "8bytes/16chars delegation ID" + "-" + "8bytes/16chars DN" */
+
+ for (i=0; i <=7; ++i)
+ sprintf(&filename[i*2], "%02x", hash_delegation_id[i]);
+
+ filename[16] = '-';
+
+ EVP_DigestInit(&ctx, m);
+ EVP_DigestUpdate(&ctx, buf, der_name_len);
+ EVP_DigestFinal(&ctx, hash_name, &hash_name_len);
+
+ for (i=0; i <=7; ++i)
+ sprintf(&filename[17 + i*2], "%02x", hash_name[i]);
+
+ return strdup(filename);
+}
+#endif
+
+/// Store a GSI proxy chain in the proxy cache, along with the private key
+int GRSTx509CacheProxy(char *proxydir, char *delegation_id,
+ char *user_dn, char *proxychain)
+///
+/// Returns GRST_RET_OK on success, non-zero otherwise. The existing
+/// private key with the same delegation ID and user DN is moved out of
+/// the temporary cache.
+{
+ int ret = 0;
+ int retval = GRST_RET_FAILED;
+ char *user_dn_enc, *prvkeyfile, *proxyfile;
+ STACK_OF(X509) *certstack;
+ canl_ctx c_ctx = NULL;
+ canl_cred proxy_bob = NULL;
+
+ if (strcmp(user_dn, "cache") == 0)
+ return GRST_RET_FAILED;
+
+
+ /* get the X509 stack */
+ if (GRSTx509StringToChain(&certstack, proxychain) != GRST_RET_OK){
+ return GRST_RET_FAILED;/* TODO MP we can use caNl error codes now */
+ }
+
+ /*Make new canl_ctx, TODO MP how to initialize it only once?*/
+ c_ctx = canl_create_ctx();
+ if (c_ctx == NULL)
+ goto end;
+
+ ret = canl_cred_new(c_ctx, &proxy_bob);
+ if (ret){
+ goto end; /* TODO MP we can use caNl error codes now */
+ }
+
+ /*Use caNl to load certstack into proxy_bob*/
+ ret = canl_cred_load_chain(c_ctx, proxy_bob, certstack);
+ if (ret){
+ goto end;
+ }
+
+ /* create directories if necessary, and set proxy filename */
+ user_dn_enc = GRSThttpUrlEncode(user_dn);
+
+ mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR,
+ "%s/%s", proxydir, user_dn_enc);
+ mkdir_printf(S_IRUSR | S_IWUSR | S_IXUSR,
+ "%s/%s/%s", proxydir, user_dn_enc, delegation_id);
+
+ asprintf(&proxyfile, "%s/%s/%s/userproxy.pem",
+ proxydir, user_dn_enc, delegation_id);
+ free(user_dn_enc);
+ user_dn_enc = NULL;
+
+ /* find the existing private key file */
+ prvkeyfile = GRSTx509CachedProxyKeyFind(proxydir, delegation_id, user_dn);
+ if (prvkeyfile == NULL)
+ goto end;
+
+ /* insert proxy private key into canl structure,
+ read from private key file */
+ ret = canl_cred_load_priv_key_file(c_ctx, proxy_bob, prvkeyfile, NULL, NULL);
+ if (ret)
+ goto end;
+ unlink(prvkeyfile);
+ free(prvkeyfile);
+ prvkeyfile = NULL;
+
+ ret = canl_cred_save_proxyfile(c_ctx, proxy_bob, proxyfile);
+ if (ret)
+ goto end;
+
+ retval = GRST_RET_OK;
+
+end:
+ if (proxyfile)
+ free(proxyfile);
+ if (proxy_bob)
+ canl_cred_free(c_ctx, proxy_bob);
+ if (certstack)
+ sk_X509_free(certstack);
+ if (prvkeyfile)
+ free(prvkeyfile);
+ if (user_dn_enc)
+ free(user_dn_enc);
+ if (c_ctx)
+ canl_free_ctx(c_ctx);
+
+ return retval;
+}