/* 
   Unix SMB/CIFS implementation.
   FS info functions
   Copyright (C) Stefan (metze) Metzmacher	2003
   
   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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"

/****************************************************************************
 Get UNIX extensions version info.
****************************************************************************/
                                                                                                                   
BOOL cli_unix_extensions_version(struct cli_state *cli, uint16 *pmajor, uint16 *pminor,
                                        uint32 *pcaplow, uint32 *pcaphigh)
{
	BOOL ret = False;
	uint16 setup;
	char param[2];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;

	setup = TRANSACT2_QFSINFO;
	
	SSVAL(param,0,SMB_QUERY_CIFS_UNIX_INFO);

	if (!cli_send_trans(cli, SMBtrans2, 
		    NULL, 
		    0, 0,
		    &setup, 1, 0,
		    param, 2, 0,
		    NULL, 0, 560)) {
		goto cleanup;
	}
	
	if (!cli_receive_trans(cli, SMBtrans2,
                              &rparam, &rparam_count,
                              &rdata, &rdata_count)) {
		goto cleanup;
	}

	if (cli_is_error(cli)) {
		ret = False;
		goto cleanup;
	} else {
		ret = True;
	}

	if (rdata_count < 12) {
		goto cleanup;
	}

	*pmajor = SVAL(rdata,0);
	*pminor = SVAL(rdata,2);
	*pcaplow = IVAL(rdata,4);
	*pcaphigh = IVAL(rdata,8);

	/* todo: but not yet needed 
	 *       return the other stuff
	 */

cleanup:
	SAFE_FREE(rparam);
	SAFE_FREE(rdata);

	return ret;	
}

/****************************************************************************
 Set UNIX extensions capabilities.
****************************************************************************/
                                                                                                                   
BOOL cli_set_unix_extensions_capabilities(struct cli_state *cli, uint16 major, uint16 minor,
                                        uint32 caplow, uint32 caphigh)
{
	BOOL ret = False;
	uint16 setup;
	char param[4];
	char data[12];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;

	setup = TRANSACT2_SETFSINFO;
	
	SSVAL(param,0,0);
	SSVAL(param,2,SMB_SET_CIFS_UNIX_INFO);

	SSVAL(data,0,major);
	SSVAL(data,2,minor);
	SIVAL(data,4,caplow);
	SIVAL(data,8,caphigh);

	if (!cli_send_trans(cli, SMBtrans2, 
		    NULL, 
		    0, 0,
		    &setup, 1, 0,
		    param, 4, 0,
		    data, 12, 560)) {
		goto cleanup;
	}
	
	if (!cli_receive_trans(cli, SMBtrans2,
                              &rparam, &rparam_count,
                              &rdata, &rdata_count)) {
		goto cleanup;
	}

	if (cli_is_error(cli)) {
		ret = False;
		goto cleanup;
	} else {
		ret = True;
	}

cleanup:
	SAFE_FREE(rparam);
	SAFE_FREE(rdata);

	return ret;	
}

BOOL cli_get_fs_attr_info(struct cli_state *cli, uint32 *fs_attr)
{
	BOOL ret = False;
	uint16 setup;
	char param[2];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;

	if (!cli||!fs_attr)
		smb_panic("cli_get_fs_attr_info() called with NULL Pionter!");

	setup = TRANSACT2_QFSINFO;
	
	SSVAL(param,0,SMB_QUERY_FS_ATTRIBUTE_INFO);

	if (!cli_send_trans(cli, SMBtrans2, 
		    NULL, 
		    0, 0,
		    &setup, 1, 0,
		    param, 2, 0,
		    NULL, 0, 560)) {
		goto cleanup;
	}
	
	if (!cli_receive_trans(cli, SMBtrans2,
                              &rparam, &rparam_count,
                              &rdata, &rdata_count)) {
		goto cleanup;
	}

	if (cli_is_error(cli)) {
		ret = False;
		goto cleanup;
	} else {
		ret = True;
	}

	if (rdata_count < 12) {
		goto cleanup;
	}

	*fs_attr = IVAL(rdata,0);

	/* todo: but not yet needed 
	 *       return the other stuff
	 */

cleanup:
	SAFE_FREE(rparam);
	SAFE_FREE(rdata);

	return ret;	
}

BOOL cli_get_fs_volume_info_old(struct cli_state *cli, fstring volume_name, uint32 *pserial_number)
{
	BOOL ret = False;
	uint16 setup;
	char param[2];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;
	unsigned char nlen;

	setup = TRANSACT2_QFSINFO;
	
	SSVAL(param,0,SMB_INFO_VOLUME);

	if (!cli_send_trans(cli, SMBtrans2, 
		    NULL, 
		    0, 0,
		    &setup, 1, 0,
		    param, 2, 0,
		    NULL, 0, 560)) {
		goto cleanup;
	}
	
	if (!cli_receive_trans(cli, SMBtrans2,
                              &rparam, &rparam_count,
                              &rdata, &rdata_count)) {
		goto cleanup;
	}

	if (cli_is_error(cli)) {
		ret = False;
		goto cleanup;
	} else {
		ret = True;
	}

	if (rdata_count < 5) {
		goto cleanup;
	}

	if (pserial_number) {
		*pserial_number = IVAL(rdata,0);
	}
	nlen = CVAL(rdata,l2_vol_cch);
	clistr_pull(cli, volume_name, rdata + l2_vol_szVolLabel, sizeof(fstring), nlen, STR_NOALIGN);

	/* todo: but not yet needed 
	 *       return the other stuff
	 */

cleanup:
	SAFE_FREE(rparam);
	SAFE_FREE(rdata);

	return ret;	
}

BOOL cli_get_fs_volume_info(struct cli_state *cli, fstring volume_name, uint32 *pserial_number, time_t *pdate)
{
	BOOL ret = False;
	uint16 setup;
	char param[2];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;
	unsigned int nlen;

	setup = TRANSACT2_QFSINFO;
	
	SSVAL(param,0,SMB_QUERY_FS_VOLUME_INFO);

	if (!cli_send_trans(cli, SMBtrans2, 
		    NULL, 
		    0, 0,
		    &setup, 1, 0,
		    param, 2, 0,
		    NULL, 0, 560)) {
		goto cleanup;
	}
	
	if (!cli_receive_trans(cli, SMBtrans2,
                              &rparam, &rparam_count,
                              &rdata, &rdata_count)) {
		goto cleanup;
	}

	if (cli_is_error(cli)) {
		ret = False;
		goto cleanup;
	} else {
		ret = True;
	}

	if (rdata_count < 19) {
		goto cleanup;
	}

	if (pdate) {
		struct timespec ts;
		ts = interpret_long_date(rdata);
		*pdate = ts.tv_sec;
	}
	if (pserial_number) {
		*pserial_number = IVAL(rdata,8);
	}
	nlen = IVAL(rdata,12);
	clistr_pull(cli, volume_name, rdata + 18, sizeof(fstring), nlen, STR_UNICODE);

	/* todo: but not yet needed 
	 *       return the other stuff
	 */

cleanup:
	SAFE_FREE(rparam);
	SAFE_FREE(rdata);

	return ret;	
}

/******************************************************************************
 Send/receive the request encryption blob.
******************************************************************************/

static NTSTATUS enc_blob_send_receive(struct cli_state *cli, DATA_BLOB *in, DATA_BLOB *out, DATA_BLOB *param_out)
{
	uint16 setup;
	char param[4];
	char *rparam=NULL, *rdata=NULL;
	unsigned int rparam_count=0, rdata_count=0;
	NTSTATUS status = NT_STATUS_OK;

	setup = TRANSACT2_SETFSINFO;

	SSVAL(param,0,0);
	SSVAL(param,2,SMB_REQUEST_TRANSPORT_ENCRYPTION);

	if (!cli_send_trans(cli, SMBtrans2,
				NULL,
				0, 0,
				&setup, 1, 0,
				param, 4, 0,
				(char *)in->data, in->length, CLI_BUFFER_SIZE)) {
		status = cli_nt_error(cli);
		goto out;
	}

	if (!cli_receive_trans(cli, SMBtrans2,
				&rparam, &rparam_count,
				&rdata, &rdata_count)) {
		status = cli_nt_error(cli);
		goto out;
	}

	if (cli_is_error(cli)) {
		status = cli_nt_error(cli);
		if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
			goto out;
		}
	}

	*out = data_blob(rdata, rdata_count);
	*param_out = data_blob(rparam, rparam_count);

  out:

	SAFE_FREE(rparam);
	SAFE_FREE(rdata);
	return status;
}

/******************************************************************************
 Start a raw ntlmssp encryption.
******************************************************************************/

NTSTATUS cli_raw_ntlm_smb_encryption_start(struct cli_state *cli, 
				const char *user,
				const char *pass,
				const char *domain)
{
	DATA_BLOB blob_in = data_blob(NULL, 0);
	DATA_BLOB blob_out = data_blob(NULL, 0);
	DATA_BLOB param_out = data_blob(NULL, 0);
	NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
	struct smb_trans_enc_state *es = NULL;

	es = SMB_MALLOC_P(struct smb_trans_enc_state);
	if (!es) {
		return NT_STATUS_NO_MEMORY;
	}
	ZERO_STRUCTP(es);
	es->smb_enc_type = SMB_TRANS_ENC_NTLM;
	status = ntlmssp_client_start(&es->s.ntlmssp_state);
	if (!NT_STATUS_IS_OK(status)) {
		goto fail;
	}

	ntlmssp_want_feature(es->s.ntlmssp_state, NTLMSSP_FEATURE_SESSION_KEY);
	es->s.ntlmssp_state->neg_flags |= (NTLMSSP_NEGOTIATE_SIGN|NTLMSSP_NEGOTIATE_SEAL);

	if (!NT_STATUS_IS_OK(status = ntlmssp_set_username(es->s.ntlmssp_state, user))) {
		goto fail;
	}
	if (!NT_STATUS_IS_OK(status = ntlmssp_set_domain(es->s.ntlmssp_state, domain))) {
		goto fail;
	}
	if (!NT_STATUS_IS_OK(status = ntlmssp_set_password(es->s.ntlmssp_state, pass))) {
		goto fail;
	}

	do {
		status = ntlmssp_update(es->s.ntlmssp_state, blob_in, &blob_out);
		data_blob_free(&blob_in);
		data_blob_free(&param_out);
		if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) || NT_STATUS_IS_OK(status)) {
			status = enc_blob_send_receive(cli, &blob_out, &blob_in, &param_out);
		}
		if (param_out.length == 2) {
			es->enc_ctx_num = SVAL(param_out.data, 0);
		}
		data_blob_free(&blob_out);
	} while (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED));

	data_blob_free(&blob_in);

	if (NT_STATUS_IS_OK(status)) {
		/* Replace the old state, if any. */
		if (cli->trans_enc_state) {
			common_free_encryption_state(&cli->trans_enc_state);
		}
		cli->trans_enc_state = es;
		cli->trans_enc_state->enc_on = True;
		es = NULL;
	}

  fail:

	common_free_encryption_state(&es);
	return status;
}