/* sslapp.c - ssl application code */
/*-
* Copyright (c) 2002, 2003, 2004, 2005 Nick Leuta
* All rights reserved.
*
* This software is based on code developed by Tim Hudson <tjh@cryptsoft.com>
* for use in the SSLftp project.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
*/
#ifdef USE_SSL
#include <pwd.h>
#include <regex.h>
#include <signal.h>
#include <stdarg.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef LINUX
#include <sys/time.h>
#endif
#include <port_base.h>
#include "sslapp.h"
BIO *bio_err;
SSL *ssl_con;
SSL_CTX *ssl_ctx;
X509_STORE *x509st_CRL = NULL;
int ssl_debug_flag = 0;
int ssl_active_flag = 0;
int ssl_verify_flag = SSL_VERIFY_NONE;
int ssl_logerr_syslog = 0; /* Log error information to syslog */
int ssl_verbose_flag = 0;
char *ssl_cert_file = NULL;
char *ssl_key_file = NULL;
char *ssl_cipher_list = NULL;
char *ssl_log_file = NULL;
char *ssl_CA_file = NULL;
char *ssl_CA_path = NULL;
char *ssl_CRL_file = NULL;
char *ssl_CRL_path = NULL;
/* fwd decl */
static void client_info_callback();
int
do_ssleay_init(int server)
{
char *p;
/* Make sure we have somewhere we can log errors to */
if (bio_err == NULL) {
if ((bio_err = BIO_new(BIO_s_file())) != NULL) {
if (ssl_log_file == NULL) {
BIO_set_fp(bio_err, stderr, BIO_NOCLOSE);
} else {
if (BIO_write_filename(bio_err, ssl_log_file)<=0) {
/* not a lot we can do */
}
}
}
}
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "SSL_DEBUG_FLAG on");
/*
* Init things so we will get meaningful error messages rather than
* numbers
*/
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
ssl_ctx = (SSL_CTX *)SSL_CTX_new(SSLv23_method());
/*
* We may require a temp 512 bit RSA key because of the wonderful way
* export things work... If so we generate one now!
*/
if (server) {
const unsigned char ctx_sid[] = "BSDftpd-ssl";
SSL_CTX_set_session_id_context(ssl_ctx, ctx_sid, strlen((const char*)ctx_sid));
if (SSL_CTX_need_tmp_RSA(ssl_ctx)) {
RSA *rsa;
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "Generating temp (512 bit) RSA key...");
rsa = RSA_generate_key(512, RSA_F4, NULL, NULL);
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "Generation of temp (512 bit) RSA key done");
if (!SSL_CTX_set_tmp_rsa(ssl_ctx, rsa)) {
ssl_log_msgn(bio_err, "Failed to assign generated temp RSA key!");
}
RSA_free(rsa);
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "Assigned temp (512 bit) RSA key");
}
}
/*
* Also switch on all the interoperability and bug workarounds so
* that we will communicate with people that cannot read poorly
* written specs :-)
*/
SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
/* The user can set whatever ciphers they want to use */
if (ssl_cipher_list == NULL) {
p = getenv("SSL_CIPHER");
if (p != NULL)
SSL_CTX_set_cipher_list(ssl_ctx, p);
} else
SSL_CTX_set_cipher_list(ssl_ctx, ssl_cipher_list);
/*
* For verbose we use the 0.6.x info callback that I got eric to
* finally add into the code :-) --tjh
*/
if (ssl_verbose_flag)
SSL_CTX_set_info_callback(ssl_ctx, client_info_callback);
/* Add in any certificates if you want to here... */
if (ssl_cert_file) {
if (!SSL_CTX_use_certificate_file(ssl_ctx, ssl_cert_file,
X509_FILETYPE_PEM)) {
ssl_log_err(bio_err, "Error loading '%s'", ssl_cert_file);
return (0);
} else {
if (!ssl_key_file)
ssl_key_file = ssl_cert_file;
if (!SSL_CTX_use_RSAPrivateKey_file(ssl_ctx,
ssl_key_file, X509_FILETYPE_PEM)) {
ssl_log_err(bio_err, "Error loading '%s'",
ssl_cert_file);
return (0);
}
}
}
/*
* Check if certificate locations specified in command-line or
* environment. Command-line values will override environment ones.
*/
if (ssl_CA_file != NULL || ssl_CA_path != NULL ||
getenv(X509_get_default_cert_file_env()) ||
getenv(X509_get_default_cert_dir_env())) {
SSL_CTX_load_verify_locations(ssl_ctx,
ssl_CA_file ? ssl_CA_file : getenv(X509_get_default_cert_file_env()),
ssl_CA_path ? ssl_CA_path : getenv(X509_get_default_cert_dir_env()));
} else {
/*
* Make sure we will find certificates in the standard
* location ... Otherwise we don't look anywhere for these
* things which is going to make client certificate exchange
* rather useless :-)
*/
SSL_CTX_set_default_verify_paths(ssl_ctx);
}
/*
* Check if CRL locations specified in command-line or environment.
* Command-line values will override environment ones.
*/
if (ssl_CRL_file != NULL || ssl_CRL_path != NULL ||
getenv("SSL_CRL_FILE") || getenv("SSL_CRL_DIR")) {
x509st_CRL = ssl_X509_STORE_create(
ssl_CRL_file ? ssl_CRL_file : getenv("SSL_CRL_FILE"),
ssl_CRL_path ? ssl_CRL_path : getenv("SSL_CRL_DIR"));
} else {
/*
* Set defaults for CRL locations
*/
char ssl_CRL_file_tmp[MAXPATHLEN], ssl_CRL_path_tmp[MAXPATHLEN];
snprintf(ssl_CRL_file_tmp, sizeof(ssl_CRL_file_tmp), "%s/crl.pem",
X509_get_default_cert_area());
snprintf(ssl_CRL_path_tmp, sizeof(ssl_CRL_path_tmp), "%s",
X509_get_default_cert_dir());
x509st_CRL = ssl_X509_STORE_create(ssl_CRL_file_tmp,
ssl_CRL_path_tmp);
}
SSL_CTX_set_verify(ssl_ctx, ssl_verify_flag, verify_cb_CRL);
return (1);
}
static void
client_info_callback(SSL *s, int where, int ret)
{
if (where == SSL_CB_CONNECT_LOOP) {
ssl_log_msgn(bio_err, "SSL_connect:%s %s",
SSL_state_string(s), SSL_state_string_long(s));
} else if (where == SSL_CB_CONNECT_EXIT) {
if (ret == 0) {
ssl_log_msgn(bio_err, "SSL_connect:failed in %s %s",
SSL_state_string(s), SSL_state_string_long(s));
} else if (ret < 0) {
ssl_log_msgn(bio_err, "SSL_connect:error in %s %s",
SSL_state_string(s), SSL_state_string_long(s));
}
}
}
/*
* Certificate Revocation List (CRL) Storage Support
*
* CRL support is based on the similar CRL support developed by
* Ralf S. Engelschall <rse@engelschall.com> for use in the
* mod_ssl project (http://www.modssl.org/).
*/
/* Initialize X509_STORE structure (which holds the tables etc for verification
* stuff).
* arguments:
* cpFile - a file of CRLs in PEM format
* cpPath - a directory containing CRLs in PEM format and its hashes.
* return:
* the pointer to X509_STORE structure or NULL if failed.
* notes:
* This function is very similar to X509_STORE_load_locations(),
* the only principal difference is that return values of
* X509_LOOKUP_*() calls aren't checked.
*/
X509_STORE *
ssl_X509_STORE_create(char *cpFile, char *cpPath)
{
X509_STORE *pStore;
X509_LOOKUP *pLookup;
if (cpFile == NULL && cpPath == NULL)
return NULL;
if ((pStore = X509_STORE_new()) == NULL)
return NULL;
if (cpFile != NULL) {
if ((pLookup = X509_STORE_add_lookup(pStore, X509_LOOKUP_file())) == NULL) {
X509_STORE_free(pStore);
return NULL;
}
X509_LOOKUP_load_file(pLookup, cpFile, X509_FILETYPE_PEM);
}
if (cpPath != NULL) {
if ((pLookup = X509_STORE_add_lookup(pStore, X509_LOOKUP_hash_dir())) == NULL) {
X509_STORE_free(pStore);
return NULL;
}
X509_LOOKUP_add_dir(pLookup, cpPath, X509_FILETYPE_PEM);
}
return pStore;
}
/* This function is a wrapper around X509_STORE_get_by_subject().
* Return values are the same, arguments nType, pName and pObj are the same
* too, pStore is used for initialization of X509_STORE_CTX structure
* which is used while validating a single certificate.
*/
int
ssl_X509_STORE_lookup(X509_STORE *pStore, int nType,
X509_NAME *pName, X509_OBJECT *pObj)
{
X509_STORE_CTX pStoreCtx;
int rc;
X509_STORE_CTX_init(&pStoreCtx, pStore, NULL, NULL);
rc = X509_STORE_get_by_subject(&pStoreCtx, nType, pName, pObj);
X509_STORE_CTX_cleanup(&pStoreCtx);
return rc;
}
/* Certificate verify callback function which performs CRL-based revocation
* checks. See SSL_CTX_set_verify() documentation for more information.
* Reports next verification errors (see verify(1) for description):
* X509_V_ERR_CRL_SIGNATURE_FAILURE
* X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD
* X509_V_ERR_CRL_HAS_EXPIRED
* X509_V_ERR_CERT_REVOKED
*
* Comments written by Ralf S. Engelschall <rse@engelschall.com>.
*/
int
verify_cb_CRL(int ok, X509_STORE_CTX *ctx)
{
X509_OBJECT obj;
X509_NAME *subject;
X509_NAME *issuer;
X509 *xs;
X509_CRL *crl;
X509_REVOKED *revoked;
int i, n, rc;
/*
* Unless a revocation store for CRLs was created we
* cannot do any CRL-based verification, of course.
*/
if (!x509st_CRL)
return ok;
/*
* Determine certificate ingredients in advance
*/
xs = X509_STORE_CTX_get_current_cert(ctx);
subject = X509_get_subject_name(xs);
issuer = X509_get_issuer_name(xs);
/*
* OpenSSL provides the general mechanism to deal with CRLs but does not
* use them automatically when verifying certificates, so we do it
* explicitly here. We will check the CRL for the currently checked
* certificate, if there is such a CRL in the store.
*
* We come through this procedure for each certificate in the certificate
* chain, starting with the root-CA's certificate. At each step we've to
* both verify the signature on the CRL (to make sure it's a valid CRL)
* and it's revocation list (to make sure the current certificate isn't
* revoked). But because to check the signature on the CRL we need the
* public key of the issuing CA certificate (which was already processed
* one round before), we've a little problem. But we can both solve it and
* at the same time optimize the processing by using the following
* verification scheme (idea and code snippets borrowed from the GLOBUS
* project):
*
* 1. We'll check the signature of a CRL in each step when we find a CRL
* through the _subject_ name of the current certificate. This CRL
* itself will be needed the first time in the next round, of course.
* But we do the signature processing one round before this where the
* public key of the CA is available.
*
* 2. We'll check the revocation list of a CRL in each step when
* we find a CRL through the _issuer_ name of the current certificate.
* This CRLs signature was then already verified one round before.
*
* This verification scheme allows a CA to revoke its own certificate as
* well, of course.
*/
/*
* Try to retrieve a CRL corresponding to the _subject_ of
* the current certificate in order to verify it's integrity.
*/
memset((char *)&obj, 0, sizeof(obj));
rc = ssl_X509_STORE_lookup(x509st_CRL, X509_LU_CRL, subject, &obj);
crl = obj.data.crl;
if (rc > 0 && crl != NULL) {
/*
* Verify the signature on this CRL
*/
if (X509_CRL_verify(crl, X509_get_pubkey(xs)) <= 0) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_SIGNATURE_FAILURE);
X509_OBJECT_free_contents(&obj);
return 0;
}
/*
* Check date of CRL to make sure it's not expired
*/
i = X509_cmp_current_time(X509_CRL_get_nextUpdate(crl));
if (i == 0) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD);
X509_OBJECT_free_contents(&obj);
return 0;
}
if (i < 0) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CRL_HAS_EXPIRED);
X509_OBJECT_free_contents(&obj);
return 0;
}
X509_OBJECT_free_contents(&obj);
}
/*
* Try to retrieve a CRL corresponding to the _issuer_ of
* the current certificate in order to check for revocation.
*/
memset((char *)&obj, 0, sizeof(obj));
rc = ssl_X509_STORE_lookup(x509st_CRL, X509_LU_CRL, issuer, &obj);
crl = obj.data.crl;
if (rc > 0 && crl != NULL) {
/*
* Check if the current certificate is revoked by this CRL
*/
n = sk_X509_REVOKED_num(X509_CRL_get_REVOKED(crl));
for (i = 0; i < n; i++) {
revoked = sk_X509_REVOKED_value(X509_CRL_get_REVOKED(crl), i);
if (ASN1_INTEGER_cmp(revoked->serialNumber, X509_get_serialNumber(xs)) == 0) {
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REVOKED);
X509_OBJECT_free_contents(&obj);
return 0;
}
}
X509_OBJECT_free_contents(&obj);
}
return ok;
}
/*
* Compare two X509 certificates.
* return:
* 1 - certificates are not NULL and equal
* 0 - certificates are not NULL and differ
* -1 - both certificates are NULL
* -2 - x509_cert1 is NULL, x509_cert2 is not NULL
* -3 - x509_cert1 in not NULL, x509_cert2 is NULL
*/
int
ssl_X509_cmp(X509 *x509_cert1, X509 *x509_cert2)
{
/* X509_cmp() will crash if any of its args are NULL
*/
if (x509_cert1 != NULL) {
if (x509_cert2 == NULL) {
return -3; /* x509_cert1 in not NULL, x509_cert2 is NULL */
} else {
if (X509_cmp(x509_cert1, x509_cert2)) {
return 0; /* certificates are differ */
} else {
return 1; /* certificates are equal */
}
}
} else {
if (x509_cert2 == NULL) {
return -1; /* both certificates are NULL */
} else {
return -2; /* x509_cert1 is NULL, x509_cert2 is not NULL */
}
}
}
/*
* Log an error and a debug information.
*/
/*
* Log the message.
*/
void
ssl_log_msg(BIO *bio, const char *fmt, ...)
{
va_list ap;
char *outputbuf;
va_start(ap, fmt);
vasprintf(&outputbuf, fmt, ap);
va_end(ap);
if (outputbuf == NULL) {
BIO_printf(bio, "\r\nRan out of memory.\r\n");
(void)BIO_flush(bio);
return;
}
BIO_printf(bio, "%s", outputbuf);
(void)BIO_flush(bio);
free(outputbuf);
}
/*
* Log the message prepended and appended by the newline.
*/
void
ssl_log_msgn(BIO *bio, const char *fmt, ...)
{
va_list ap;
char *outputbuf;
va_start(ap, fmt);
vasprintf(&outputbuf, fmt, ap);
va_end(ap);
if (outputbuf == NULL) {
BIO_printf(bio, "\r\nRan out of memory.\r\n");
(void)BIO_flush(bio);
return;
}
BIO_printf(bio, "\r\n%s\r\n", outputbuf);
(void)BIO_flush(bio);
free(outputbuf);
}
/*
* Common code for both ssl_log_vwarn() and ssl_log_vwarn_debug().
*/
void
ssl_log_vwarn_common(BIO *bio, int debug_flag, const char *fmt, va_list ap)
{
char *tmp, *outputbuf;
vasprintf(&tmp, fmt, ap);
if (tmp == NULL) {
BIO_printf(bio, "\r\nRan out of memory.\r\n");
(void)BIO_flush(bio);
if (ssl_logerr_syslog)
syslog(LOG_ERR, "Ran out of memory.");
return;
}
asprintf(&outputbuf, "%s: %s", tmp,
debug_flag ? ERR_error_string(ERR_get_error(), NULL) :
ERR_reason_error_string(ERR_get_error()));
free(tmp);
if (outputbuf == NULL) {
BIO_printf(bio, "\r\nRan out of memory.\r\n");
(void)BIO_flush(bio);
if (ssl_logerr_syslog)
syslog(LOG_ERR, "Ran out of memory.");
return;
}
BIO_printf(bio, "%s\r\n", outputbuf);
(void)BIO_flush(bio);
if (ssl_logerr_syslog)
syslog(LOG_WARNING, "%s", outputbuf);
free(outputbuf);
}
/*
* Log the message appended by the reason of the last error code from the SSL
* error queue and removes that code from the queue.
*/
void
ssl_log_vwarn(BIO *bio, const char *fmt, va_list ap)
{
ssl_log_vwarn_common(bio, 0, fmt, ap);
}
void
ssl_log_warn(BIO *bio, const char *fmt, ...)
{
va_list ap;
va_start(ap,fmt);
ssl_log_vwarn(bio, fmt, ap);
va_end(ap);
}
/*
* Log the message appended by the human-readable string that represents the
* last error code from the SSL error queue and removes it.
*/
void
ssl_log_vwarn_debug(BIO *bio, const char *fmt, va_list ap)
{
ssl_log_vwarn_common(bio, 1, fmt, ap);
}
void
ssl_log_warn_debug(BIO *bio, const char *fmt, ...)
{
va_list ap;
va_start(ap,fmt);
ssl_log_vwarn_debug(bio, fmt, ap);
va_end(ap);
}
/*
* Log the message with the verbosity level depending on the SSL debug state.
*/
void
ssl_log_err(BIO *bio, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (ssl_debug_flag) {
ssl_log_vwarn_debug(bio, fmt, ap);
} else {
ssl_log_vwarn(bio, fmt, ap);
}
va_end(ap);
}
/*
* Support for the X.509 client authentication.
*/
/*
* Get the text value by the object type.
* arguments:
* obj_type - object type name, may be provided in "short" (SN; for
* example "CN") or "long" (LN; for example "commonName")
* form.
* x509_name, str and len means the same as correspondent arguments of
* X509_NAME_get_text_by_NID().
* str - buffer for returning the string value.
* len - size of the buffer.
* return:
* >=0 - actual length of the returned string str.
* -1 - object not found.
* -2 - unknown object type in obj_type.
* notes:
* This function is a wrapper around X509_NAME_get_text_by_NID().
*/
int
ssl_X509_NAME_get_text_by_name(X509_NAME *x509_name, char *obj_type,
char *str, int len)
{
int nid;
if ((nid=OBJ_txt2nid(obj_type)) == NID_undef) {
return -2;
}
return (X509_NAME_get_text_by_NID(x509_name, nid, str, len));
}
/*
* Get the login name from the certificate.
* arguments:
* cert - X.509 certificate.
* obj_type - object type name, may be provided in "short" (SN; for
* example "CN") or "long" (LN; for example "commonName")
* form.
* username - buffer for returning the string value.
* len - size of the buffer.
* return:
* 1 - if no errors occurred.
* <=0 - if any error(s) occurred.
* notes:
* in future the list of return values <=0 may be extended, currently
* only 0 is supported.
*/
int
x509_get_value(X509 *cert, char *obj_type, char *username, int len)
{
X509_NAME *x509_subject_name;
int ret = -1;
x509_subject_name = X509_get_subject_name(cert);
if (x509_subject_name != NULL)
ret = ssl_X509_NAME_get_text_by_name(x509_subject_name, obj_type,
username, len);
if (ret > 0)
return 1;
else
return 0;
}
/*
* Get the login name by the e-mail address from the certificate.
* arguments:
* cert - X.509 certificate.
* obj_type - object type name, may be provided in "short" (SN; for
* example "CN") or "long" (LN; for example "commonName")
* form.
* domain - domain name that must be after the `@' symbol in the e-mail
* address; ignored if NULL.
* username - buffer for returning the string value.
* len - size of the buffer.
* return:
* 1 - if login name extracted successfully.
* <=0 - if any error(s) occurred.
* notes:
* E-mail address is expected in form: "username@domain.name". If domain
* is not NULL, it will be verified against "domain.name", after that
* "username" will be returned as the login name.
*
* In future the list of return values <=0 may be extended, currently
* only 0 is supported.
*/
int
ssl_get_login_by_email(X509 *cert, char *obj_type, char *domain,
char *username, int len)
{
char email[512], *cp_login, *cp_domain;
/* Extract the field from the cert */
if (x509_get_value(cert, obj_type, email, sizeof(email)) > 0) {
/* Process e-mail */
cp_login = email;
cp_domain = strchr(email, '@');
if (!cp_domain) {
/* Invalid e-mail address */
return 0;
}
*cp_domain++ = '\0';
/* If domain is specified, it must be compared with the cert's one */
if (domain != NULL) {
if (strcasecmp(cp_domain, domain) != 0) {
/* Cert's e-mail domain doesn't match the expected one */
return 0;
}
}
strncpy(username, cp_login, len);
return 1;
}
/* Error extracting a field from the cert */
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_get_login_by_email(): error extracting field: %s", obj_type);
return 0;
}
/*
* Check if the client's certificate is presented in the specified file.
* arguments:
* cert - client's X.509 certificate.
* filename - name of the file that contains a set of certificates.
* return:
* 1 - if file contains the provided certificate.
* 0 - otherwise, or if any error(s) occurred.
*/
int
ssl_check_cert_file(X509 *cert, char *filename)
{
int ret = 0;
FILE *fp;
X509 *file_cert;
struct stat stbuf;
if (!cert || !filename) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_check_cert_file(): at least one argument in NULL");
return 0;
}
if (lstat(filename, &stbuf) < 0 || !S_ISREG(stbuf.st_mode)) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_check_cert_file(): '%s' is not a plain file or is a symlink",
filename);
return 0;
}
if (!(fp = fopen(filename, "r"))) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_check_cert_file(): can't open '%s' for reading", filename);
return 0;
}
while ((file_cert = PEM_read_X509(fp, NULL, NULL, NULL))) {
if (ssl_X509_cmp(cert, file_cert) == 1)
ret = 1;
X509_free(file_cert);
if (ret)
break;
}
fclose(fp);
return ret;
}
/* Check the client's login name and the certificate with help of an external
* program.
* arguments:
* cert - client's X.509 certificate.
* name - non-NULL string that contains the login name provided by the
* client; if it is a zero-length string (i.e. the first symbol
* is '\0'), it will be used as a buffer for returning the
* login name.
* len - size of the buffer; ignored if name has non-zero length.
* progname - full path to the external authentication program.
* return:
* 1 - if the external authenticator allows access to the requested
* account.
* 0 - otherwise, or if any error(s) occurred.
*/
int
ssl_check_ext_prog(X509 *cert, char *name, int len, char *progname)
{
int ret = 0, err;
int p_in[2], p_out[2]; /* pipes for child's stdin and stdout */
FILE *f_in = NULL; /* stream for child's stdin */
pid_t child_pid = -1;
int oldmask, status;
char buf[BUFSIZ], *cp_rcode, *cp_name, *str;
fd_set rfds;
struct timeval tv;
if (!cert || !name || !progname) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_check_ext_prog(): at least one argument is NULL");
return 0;
}
if (pipe(p_in)) {
ret = 0;
goto end;
}
if (pipe(p_out)) {
ret = 0;
goto end;
}
/*
* Fork
*/
child_pid = fork();
/* Is it the parent or the child process? */
switch (child_pid) {
case -1: /* fork() failed and it is still to be the parent process */
ret = 0;
goto end;
case 0: /* In the child */
/* Map stdin/stdout/stderr to the pipe to the parent */
dup2(p_in[0], 0); /* stdin */
dup2(p_out[1], 1); /* stdout */
dup2(p_out[1], 2); /* stderr */
/* Close descriptors of pipes */
close(p_in[0]);
close(p_in[1]);
close(p_out[1]);
close(p_out[0]);
/* Exec */
execl(progname, progname, NULL);
/* Exec failed */
_exit(1);
}
/*
* In the parent process
*/
/* Those ends of pipes must not be used by the parent */
close(p_in[0]);
close(p_out[1]);
/* Get the control over child's stdin */
if (!(f_in = fdopen(p_in[1], "w"))) {
ret = 0;
goto end;
}
/* Send tokens to the auth program */
fprintf(f_in, "%s\r\n", name);
PEM_write_X509(f_in, cert);
fflush(f_in);
/* Wait for the result of the authentication from child's stdout */
FD_ZERO(&rfds);
FD_SET(p_out[0], &rfds);
tv.tv_sec = 20; /* auth timeout */
tv.tv_usec = 0;
err = select(FD_SETSIZE, &rfds, NULL, NULL, &tv);
if (err < 1) { /* timeout expired */
kill(child_pid, SIGTERM); /* terminate the ext. program */
ret = 0;
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"No response from the external authentication program");
goto end;
}
/* Read the result of the authentication */
err = read(p_out[0], buf, sizeof(buf) - 1);
if (err <= 7) { /* error reading the reply of the authenticator, or an
* incorrect reply (min length == 7) */
ret = 0;
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"Invalid response from the external authentication program");
goto end;
}
buf[err] = '\0';
cp_rcode = buf;
str = strchr(buf, '\n');
if (str) {
*str++ = '\0';
cp_name = str;
}
else {
ret = 0;
goto end;
}
/* Wipe cr/lf symbols */
str = strchr(cp_rcode, '\r');
if (str)
*str = '\0';
str = strchr(cp_name, '\r');
if (str)
*str = '\0';
str = strchr(cp_name, '\n');
if (str)
*str = '\0';
/* Process the result of the authentication */
if (cp_rcode[0] == '1') { /* 1xx - access allowed by the program */
if (strlen(name) > 0) {
/* Sanity checking */
ret = (strcmp(name, cp_name) == 0) ? 1 : 0;
} else {
/* Return the login name extracted from the certificate */
strncpy(name, cp_name, len);
ret = 1;
}
} else {
ret = 0;
}
end:
/* Close streams */
if (f_in!=NULL)
fclose(f_in);
/* Close used ends of pipes */
close(p_in[1]);
close(p_out[0]);
/* Close other ends of pipes, normally it isn't needed */
close(p_in[0]);
close(p_out[1]);
/* Wait for child's termination */
if (child_pid > 0) {
oldmask = sigblock(sigmask(SIGINT)|sigmask(SIGQUIT)|sigmask(SIGHUP));
while (waitpid(child_pid, &status, 0) < 0 && errno == EINTR)
continue;
(void)sigsetmask(oldmask);
}
return ret;
}
/* Do the X.509 user authentication.
* arguments:
* service - name of the service which requests the auth.
* cert - an X.509 certificate.
* name - the login name provided by the client.
* return:
* 1 - the X.509 certificate is authorized to use the name.
* <=0 - otherwise, or if the peer doesn't provide the cert (for any
* reason, include an unencrypted connection), or if any error(s)
* occurred.
* 0 - the X.509 certificate is NOT authorized to use the name.
* -5 - the certificate is not provided.
* -6 - the service name is not provided.
* -7 - error opening _PATH_X509_AUTH
*/
int
ssl_x509_auth(char *service, X509 *cert, char *name)
{
FILE *user_fp;
char buf[_X509_AUTH_MAXSTRLEN];
int ret = 1; /* 0 - all ok, !=0 - something wrong */
char username[MAXLOGNAME];
char *ssl_auth_name = NULL;
/* Sanity checking */
if (service == NULL) {
/* The service name isn't provided */
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "ssl_x509_auth(): service name is NULL");
return -6;
}
if (cert != NULL) {
/* Get the certificate subject */
ssl_auth_name = (char*)ONELINE_NAME(X509_get_subject_name(cert));
} else {
/* Can't authenticate because no peer certificate provided */
if (ssl_debug_flag)
ssl_log_msgn(bio_err, "ssl_x509_auth(): certificate is NULL");
return -5;
}
user_fp = fopen(_PATH_X509_AUTH, "r");
if (!user_fp) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_x509_auth(): can't open '%s' for reading", _PATH_X509_AUTH);
return -7;
}
while (fgets(buf, sizeof(buf), user_fp)) {
char *cp_service, *cp_action, *cp_userlist, *cp_subject;
char *n;
int action_flag; /* deny (0) | allow (1) */
/* Allow comments in the file */
if (buf[0]=='#')
continue;
/* Wipe cr/lf symbols */
n = strchr(buf, '\n');
if (n)
*n = '\0';
n = strchr(buf, '\r');
if (n)
*n = '\0';
/*
* Process fields
*/
/* Point to the service name */
cp_service = buf;
/* Point to "action", NUL-terminate "service" */
cp_action = strchr(cp_service, ':');
if (!cp_action)
continue;
*cp_action++ = '\0';
/* Check if the service name matches with the requested one */
if (strlen(cp_service) == 0)
continue;
else {
if (strcmp(cp_service, service))
continue;
}
/* Point to "userlist", NUL-terminate "action" */
cp_userlist = strchr(cp_action, ':');
if (!cp_userlist)
continue;
*cp_userlist++ = '\0';
/* Process the action, set the action flag */
switch (strlen(cp_action) == 0) {
case 1:
continue;
case 0:
if(!strcmp(cp_action, "deny")) {
action_flag = 0;
break;
}
if(!strcmp(cp_action, "allow")) {
action_flag = 1;
break;
}
default:
ssl_log_msgn(bio_err, "%s: invalid action: %s", _PATH_X509_AUTH,
cp_action);
continue;
}
/* Point to the subject template, NUL-terminate "userlist" */
cp_subject = strchr(cp_userlist, ':');
if (!cp_subject)
continue;
*cp_subject++ = '\0';
/*
* Process the userlist
*/
n = cp_userlist;
while (n) {
/* Get next template for allowed login name from the list */
cp_userlist = strchr(n, ',');
if (cp_userlist)
*cp_userlist++ = '\0';
/* Process the template and prepare the username */
switch (n[0]) {
case '/': /* username is a field of provided subject */
if (n[1] == '/') {
/* The field contains the e-mail address */
char *domain, field[BUFSIZ];
domain = strchr(n + 2, '/');
if (domain)
*domain++ = '\0';
strncpy(field, n + 2, sizeof(field));
if (ssl_get_login_by_email(cert, field, domain, username,
sizeof(username)) <= 0) {
n = cp_userlist;
continue;
}
} else {
/* The field contains the username itself */
if (x509_get_value(cert, n + 1, username,
sizeof(username)) <= 0) {
n = cp_userlist;
continue;
}
}
break;
case '*': /* Username is issued by the client */
strncpy(username, name, sizeof(username));
break;
default: /* The username itself */
strncpy(username, n, sizeof(username));
}
/* Compare the provided login name against the one from userlist */
if (!strcmp(name, username)) {
ret = 0;
break;
}
n = cp_userlist;
}
/* Compare the certificate against the allowed one */
if (ret == 0) {
if (cp_subject[0] == '-') {
/* The allowed certificate is a complex expression */
switch (cp_subject[1]) {
case 'r': { /* subject is defined as a reqular expression */
regex_t preg;
cp_subject = cp_subject + 2;
regcomp(&preg, cp_subject, REG_EXTENDED|REG_NOSUB);
ret = regexec(&preg, ssl_auth_name, 0, 0, 0);
regfree(&preg);
break;
}
case 'f': { /* certificate itself in a file */
struct passwd *pwd;
char fnbuf[MAXPATHLEN + 1];
cp_subject = cp_subject + 2;
/* Do '~' expansion */
if (cp_subject[0] == '~') {
char *cp;
cp = cp_subject + 1;
if (!(pwd = getpwnam(name))) {
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_x509_auth(): no such user: %s", name);
break;
}
snprintf(fnbuf, sizeof(buf), "%s/%s", pwd->pw_dir, cp);
} else {
snprintf(fnbuf, sizeof(buf), cp_subject);
}
ret = !ssl_check_cert_file(cert, fnbuf);
break;
}
case 'p': { /* send the cert to the external program */
cp_subject = cp_subject + 2;
ret = !ssl_check_ext_prog(cert, name, strlen(name),
cp_subject);
break;
}
default:
if (ssl_debug_flag)
ssl_log_msgn(bio_err,
"ssl_x509_auth(): incorrect directive: -%c",
cp_subject[1]);
ret = -1;
}
} else {
/* The pre-configured subject */
ret = strcmp(ssl_auth_name, cp_subject);
}
}
if (ret == 0) {
/* Match is found */
fclose(user_fp);
switch (action_flag) {
case 0: /* deny */
return 0;
case 1: /* allow */
return 1;
default:
/* Programming error */
ssl_log_msgn(bio_err,
"ssl_x509_auth(): internal: unhandled action flag '%d' (action '%s')",
action_flag, cp_action);
}
}
}
/* No match is found */
fclose(user_fp);
return 0;
}
#else /* !USE_SSL */
/* Something here to stop warnings if we build without TLS/SSL support */
static int
dummy_func()
{
return 0;
}
#endif /* USE_SSL */
syntax highlighted by Code2HTML, v. 0.9.1