/*****************************************************************************
 * tls.c
 *****************************************************************************
 * Copyright © 2004-2007 Rémi Denis-Courmont
 * $Id: 11e3dd2a24861ce817b2de1bd85aed9ba9252aa8 $
 *
 * Authors: Rémi Denis-Courmont <rem # videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/**
 * @file
 * libvlc interface to the Transport Layer Security (TLS) plugins.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <vlc_common.h>
#include "libvlc.h"

#include <vlc_tls.h>

/**
 * Allocates a whole server's TLS credentials.
 *
 * @param cert_path required (Unicode) path to an x509 certificate,
 *                  if NULL, anonymous key exchange will be used.
 * @param key_path (UTF-8) path to the PKCS private key for the certificate,
 *                 if NULL; cert_path will be used.
 *
 * @return NULL on error.
 */
tls_server_t *
tls_ServerCreate (vlc_object_t *obj, const char *cert_path,
                  const char *key_path)
{
    tls_server_t *srv;

    srv = (tls_server_t *)vlc_custom_create (obj, sizeof (*srv),
                                             VLC_OBJECT_GENERIC,
                                             "tls server");
    if (srv == NULL)
        return NULL;

    var_Create (srv, "tls-x509-cert", VLC_VAR_STRING);
    var_Create (srv, "tls-x509-key", VLC_VAR_STRING);

    if (cert_path != NULL)
    {
        var_SetString (srv, "tls-x509-cert", cert_path);

        if (key_path == NULL)
            key_path = cert_path;
        var_SetString (srv, "tls-x509-key", key_path);
    }

    srv->p_module = module_Need (srv, "tls server", 0, 0);
    if (srv->p_module == NULL)
    {
        msg_Err (srv, "TLS server plugin not available");
        vlc_object_release (srv);
        return NULL;
    }

    vlc_object_attach (srv, obj);
    msg_Dbg (srv, "TLS server plugin initialized");
    return srv;
}


/**
 * Releases data allocated with tls_ServerCreate.
 * @param srv TLS server object to be destroyed, or NULL
 */
void tls_ServerDelete (tls_server_t *srv)
{
    if (srv == NULL)
        return;

    module_Unneed (srv, srv->p_module);
    vlc_object_detach (srv);
    vlc_object_release (srv);
}


/**
 * Adds one or more certificate authorities from a file.
 * @return -1 on error, 0 on success.
 */
int tls_ServerAddCA (tls_server_t *srv, const char *path)
{
    return srv->pf_add_CA (srv, path);
}


/**
 * Adds one or more certificate revocation list from a file.
 * @return -1 on error, 0 on success.
 */
int tls_ServerAddCRL (tls_server_t *srv, const char *path)
{
    return srv->pf_add_CRL (srv, path);
}


tls_session_t *tls_ServerSessionPrepare (tls_server_t *srv)
{
    tls_session_t *ses;

    ses = srv->pf_open (srv);
    if (ses == NULL)
        return NULL;

    vlc_object_attach (ses, srv);
    return ses;
}


void tls_ServerSessionClose (tls_session_t *ses)
{
    tls_server_t *srv = (tls_server_t *)(ses->p_parent);
    srv->pf_close (srv, ses);
}


int tls_ServerSessionHandshake (tls_session_t *ses, int fd)
{
    ses->pf_set_fd (ses, fd);
    return 2;
}


int tls_SessionContinueHandshake (tls_session_t *ses)
{
    int val = ses->pf_handshake (ses);
    if (val < 0)
        tls_ServerSessionClose (ses);
    return val;
}


/**
 * Allocates a client's TLS credentials and shakes hands through the network.
 * This is a blocking network operation.
 *
 * @param fd stream socket through which to establish the secure communication
 * layer.
 * @param psz_hostname Server Name Indication to pass to the server, or NULL.
 *
 * @return NULL on error.
 **/
tls_session_t *
tls_ClientCreate (vlc_object_t *obj, int fd, const char *psz_hostname)
{
    tls_session_t *cl;
    int val;

    cl = (tls_session_t *)vlc_custom_create (obj, sizeof (*cl),
                                             VLC_OBJECT_GENERIC,
                                             "tls client");
    if (cl == NULL)
        return NULL;

    var_Create (cl, "tls-server-name", VLC_VAR_STRING);
    if (psz_hostname != NULL)
    {
        msg_Dbg (cl, "requested server name: %s", psz_hostname);
        var_SetString (cl, "tls-server-name", psz_hostname);
    }
    else
        msg_Dbg (cl, "requested anonymous server");

    cl->p_module = module_Need (cl, "tls client", 0, 0);
    if (cl->p_module == NULL)
    {
        msg_Err (cl, "TLS client plugin not available");
        vlc_object_release (cl);
        return NULL;
    }

    cl->pf_set_fd (cl, fd);

    do
        val = cl->pf_handshake (cl);
    while (val > 0);

    if (val == 0)
    {
        msg_Dbg (cl, "TLS client session initialized");
        vlc_object_attach (cl, obj);
        return cl;
    }
    msg_Err (cl, "TLS client session handshake error");

    module_Unneed (cl, cl->p_module);
    vlc_object_release (cl);
    return NULL;
}


/**
 * Releases data allocated with tls_ClientCreate.
 * It is your job to close the underlying socket.
 */
void tls_ClientDelete (tls_session_t *cl)
{
    if (cl == NULL)
        return;

    module_Unneed (cl, cl->p_module);
    vlc_object_detach (cl);
    vlc_object_release (cl);
}
