/*****************************************************************************
 *  __________________    _________  _____            _____  .__         ._.
 *  \______   \______ \  /   _____/ /     \          /  _  \ |__| ____   | |
 *   |    |  _/|    |  \ \_____  \ /  \ /  \        /  /_\  \|  _/ __ \  | |
 *   |    |   \|    `   \/        /    Y    \      /    |    |  \  ___/   \|
 *   |______  /_______  /_______  \____|__  / /\   \____|__  |__|\___ |   __
 *          \/        \/        \/        \/  )/           \/        \/   \/
 *
 * This file is part of liBDSM. Copyright © 2014-2015 VideoLabs SAS
 *
 * Author: Julien 'Lta' BALLET <contact@lta.io>
 *
 * liBDSM is released under LGPLv2.1 (or later) and is also available
 * under a commercial license.
 *****************************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.
 *****************************************************************************/

/*
 * Copyright (c) 2003-2017
 * Distributed Systems Software.  All rights reserved.
 */

#include "config.h"

#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <netinet/in.h>

#include "smb_session_msg.h"
#include "smb_fd.h"
#include "smb_utils.h"
#include "smb_file.h"

#ifndef lint
static MAYBE_UNUSED const char copyright[] =
"Copyright (c) 2003-2017\n\
Distributed Systems Software.  All rights reserved.";
static MAYBE_UNUSED const char revid[] =
  "$Id: smb_file.c 2958 2017-05-18 18:56:19Z brachman $";
#endif

int
smb_fopen(smb_session *s, smb_tid tid, const char *path, uint32_t o_flags,
		  smb_fd *fd)
{
  smb_share *share;
  smb_file *file;
  smb_message *req_msg, resp_msg;
  smb_create_req req;
  smb_create_resp *resp;
  size_t path_len;
  int res;
  char *utf_path;

  assert(s != NULL && path != NULL && fd != NULL);

  if ((share = smb_session_share_get(s, tid)) == NULL)
	return(DSM_ERROR_GENERIC);

  path_len = smb_to_utf16(path, strlen(path) + 1, &utf_path);
  if (path_len == 0)
	return(DSM_ERROR_CHARSET);

  req_msg = smb_message_new(SMB_CMD_CREATE);
  if (!req_msg) {
	free(utf_path);
	return(DSM_ERROR_GENERIC);
  }

  // Set SMB Headers
  req_msg->packet->header.tid = tid;

  // Create AndX Params
  SMB_MSG_INIT_PKT_ANDX(req);
  req.wct = 24;
  req.flags = 0;
  req.root_fid = 0;
  req.access_mask = o_flags;
  req.alloc_size = 0;
  req.file_attr = 0;
  req.share_access = SMB_SHARE_READ | SMB_SHARE_WRITE;

  if ((o_flags & SMB_MOD_RW) == SMB_MOD_RW) {
	req.disposition = SMB_DISPOSITION_FILE_SUPERSEDE; // Create if doesn't exist
	req.create_opts = SMB_CREATEOPT_WRITE_THROUGH;
  }
  else {
	req.disposition = SMB_DISPOSITION_FILE_OPEN;  // Fail if doesn't exist
	req.create_opts = 0;                          // We don't support create
  }
  req.impersonation = SMB_IMPERSONATION_SEC_IMPERSONATE;
  req.security_flags = SMB_SECURITY_NO_TRACKING;
  req.path_length = path_len;
  req.bct = path_len + 1;
  SMB_MSG_PUT_PKT(req_msg, req);

  // Create AndX 'Body'
  smb_message_put8(req_msg, 0);   // Align beginning of path
  smb_message_append(req_msg, utf_path, path_len);
  free(utf_path);

  // smb_message_put16(req_msg, 0);  // ??

  res = smb_session_send_msg(s, req_msg);
  smb_message_destroy(req_msg);
  if (!res)
	return(DSM_ERROR_NETWORK);

  if (!smb_session_recv_msg(s, &resp_msg))
	return(DSM_ERROR_NETWORK);
  if (!smb_session_check_nt_status(s, &resp_msg))
	return(DSM_ERROR_NT);

  resp = (smb_create_resp *) resp_msg.packet->payload;
  file = calloc(1, sizeof(smb_file));
  if (!file)
	return(DSM_ERROR_GENERIC);

  file->fid = resp->fid;
  file->tid = tid;
  file->created = resp->created;
  file->accessed = resp->accessed;
  file->written = resp->written;
  file->changed = resp->changed;
  file->alloc_size = resp->alloc_size;
  file->size = resp->size;
  file->attr = resp->attr;
  file->is_dir = resp->is_dir;

  smb_session_file_add(s, tid, file); // XXX Check return

  *fd = SMB_FD(tid, file->fid);
  return(DSM_SUCCESS);
}

void
smb_fclose(smb_session *s, smb_fd fd)
{
  smb_file *file;
  smb_message *msg;
  smb_close_req req;

  assert(s != NULL);
  if (!fd)
	return;

  // XXX Memory leak, destroy the file after removing it
  if ((file = smb_session_file_remove(s, fd)) == NULL)
	return;

  msg = smb_message_new(SMB_CMD_CLOSE);
  if (!msg) {
	free(file->name);
	free(file);
	return;
  }

  msg->packet->header.tid = SMB_FD_TID(fd);

  SMB_MSG_INIT_PKT(req);
  req.wct = 3;
  req.fid = SMB_FD_FID(fd);
  req.last_write = ~0;
  req.bct = 0;
  SMB_MSG_PUT_PKT(msg, req);

  // We don't check for succes or failure, since we actually don't really
  // care about creating a potentiel leak server side.
  smb_session_send_msg(s, msg);
  smb_session_recv_msg(s, 0);
  smb_message_destroy(msg);

  free(file->name);
  free(file);
}

ssize_t
smb_fread(smb_session *s, smb_fd fd, void *buf, size_t buf_size)
{
  smb_file *file;
  smb_message *req_msg, resp_msg;
  smb_read_req req;
  smb_read_resp *resp;
  size_t max_read;
  int res;

  assert(s != NULL);

  if ((file = smb_session_file_get(s, fd)) == NULL)
	return(-1);

  req_msg = smb_message_new(SMB_CMD_READ);
  if (!req_msg)
	return(-1);
  req_msg->packet->header.tid = file->tid;

  max_read = 0xffff;
  max_read = max_read < buf_size ? max_read : buf_size;

  SMB_MSG_INIT_PKT_ANDX(req);
  req.wct = 12;
  req.fid = file->fid;
  req.offset = file->offset;
  req.max_count = max_read;
  req.min_count = max_read;
  req.max_count_high = 0;
  req.remaining = 0;
  req.offset_high = (file->offset >> 32) & 0xffffffff;
  req.bct = 0;
  SMB_MSG_PUT_PKT(req_msg, req);

  res = smb_session_send_msg(s, req_msg);
  smb_message_destroy(req_msg);
  if (!res)
	return(-1);

  if (!smb_session_recv_msg(s, &resp_msg))
	return(-1);
  if (!smb_session_check_nt_status(s, &resp_msg))
	return(-1);

  resp = (smb_read_resp *)resp_msg.packet->payload;
  if (buf)
	memcpy(buf, (char *)resp_msg.packet + resp->data_offset, resp->data_len);
  smb_fseek(s, fd, resp->data_len, SEEK_CUR);

  return(resp->data_len);
}

ssize_t
smb_fwrite(smb_session *s, smb_fd fd, void *buf, size_t buf_size)
{
  smb_file *file;
  smb_message *req_msg, resp_msg;
  smb_write_req req;
  smb_write_resp *resp;
  uint16_t max_write;
  int res;

  assert(s != NULL && buf != NULL);

  file = smb_session_file_get(s, fd);
  if (file == NULL)
	return(-1);

  req_msg = smb_message_new(SMB_CMD_WRITE);
  if (!req_msg)
	return(-1);
  req_msg->packet->header.tid = (uint16_t) file->tid;

  // SMB message size shall not exceed maximum size of netbios data payload
  max_write = UINT16_MAX - sizeof(smb_packet) - sizeof(smb_write_req);
  max_write = max_write < buf_size ? max_write : (uint16_t) buf_size;

  SMB_MSG_INIT_PKT_ANDX(req);
  req.wct = 14; // Must be 14
  req.fid = file->fid;
  req.offset = file->offset & 0xffffffff;
  req.timeout = 0;
  req.write_mode = SMB_WRITEMODE_WRITETHROUGH;
  req.remaining = 0;
  req.reserved = 0;
  req.data_len = max_write;
  req.data_offset = sizeof(smb_packet) + sizeof(smb_write_req);
  req.offset_high = (file->offset >> 32) & 0xffffffff;
  req.bct = max_write;
  SMB_MSG_PUT_PKT(req_msg, req);
  smb_message_append(req_msg, buf, max_write);

  res = smb_session_send_msg(s, req_msg);
  smb_message_destroy(req_msg);
  if (!res)
	return(-1);

  if (!smb_session_recv_msg(s, &resp_msg))
	return(-1);
  if (!smb_session_check_nt_status(s, &resp_msg))
	return(-1);

  resp = (smb_write_resp *) resp_msg.packet->payload;

  smb_fseek(s, fd, resp->data_len, SEEK_CUR);

  return(resp->data_len);
}

ssize_t
smb_fseek(smb_session *s, smb_fd fd, off_t offset, int whence)
{
  smb_file  *file;

  assert(s != NULL);

  if ((file = smb_session_file_get(s, fd)) == NULL)
	return(-1);

  if (whence == SMB_SEEK_SET)
	file->offset = offset;
  else if (whence == SMB_SEEK_CUR)
	file->offset += offset;

  return(file->offset);
}

int
smb_file_rm(smb_session *s, smb_tid tid, const char *path)
{
  smb_message *req_msg, resp_msg;
  smb_file_rm_req req;
  smb_file_rm_resp *resp;
  size_t utf_pattern_len;
  char *utf_pattern;

  assert(s != NULL && path != NULL);

  utf_pattern_len = smb_to_utf16(path, strlen(path) + 1, &utf_pattern);
  if (utf_pattern_len == 0)
	return(DSM_ERROR_CHARSET);

  req_msg = smb_message_new(SMB_CMD_RMFILE);
  if (!req_msg) {
	free(utf_pattern);
	return(DSM_ERROR_GENERIC);
  }

  req_msg->packet->header.tid = (uint16_t) tid;
  req_msg->packet->header.flags2 = SMB_FLAGS2_LONG_NAMES;

  SMB_MSG_INIT_PKT(req);
  req.wct = 0x01; // Must be 1
  req.search_attributes = SMB_ATTR_HIDDEN | SMB_ATTR_SYS;
  req.bct = (uint16_t) (utf_pattern_len + 1);
  req.buffer_format = 0x04; // Must be 4
  SMB_MSG_PUT_PKT(req_msg, req);
  smb_message_append(req_msg, utf_pattern, utf_pattern_len);

  smb_session_send_msg(s, req_msg);
  smb_message_destroy(req_msg);

  free(utf_pattern);

  if (!smb_session_recv_msg(s, &resp_msg))
	return(DSM_ERROR_NETWORK);
  if (!smb_session_check_nt_status(s, &resp_msg))
	return(DSM_ERROR_NT);

  resp = (smb_file_rm_resp *) resp_msg.packet->payload;
  if ((resp->wct != 0) || (resp->bct != 0))
	return(DSM_ERROR_NETWORK);

  return(0);
}

int
smb_file_mv(smb_session *s, smb_tid tid, const char *old_path,
			const char *new_path)
{
  smb_message *req_msg, resp_msg;
  smb_file_mv_req req;
  smb_file_mv_resp *resp;
  size_t utf_old_len,utf_new_len;
  char *utf_old_path,*utf_new_path;

  assert(s != NULL && old_path != NULL && new_path != NULL);

  utf_old_len = smb_to_utf16(old_path, strlen(old_path) + 1, &utf_old_path);
  if (utf_old_len == 0)
	return(DSM_ERROR_CHARSET);

  utf_new_len = smb_to_utf16(new_path, strlen(new_path) + 1, &utf_new_path);
  if (utf_new_len == 0) {
	free(utf_old_path);
	return(DSM_ERROR_CHARSET);
  }

  req_msg = smb_message_new(SMB_CMD_MOVE);
  if (!req_msg) {
	free(utf_old_path);
	free(utf_new_path);
	return(DSM_ERROR_GENERIC);
  }

  req_msg->packet->header.tid = (uint16_t) tid;
  req_msg->packet->header.flags2 = SMB_FLAGS2_LONG_NAMES;

  SMB_MSG_INIT_PKT(req);
  req.wct = 0x01; // Must be 1
  req.search_attributes = SMB_ATTR_HIDDEN | SMB_ATTR_SYS;
  // We have 2 bytes for buffer formats and 1 padding byte
  req.bct = (uint16_t) (utf_old_len + utf_new_len + 3);
  SMB_MSG_PUT_PKT(req_msg, req);
  smb_message_put8(req_msg, 0x04); // Buffer format 1, must be 4
  smb_message_append(req_msg, utf_old_path, utf_old_len);
  smb_message_put8(req_msg, 0x00); // padding to have next byte 16 bits aligned
  smb_message_put8(req_msg, 0x04); // Buffer format 2, must be 4
  smb_message_append(req_msg, utf_new_path, utf_new_len);

  smb_session_send_msg(s, req_msg);
  smb_message_destroy(req_msg);

  free(utf_old_path);
  free(utf_new_path);

  if (!smb_session_recv_msg(s, &resp_msg))
	return(DSM_ERROR_NETWORK);

  if (!smb_session_check_nt_status(s, &resp_msg))
	return(DSM_ERROR_NT);

  resp = (smb_file_mv_resp *) resp_msg.packet->payload;
  if ((resp->wct != 0) || (resp->bct != 0))
	return(DSM_ERROR_NETWORK);

  return(DSM_SUCCESS);
}
