/* Licensed to the Apache Software Foundation (ASF) under one or more *contributorlicenseagreements.SeetheNOTICEfiledistributedwith *thisworkforadditionalinformationregardingcopyrightownership. *TheASFlicensesthisfiletoYouundertheApacheLicense,Version2.0 *(the"License");youmaynotusethisfileexceptincompliancewith *theLicense.YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
/* _ _ *___________||______||mod_ssl *|'_`_\/_\/_`|/__/__||ApacheInterfacetoOpenSSL *||||||(_)|(_||\__\__\| *|_||_||_|\___/\__,_|___|___/___/_| *|_____| *ssl_engine_vars.c *VariableLookupFacility
*/ /* ``Those of you who think they knoweverythingareveryannoying tothoseofuswhodo.''
-- Unknown */ #include"ssl_private.h" #include"mod_ssl.h" #include"ap_expr.h"
/* This function must remain safe to use for a non-SSL connection. */ char *ssl_var_lookup(apr_pool_t *p, server_rec *s, conn_rec *c, request_rec *r, char *var)
{
SSLModConfigRec *mc = myModConfig(s); constchar *result; BOOL resdup;
apr_time_exp_t tm;
result = NULL;
resdup = TRUE;
/* *Whennopoolisgiventrytofindone
*/ if (p == NULL) { if (r != NULL)
p = r->pool; elseif (c != NULL)
p = c->pool; else
p = mc->pPool;
}
/* *Requestdependentstuff
*/ if (r != NULL) { switch (var[0]) { case'H': case'h': if (strcEQ(var, "HTTP_USER_AGENT"))
result = apr_table_get(r->headers_in, "User-Agent"); elseif (strcEQ(var, "HTTP_REFERER"))
result = apr_table_get(r->headers_in, "Referer"); elseif (strcEQ(var, "HTTP_COOKIE"))
result = apr_table_get(r->headers_in, "Cookie"); elseif (strcEQ(var, "HTTP_FORWARDED"))
result = apr_table_get(r->headers_in, "Forwarded"); elseif (strcEQ(var, "HTTP_HOST"))
result = apr_table_get(r->headers_in, "Host"); elseif (strcEQ(var, "HTTP_PROXY_CONNECTION"))
result = apr_table_get(r->headers_in, "Proxy-Connection"); elseif (strcEQ(var, "HTTP_ACCEPT"))
result = apr_table_get(r->headers_in, "Accept"); elseif (strlen(var) > 5 && strcEQn(var, "HTTP:", 5)) /* all other headers from which we are still not know about */
result = apr_table_get(r->headers_in, var+5); break;
case'R': case'r': if (strcEQ(var, "REQUEST_METHOD"))
result = r->method; elseif (strcEQ(var, "REQUEST_SCHEME"))
result = ap_http_scheme(r); elseif (strcEQ(var, "REQUEST_URI"))
result = r->uri; elseif (strcEQ(var, "REQUEST_FILENAME"))
result = r->filename; elseif (strcEQ(var, "REMOTE_ADDR"))
result = r->useragent_ip; elseif (strcEQ(var, "REMOTE_HOST"))
result = ap_get_useragent_host(r, REMOTE_NAME, NULL); elseif (strcEQ(var, "REMOTE_IDENT"))
result = ap_get_remote_logname(r); elseif (strcEQ(var, "REMOTE_USER"))
result = r->user; break;
case'S': case's': if (strcEQn(var, "SSL", 3)) break; /* shortcut common case */
if (strcEQ(var, "SERVER_ADMIN"))
result = r->server->server_admin; elseif (strcEQ(var, "SERVER_NAME"))
result = ap_get_server_name_for_url(r); elseif (strcEQ(var, "SERVER_PORT"))
result = apr_psprintf(p, "%u", ap_get_server_port(r)); elseif (strcEQ(var, "SERVER_PROTOCOL"))
result = r->protocol; elseif (strcEQ(var, "SCRIPT_FILENAME"))
result = r->filename; break;
default: if (strcEQ(var, "PATH_INFO"))
result = r->path_info; elseif (strcEQ(var, "QUERY_STRING"))
result = r->args; elseif (strcEQ(var, "IS_SUBREQ"))
result = (r->main != NULL ? "true" : "false"); elseif (strcEQ(var, "DOCUMENT_ROOT"))
result = ap_document_root(r); elseif (strcEQ(var, "AUTH_TYPE"))
result = r->ap_auth_type; elseif (strcEQ(var, "THE_REQUEST"))
result = r->the_request; elseif (strlen(var) > 4 && strcEQn(var, "ENV:", 4)) {
result = apr_table_get(r->notes, var+4); if (result == NULL)
result = apr_table_get(r->subprocess_env, var+4);
} break;
}
}
/* *Connectionstuff
*/ if (result == NULL && c != NULL) {
SSLConnRec *sslconn = ssl_get_effective_config(c); if (strlen(var) > 4 && strcEQn(var, "SSL_", 4)
&& sslconn && sslconn->ssl)
result = ssl_var_lookup_ssl(p, sslconn, r, var+4); elseif (strcEQ(var, "HTTPS")) { if (sslconn && sslconn->ssl)
result = "on"; else
result = "off";
}
}
if (strcEQn(var, "Email_", 6)) {
type = GEN_EMAIL;
var += 6;
} elseif (strcEQn(var, "DNS_", 4)) {
type = GEN_DNS;
var += 4;
} elseif (strcEQn(var, "OTHER_", 6)) {
type = GEN_OTHERNAME;
var += 6; if (strEQn(var, "msUPN_", 6)) {
var += 6;
onf = "msUPN";
} elseif (strEQn(var, "dnsSRV_", 7)) {
var += 7;
onf = "id-on-dnsSRV";
} else return NULL;
} else return NULL;
/* sanity check: number must be between 1 and 4 digits */
numlen = strspn(var, "0123456789"); if ((numlen < 1) || (numlen > 4) || (numlen != strlen(var))) return NULL;
if (modssl_X509_getSAN(p, xs, type, onf, atoi(var), &entries)) /* return the first entry from this 1-element array */ return APR_ARRAY_IDX(entries, 0, char *); else return NULL;
}
/* Return a string giving the number of days remaining until 'tm', or
* "0" if this can't be determined. */ staticchar *ssl_var_lookup_ssl_cert_remain(apr_pool_t *p, ASN1_TIME *tm)
{
apr_time_t then, now = apr_time_now();
apr_time_exp_t exp = {0}; long diff; unsignedchar *dp;
/* Fail if the time isn't a valid ASN.1 TIME; RFC3280 mandates *thatthesecondsdigitsarepresenteventhoughASN.1
* doesn't. */ if ((tm->type == V_ASN1_UTCTIME && tm->length < 11) ||
(tm->type == V_ASN1_GENERALIZEDTIME && tm->length < 13) ||
!ASN1_TIME_check(tm)) { return apr_pstrdup(p, "0");
}
/* Add each RDN in 'xn' to the table 't' where the NID is present in
* 'nids', using key prefix 'pfx'. */ staticvoid extract_dn(apr_table_t *t, apr_hash_t *nids, constchar *pfx,
X509_NAME *xn, apr_pool_t *p)
{
X509_NAME_ENTRY *xsne;
apr_hash_t *count; int i, nid;
/* Hash of (int) NID -> (int *) counter to count each time an RDN
* with the given NID has been seen. */
count = apr_hash_make(p);
/* For each RDN... */ for (i = 0; i < X509_NAME_entry_count(xn); i++) { constchar *tag;
xsne = X509_NAME_get_entry(xn, i);
/* Retrieve the nid, and check whether this is one of the nids
* which are to be extracted. */
nid = OBJ_obj2nid((ASN1_OBJECT *)X509_NAME_ENTRY_get_object(xsne));
tag = apr_hash_get(nids, &nid, sizeof nid); if (tag) { constchar *key; int *dup; char *value;
/* Check whether a variable with this nid was already
* been used; if so, use the foo_N=bar syntax. */
dup = apr_hash_get(count, &nid, sizeof nid); if (dup) {
key = apr_psprintf(p, "%s%s_%d", pfx, tag, ++(*dup));
} else { /* Otherwise, use the plain foo=bar syntax. */
dup = apr_pcalloc(p, sizeof *dup);
apr_hash_set(count, &nid, sizeof nid, dup);
key = apr_pstrcat(p, pfx, tag, NULL);
}
value = modssl_X509_NAME_ENTRY_to_string(p, xsne, 0);
apr_table_setn(t, key, value);
}
}
}
/* Build up a hash table of (int *)NID->(char *)short-name for all
* the tags which are to be extracted: */
nids = apr_hash_make(p); for (n = 0; ssl_var_lookup_ssl_cert_dn_rec[n].name; n++) { if (ssl_var_lookup_ssl_cert_dn_rec[n].extract) {
apr_hash_set(nids, &ssl_var_lookup_ssl_cert_dn_rec[n].nid, sizeof(ssl_var_lookup_ssl_cert_dn_rec[0].nid),
ssl_var_lookup_ssl_cert_dn_rec[n].name);
}
}
/* Extract the server cert DNS -- note that the refcount does NOT
* increase: */
xs = SSL_get_certificate(ssl); if (xs) {
extract_dn(t, nids, "SSL_SERVER_S_DN_", X509_get_subject_name(xs), p);
extract_dn(t, nids, "SSL_SERVER_I_DN_", X509_get_issuer_name(xs), p);
}
/* Extract the client cert DNs -- note that the refcount DOES
* increase: */
xs = SSL_get_peer_certificate(ssl); if (xs) {
extract_dn(t, nids, "SSL_CLIENT_S_DN_", X509_get_subject_name(xs), p);
extract_dn(t, nids, "SSL_CLIENT_I_DN_", X509_get_issuer_name(xs), p);
X509_free(xs);
}
}
/* subjectAltName entries of the server certificate */
xs = SSL_get_certificate(ssl); if (xs) { if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
extract_san_array(t, "SSL_SERVER_SAN_Email", entries, p);
} if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
extract_san_array(t, "SSL_SERVER_SAN_DNS", entries, p);
} if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "id-on-dnsSRV", -1,
&entries)) {
extract_san_array(t, "SSL_SERVER_SAN_OTHER_dnsSRV", entries, p);
} /* no need to free xs (refcount does not increase) */
}
/* subjectAltName entries of the client certificate */
xs = SSL_get_peer_certificate(ssl); if (xs) { if (modssl_X509_getSAN(p, xs, GEN_EMAIL, NULL, -1, &entries)) {
extract_san_array(t, "SSL_CLIENT_SAN_Email", entries, p);
} if (modssl_X509_getSAN(p, xs, GEN_DNS, NULL, -1, &entries)) {
extract_san_array(t, "SSL_CLIENT_SAN_DNS", entries, p);
} if (modssl_X509_getSAN(p, xs, GEN_OTHERNAME, "msUPN", -1, &entries)) {
extract_san_array(t, "SSL_CLIENT_SAN_OTHER_msUPN", entries, p);
}
X509_free(xs);
}
}
/* For an extension type which OpenSSL does not recognize, attempt to *parsetheextensiontypeasaprimitivestring.Thiswillfailfor *anystructuredextensiontypeperthedocs.Returnsnon-zeroon
* success and writes the string to the given bio. */ staticint dump_extn_value(BIO *bio, ASN1_OCTET_STRING *str)
{ constunsignedchar *pp = str->data;
ASN1_STRING *ret = ASN1_STRING_new(); int rv = 0;
/* This allows UTF8String, IA5String, VisibleString, or BMPString;
* conversion to UTF-8 is forced. */ if (d2i_DISPLAYTEXT(&ret, &pp, str->length)) {
ASN1_STRING_print_ex(bio, ret, ASN1_STRFLGS_UTF8_CONVERT);
rv = 1;
}
/* We accept the "extension" string to be converted as *alongname(nsComment),shortname(DN)or *numericOID(1.2.3.4).
*/
oid = OBJ_txt2obj(extension, 0); if (!oid) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(01970) "could not parse OID '%s'", extension);
ERR_clear_error(); return NULL;
}