Exemple #1
0
int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
                           int port)
{
	int match;
	int index;
	char* common_name = NULL;
	int common_name_length = 0;
	char** alt_names = NULL;
	int alt_names_count = 0;
	int* alt_names_lengths = NULL;
	BOOL certificate_status;
	BOOL hostname_match = FALSE;
	BOOL verification_status = FALSE;
	rdpCertificateData* certificate_data;

	if (tls->settings->ExternalCertificateManagement)
	{
		BIO* bio;
		int status;
		int length;
		int offset;
		BYTE* pemCert;
		freerdp* instance = (freerdp*) tls->settings->instance;
		/**
		 * Don't manage certificates internally, leave it up entirely to the external client implementation
		 */
		bio = BIO_new(BIO_s_mem());

		if (!bio)
		{
			WLog_ERR(TAG, "BIO_new() failure");
			return -1;
		}

		status = PEM_write_bio_X509(bio, cert->px509);

		if (status < 0)
		{
			WLog_ERR(TAG, "PEM_write_bio_X509 failure: %d", status);
			return -1;
		}

		offset = 0;
		length = 2048;
		pemCert = (BYTE*) malloc(length + 1);

		if (!pemCert)
		{
			WLog_ERR(TAG, "error allocating pemCert");
			return -1;
		}

		status = BIO_read(bio, pemCert, length);

		if (status < 0)
		{
			WLog_ERR(TAG, "failed to read certificate");
			return -1;
		}

		offset += status;

		while (offset >= length)
		{
			int new_len;
			BYTE* new_cert;
			new_len = length * 2;
			new_cert = (BYTE*) realloc(pemCert, new_len + 1);

			if (!new_cert)
				return -1;

			length = new_len;
			pemCert = new_cert;
			status = BIO_read(bio, &pemCert[offset], length);

			if (status < 0)
				break;

			offset += status;
		}

		if (status < 0)
		{
			WLog_ERR(TAG, "failed to read certificate");
			return -1;
		}

		length = offset;
		pemCert[length] = '\0';
		status = -1;

		if (instance->VerifyX509Certificate)
			status = instance->VerifyX509Certificate(instance, pemCert, length, hostname,
			         port, tls->isGatewayTransport);
		else
			WLog_ERR(TAG, "No VerifyX509Certificate callback registered!");

		free(pemCert);
		BIO_free(bio);

		if (status < 0)
		{
			WLog_ERR(TAG, "VerifyX509Certificate failed: (length = %d) status: [%d] %s",
			         length, status, pemCert);
			return -1;
		}

		return (status == 0) ? 0 : 1;
	}

	/* ignore certificate verification if user explicitly required it (discouraged) */
	if (tls->settings->IgnoreCertificate)
		return 1;  /* success! */

	/* if user explicitly specified a certificate name, use it instead of the hostname */
	if (tls->settings->CertificateName)
		hostname = tls->settings->CertificateName;

	/* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
	certificate_status = x509_verify_certificate(cert,
	                     tls->certificate_store->path);
	/* verify certificate name match */
	certificate_data = crypto_get_certificate_data(cert->px509, hostname, port);
	/* extra common name and alternative names */
	common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length);
	alt_names = crypto_cert_subject_alt_name(cert->px509, &alt_names_count,
	            &alt_names_lengths);

	/* compare against common name */

	if (common_name)
	{
		if (tls_match_hostname(common_name, common_name_length, hostname))
			hostname_match = TRUE;
	}

	/* compare against alternative names */

	if (alt_names)
	{
		for (index = 0; index < alt_names_count; index++)
		{
			if (tls_match_hostname(alt_names[index], alt_names_lengths[index], hostname))
			{
				hostname_match = TRUE;
				break;
			}
		}
	}

	/* if the certificate is valid and the certificate name matches, verification succeeds */
	if (certificate_status && hostname_match)
		verification_status = TRUE; /* success! */

	/* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */
	if (!certificate_status || !hostname_match)
	{
		char* issuer;
		char* subject;
		char* fingerprint;
		freerdp* instance = (freerdp*) tls->settings->instance;
		DWORD accept_certificate = 0;
		issuer = crypto_cert_issuer(cert->px509);
		subject = crypto_cert_subject(cert->px509);
		fingerprint = crypto_cert_fingerprint(cert->px509);
		/* search for matching entry in known_hosts file */
		match = certificate_data_match(tls->certificate_store, certificate_data);

		if (match == 1)
		{
			/* no entry was found in known_hosts file, prompt user for manual verification */
			if (!hostname_match)
				tls_print_certificate_name_mismatch_error(
				    hostname, port,
				    common_name, alt_names,
				    alt_names_count);

			/* Automatically accept certificate on first use */
			if (tls->settings->AutoAcceptCertificate)
			{
				WLog_INFO(TAG, "No certificate stored, automatically accepting.");
				accept_certificate = 1;
			}
			else if (instance->VerifyCertificate)
			{
				accept_certificate = instance->VerifyCertificate(
				                         instance, common_name,
				                         subject, issuer,
				                         fingerprint, !hostname_match);
			}

			switch (accept_certificate)
			{
				case 1:
					/* user accepted certificate, add entry in known_hosts file */
					verification_status = certificate_data_print(tls->certificate_store,
					                      certificate_data);
					break;

				case 2:
					/* user did accept temporaty, do not add to known hosts file */
					verification_status = TRUE;
					break;

				default:
					/* user did not accept, abort and do not add entry in known_hosts file */
					verification_status = FALSE; /* failure! */
					break;
			}
		}
		else if (match == -1)
		{
			char* old_subject = NULL;
			char* old_issuer = NULL;
			char* old_fingerprint = NULL;
			/* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */
			tls_print_certificate_error(hostname, port, fingerprint,
			                            tls->certificate_store->file);

			if (!certificate_get_stored_data(tls->certificate_store,
			                                 certificate_data, &old_subject,
			                                 &old_issuer, &old_fingerprint))
				WLog_WARN(TAG, "Failed to get certificate entry for %s:%d",
				          hostname, port);

			if (instance->VerifyChangedCertificate)
			{
				accept_certificate = instance->VerifyChangedCertificate(
				                         instance, common_name, subject, issuer,
				                         fingerprint, old_subject, old_issuer,
				                         old_fingerprint);
			}

			free(old_subject);
			free(old_issuer);
			free(old_fingerprint);

			switch (accept_certificate)
			{
				case 1:
					/* user accepted certificate, add entry in known_hosts file */
					verification_status = certificate_data_replace(tls->certificate_store,
					                      certificate_data);
					break;

				case 2:
					/* user did accept temporaty, do not add to known hosts file */
					verification_status = TRUE;
					break;

				default:
					/* user did not accept, abort and do not add entry in known_hosts file */
					verification_status = FALSE; /* failure! */
					break;
			}
		}
		else if (match == 0)
			verification_status = TRUE; /* success! */

		free(issuer);
		free(subject);
		free(fingerprint);
	}

	certificate_data_free(certificate_data);
	free(common_name);

	if (alt_names)
		crypto_cert_subject_alt_name_free(alt_names_count, alt_names_lengths,
		                                  alt_names);

	return (verification_status == 0) ? 0 : 1;
}
Exemple #2
0
int tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname,
                           int port)
{
	int match;
	int index;
	char* common_name = NULL;
	int common_name_length = 0;
	char** dns_names = 0;
	int dns_names_count = 0;
	int* dns_names_lengths = NULL;
	BOOL certificate_status;
	BOOL hostname_match = FALSE;
	BOOL verification_status = FALSE;
	rdpCertificateData* certificate_data;
	freerdp* instance = (freerdp*) tls->settings->instance;
	DWORD length;
	BYTE* pemCert;

	if (!tls_extract_pem(cert, &pemCert, &length))
		return -1;

	/* Check, if we already accepted this key. */
	if (is_accepted(tls, pemCert, length))
	{
		free(pemCert);
		return 1;
	}

	if (tls->settings->ExternalCertificateManagement)
	{
		int status = -1;

		if (instance->VerifyX509Certificate)
			status = instance->VerifyX509Certificate(instance, pemCert, length, hostname,
			         port, tls->isGatewayTransport | is_redirected(tls) ? 2 : 0);
		else
			WLog_ERR(TAG, "No VerifyX509Certificate callback registered!");

		if (status > 0)
		{
			accept_cert(tls, pemCert, length);
		}
		else if (status < 0)
		{
			WLog_ERR(TAG, "VerifyX509Certificate failed: (length = %d) status: [%d] %s",
			         length, status, pemCert);
			free(pemCert);
			return -1;
		}
		else
			free(pemCert);

		return (status == 0) ? 0 : 1;
	}

	/* ignore certificate verification if user explicitly required it (discouraged) */
	if (tls->settings->IgnoreCertificate)
	{
		free(pemCert);
		return 1;  /* success! */
	}

	if (!tls->isGatewayTransport && tls->settings->AuthenticationLevel == 0)
	{
		free(pemCert);
		return 1;  /* success! */
	}

	/* if user explicitly specified a certificate name, use it instead of the hostname */
	if (!tls->isGatewayTransport && tls->settings->CertificateName)
		hostname = tls->settings->CertificateName;

	/* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
	certificate_status = x509_verify_certificate(cert,
	                     tls->certificate_store->path);
	/* verify certificate name match */
	certificate_data = crypto_get_certificate_data(cert->px509, hostname, port);
	/* extra common name and alternative names */
	common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length);
	dns_names = crypto_cert_get_dns_names(cert->px509, &dns_names_count,
	                                      &dns_names_lengths);

	/* compare against common name */

	if (common_name)
	{
		if (tls_match_hostname(common_name, common_name_length, hostname))
			hostname_match = TRUE;
	}

	/* compare against alternative names */

	if (dns_names)
	{
		for (index = 0; index < dns_names_count; index++)
		{
			if (tls_match_hostname(dns_names[index], dns_names_lengths[index], hostname))
			{
				hostname_match = TRUE;
				break;
			}
		}
	}

	/* if the certificate is valid and the certificate name matches, verification succeeds */
	if (certificate_status && hostname_match)
		verification_status = TRUE; /* success! */

	/* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */
	if (!certificate_status || !hostname_match)
	{
		char* issuer;
		char* subject;
		char* fingerprint;
		DWORD accept_certificate = 0;
		issuer = crypto_cert_issuer(cert->px509);
		subject = crypto_cert_subject(cert->px509);
		fingerprint = crypto_cert_fingerprint(cert->px509);
		/* search for matching entry in known_hosts file */
		match = certificate_data_match(tls->certificate_store, certificate_data);

		if (match == 1)
		{
			/* no entry was found in known_hosts file, prompt user for manual verification */
			if (!hostname_match)
				tls_print_certificate_name_mismatch_error(
				    hostname, port,
				    common_name, dns_names,
				    dns_names_count);

			/* Automatically accept certificate on first use */
			if (tls->settings->AutoAcceptCertificate)
			{
				WLog_INFO(TAG, "No certificate stored, automatically accepting.");
				accept_certificate = 1;
			}
			else if (instance->VerifyCertificate)
			{
				accept_certificate = instance->VerifyCertificate(
				                         instance, common_name,
				                         subject, issuer,
				                         fingerprint, !hostname_match);
			}

			switch (accept_certificate)
			{
				case 1:
					/* user accepted certificate, add entry in known_hosts file */
					verification_status = certificate_data_print(tls->certificate_store,
					                      certificate_data);
					break;

				case 2:
					/* user did accept temporaty, do not add to known hosts file */
					verification_status = TRUE;
					break;

				default:
					/* user did not accept, abort and do not add entry in known_hosts file */
					verification_status = FALSE; /* failure! */
					break;
			}
		}
		else if (match == -1)
		{
			char* old_subject = NULL;
			char* old_issuer = NULL;
			char* old_fingerprint = NULL;
			/* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */
			tls_print_certificate_error(hostname, port, fingerprint,
			                            tls->certificate_store->file);

			if (!certificate_get_stored_data(tls->certificate_store,
			                                 certificate_data, &old_subject,
			                                 &old_issuer, &old_fingerprint))
				WLog_WARN(TAG, "Failed to get certificate entry for %s:%d",
				          hostname, port);

			if (instance->VerifyChangedCertificate)
			{
				accept_certificate = instance->VerifyChangedCertificate(
				                         instance, common_name, subject, issuer,
				                         fingerprint, old_subject, old_issuer,
				                         old_fingerprint);
			}

			free(old_subject);
			free(old_issuer);
			free(old_fingerprint);

			switch (accept_certificate)
			{
				case 1:
					/* user accepted certificate, add entry in known_hosts file */
					verification_status = certificate_data_replace(tls->certificate_store,
					                      certificate_data);
					break;

				case 2:
					/* user did accept temporaty, do not add to known hosts file */
					verification_status = TRUE;
					break;

				default:
					/* user did not accept, abort and do not add entry in known_hosts file */
					verification_status = FALSE; /* failure! */
					break;
			}
		}
		else if (match == 0)
			verification_status = TRUE; /* success! */

		free(issuer);
		free(subject);
		free(fingerprint);
	}

	certificate_data_free(certificate_data);
	free(common_name);

	if (dns_names)
		crypto_cert_dns_names_free(dns_names_count, dns_names_lengths,
		                           dns_names);

	if (verification_status > 0)
	{
		accept_cert(tls, pemCert, length);
	}
	else
	{
		free(pemCert);
	}

	return (verification_status == 0) ? 0 : 1;
}
Exemple #3
0
boolean tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname)
{
	int match;
	int index;
	char* common_name = NULL;
	int common_name_length = 0;
	char** alt_names = NULL;
	int alt_names_count = 0;
	int* alt_names_lengths = NULL;
	boolean certificate_status;
	boolean hostname_match = false;
	boolean verification_status = false;
	rdpCertificateData* certificate_data;

	/* ignore certificate verification if user explicitly required it (discouraged) */
	if (tls->settings->ignore_certificate)
		return true;  /* success! */

	/* if user explicitly specified a certificate name, use it instead of the hostname */
	if (tls->settings->certificate_name)
		hostname = tls->settings->certificate_name;

	/* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
	certificate_status = x509_verify_certificate(cert, tls->certificate_store->path);

	/* verify certificate name match */
	certificate_data = crypto_get_certificate_data(cert->px509, hostname);

	/* extra common name and alternative names */
	common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length);
	alt_names = crypto_cert_subject_alt_name(cert->px509, &alt_names_count, &alt_names_lengths);

	/* compare against common name */

	if (common_name != NULL)
	{
		if (strlen(hostname) == common_name_length)
		{
			if (memcmp((void*) hostname, (void*) common_name, common_name_length) == 0)
				hostname_match = true;
		}
	}

	/* compare against alternative names */

	if (alt_names != NULL)
	{
		for (index = 0; index < alt_names_count; index++)
		{
			if (strlen(hostname) == alt_names_lengths[index])
			{
				if (memcmp((void*) hostname, (void*) alt_names[index], alt_names_lengths[index]) == 0)
					hostname_match = true;
			}
		}
	}

	/* if the certificate is valid and the certificate name matches, verification succeeds */
	if (certificate_status && hostname_match)
	{
		if (common_name)
		{
			xfree(common_name);
			common_name = NULL;
		}

		verification_status = true; /* success! */
	}

	/* if the certificate is valid but the certificate name does not match, warn user, do not accept */
	if (certificate_status && !hostname_match)
		tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count);

	/* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */

	if (!certificate_status)
	{
		char* issuer;
		char* subject;
		char* fingerprint;
		freerdp* instance = (freerdp*) tls->settings->instance;
		boolean accept_certificate = false;

		issuer = crypto_cert_issuer(cert->px509);
		subject = crypto_cert_subject(cert->px509);
		fingerprint = crypto_cert_fingerprint(cert->px509);

		/* search for matching entry in known_hosts file */
		match = certificate_data_match(tls->certificate_store, certificate_data);

		if (match == 1)
		{
			/* no entry was found in known_hosts file, prompt user for manual verification */
			if (!hostname_match)
				tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count);

			if (instance->VerifyCertificate)
				accept_certificate = instance->VerifyCertificate(instance, subject, issuer, fingerprint);

			if (!accept_certificate)
			{
				/* user did not accept, abort and do not add entry in known_hosts file */
				verification_status = false; /* failure! */
			}
			else
			{
				/* user accepted certificate, add entry in known_hosts file */
				certificate_data_print(tls->certificate_store, certificate_data);
				verification_status = true; /* success! */
			}
		}
		else if (match == -1)
		{
			/* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */
			tls_print_certificate_error(hostname, fingerprint);
			
			if (instance->VerifyChangedCertificate)
				accept_certificate = instance->VerifyChangedCertificate(instance, subject, issuer, fingerprint, "");

			if (!accept_certificate)
			{
				/* user did not accept, abort and do not change known_hosts file */
				verification_status = false;  /* failure! */
			}
			else
			{
				/* user accepted new certificate, add replace fingerprint for this host in known_hosts file */
				certificate_data_replace(tls->certificate_store, certificate_data);
				verification_status = true; /* success! */
			}
		}
		else if (match == 0)
		{
			verification_status = true; /* success! */
		}

		xfree(issuer);
		xfree(subject);
		xfree(fingerprint);
	}

	if (certificate_data)
	{
		xfree(certificate_data->fingerprint);
		xfree(certificate_data->hostname);
		xfree(certificate_data);
	}

	return verification_status;
}
Exemple #4
0
BOOL tls_verify_certificate(rdpTls* tls, CryptoCert cert, char* hostname, int port)
{
    int match;
    int index;
    char* common_name = NULL;
    int common_name_length = 0;
    char** alt_names = NULL;
    int alt_names_count = 0;
    int* alt_names_lengths = NULL;
    BOOL certificate_status;
    BOOL hostname_match = FALSE;
    BOOL verification_status = FALSE;
    rdpCertificateData* certificate_data;

    if (tls->settings->ExternalCertificateManagement)
    {
        BIO* bio;
        int status;
        int length;
        int offset;
        BYTE* pemCert;
        freerdp* instance = (freerdp*) tls->settings->instance;

        /**
         * Don't manage certificates internally, leave it up entirely to the external client implementation
         */

        bio = BIO_new(BIO_s_mem());

        if (!bio)
        {
            fprintf(stderr, "tls_verify_certificate: BIO_new() failure\n");
            return FALSE;
        }

        status = PEM_write_bio_X509(bio, cert->px509);

        if (status < 0)
        {
            fprintf(stderr, "tls_verify_certificate: PEM_write_bio_X509 failure: %d\n", status);
            return FALSE;
        }

        offset = 0;
        length = 2048;
        pemCert = (BYTE*) malloc(length + 1);

        status = BIO_read(bio, pemCert, length);

        if (status < 0)
        {
            fprintf(stderr, "tls_verify_certificate: failed to read certificate\n");
            return FALSE;
        }

        offset += status;

        while (offset >= length)
        {
            length *= 2;
            pemCert = (BYTE*) realloc(pemCert, length + 1);

            status = BIO_read(bio, &pemCert[offset], length);

            if (status < 0)
                break;

            offset += status;
        }

        if (status < 0)
        {
            fprintf(stderr, "tls_verify_certificate: failed to read certificate\n");
            return FALSE;
        }

        length = offset;
        pemCert[length] = '\0';

        status = -1;

        if (instance->VerifyX509Certificate)
        {
            status = instance->VerifyX509Certificate(instance, pemCert, length, hostname, port, 0);
        }

        fprintf(stderr, "VerifyX509Certificate: (length = %d) status: %d\n%s\n",
                length, status, pemCert);

        free(pemCert);
        BIO_free(bio);

        return (status < 0) ? FALSE : TRUE;
    }

    /* ignore certificate verification if user explicitly required it (discouraged) */
    if (tls->settings->IgnoreCertificate)
        return TRUE;  /* success! */

    /* if user explicitly specified a certificate name, use it instead of the hostname */
    if (tls->settings->CertificateName)
        hostname = tls->settings->CertificateName;

    /* attempt verification using OpenSSL and the ~/.freerdp/certs certificate store */
    certificate_status = x509_verify_certificate(cert, tls->certificate_store->path);

    /* verify certificate name match */
    certificate_data = crypto_get_certificate_data(cert->px509, hostname);

    /* extra common name and alternative names */
    common_name = crypto_cert_subject_common_name(cert->px509, &common_name_length);
    alt_names = crypto_cert_subject_alt_name(cert->px509, &alt_names_count, &alt_names_lengths);

    /* compare against common name */

    if (common_name != NULL)
    {
        if (tls_match_hostname(common_name, common_name_length, hostname))
            hostname_match = TRUE;
    }

    /* compare against alternative names */

    if (alt_names != NULL)
    {
        for (index = 0; index < alt_names_count; index++)
        {
            if (tls_match_hostname(alt_names[index], alt_names_lengths[index], hostname))
            {
                hostname_match = TRUE;
                break;
            }
        }
    }

    /* if the certificate is valid and the certificate name matches, verification succeeds */
    if (certificate_status && hostname_match)
    {
        if (common_name)
        {
            free(common_name);
            common_name = NULL;
        }

        verification_status = TRUE; /* success! */
    }

    /* if the certificate is valid but the certificate name does not match, warn user, do not accept */
    if (certificate_status && !hostname_match)
        tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count);

    /* verification could not succeed with OpenSSL, use known_hosts file and prompt user for manual verification */

    if (!certificate_status)
    {
        char* issuer;
        char* subject;
        char* fingerprint;
        freerdp* instance = (freerdp*) tls->settings->instance;
        BOOL accept_certificate = FALSE;

        issuer = crypto_cert_issuer(cert->px509);
        subject = crypto_cert_subject(cert->px509);
        fingerprint = crypto_cert_fingerprint(cert->px509);

        /* search for matching entry in known_hosts file */
        match = certificate_data_match(tls->certificate_store, certificate_data);

        if (match == 1)
        {
            /* no entry was found in known_hosts file, prompt user for manual verification */
            if (!hostname_match)
                tls_print_certificate_name_mismatch_error(hostname, common_name, alt_names, alt_names_count);

            if (instance->VerifyCertificate)
                accept_certificate = instance->VerifyCertificate(instance, subject, issuer, fingerprint);

            if (!accept_certificate)
            {
                /* user did not accept, abort and do not add entry in known_hosts file */
                verification_status = FALSE; /* failure! */
            }
            else
            {
                /* user accepted certificate, add entry in known_hosts file */
                certificate_data_print(tls->certificate_store, certificate_data);
                verification_status = TRUE; /* success! */
            }
        }
        else if (match == -1)
        {
            /* entry was found in known_hosts file, but fingerprint does not match. ask user to use it */
            tls_print_certificate_error(hostname, fingerprint, tls->certificate_store->file);

            if (instance->VerifyChangedCertificate)
                accept_certificate = instance->VerifyChangedCertificate(instance, subject, issuer, fingerprint, "");

            if (!accept_certificate)
            {
                /* user did not accept, abort and do not change known_hosts file */
                verification_status = FALSE;  /* failure! */
            }
            else
            {
                /* user accepted new certificate, add replace fingerprint for this host in known_hosts file */
                certificate_data_replace(tls->certificate_store, certificate_data);
                verification_status = TRUE; /* success! */
            }
        }
        else if (match == 0)
        {
            verification_status = TRUE; /* success! */
        }

        free(issuer);
        free(subject);
        free(fingerprint);
    }

    if (certificate_data)
    {
        free(certificate_data->fingerprint);
        free(certificate_data->hostname);
        free(certificate_data);
    }

#ifndef _WIN32
    if (common_name)
        free(common_name);
#endif

    if (alt_names)
        crypto_cert_subject_alt_name_free(alt_names_count, alt_names_lengths,
                                          alt_names);

    return verification_status;
}