#!/usr/bin/python
#
#Copyright (c) 2010 Cloud Linux LTD
#All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions
#are met:
#  * Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#  * 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.
#  * The names of its contributors may not be used to endorse or
#    promote products derived from this software without specific
#    prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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
#COPYRIGHT OWNER 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.
#

# This module provides functions for secure I/O and filesystem operations

import os
from ctypes import *

libc = cdll.LoadLibrary("libc.so.6")
liblve = cdll.LoadLibrary("liblve.so.0")

# --- libc functions -----------------------------------------------

ino_t = c_long
off_t = c_long

class DIRENTRY(Structure):
	_fields_ = [
		('d_ino', ino_t),                 # inode number
		('d_off', off_t),                 # offset to the next dirent
		('d_reclen', c_ushort),           # length of this record
		('d_type', c_ubyte),              # type of file; not supported by all file system types
		('d_name', c_char * 256),         # filename
	]

DIRENTRY_P = POINTER(DIRENTRY)

fchown = libc.fchown
fchown.argtypes = [c_int, c_int, c_int]
fchown.restype = c_int

fchmod = libc.fchmod
fchmod.argtypes = [c_int, c_int]
fchmod.restype = c_int

fdopendir = libc.fdopendir
# accepts file/dir descriptor (integer)
fdopendir.argtypes = [c_int]
# returns pointer to DIR structure
fdopendir.restype = c_void_p

readdir = libc.readdir
# accepts pointer to DIR structure
readdir.argtypes = [c_void_p]
# returns pointer to DIRENTRY structure
readdir.restype = DIRENTRY_P

rewinddir = libc.rewinddir
# accepts pointer to DIR structure
rewinddir.argtypes = [c_void_p]
# returns void
rewinddir.restype = None

closedir = libc.closedir
# accepts pointer to DIR structure
closedir.argtypes = [c_void_p]
closedir.restype = c_int


# --- liblve functions -----------------------------------------------

# Opens path for reading not following symlinks and verifies that opened path is inside parent_path
# Returns:
# descriptor if successful
# -1 if path does not exist or is a symlink
# -2 if opened path is NOT inside parent_path or cannot be determined
# accepts path, parent_path
open_not_symlink = liblve.open_not_symlink
open_not_symlink.argtypes = [c_char_p, c_char_p]
open_not_symlink.restype = c_int

# Closes descriptor (if it is not equal -1)
# accepts file/dir descriptor (integer)
liblve.closefd.argtypes = [c_int]
# returns void
liblve.closefd.restype = None

# Tries to read first directory entry in order to ensure that descriptor is valid
# Returns 0 if reading succeeded or -1 if error has occured
check_dir = liblve.check_dir
# accepts descriptor
check_dir.argtypes = [c_int]
check_dir.restype = c_int

# Checks if path is a directory (in secure manner)
# Also opens path (if descriptor fd == -1) and then checks that opened path is inside parent_path
# Returns descriptor if path refers to directory
# Returns -1 if path does not exist or is not a directory
# Returns -2 if opened path is NOT inside parent_path or cannot be determined
isdir = liblve.isdir
# accepts path, descriptor, parent_path
isdir.argtypes = [c_char_p, c_int, c_char_p]
isdir.restype = c_int

# Sets permissions to directory (in secure manner)
# Returns descriptor if successful
# Returns -1 if error has occured
# Returns -2 if opened path is NOT inside parent_path or cannot be determined
# accepts: const char *path, mode_t perm, int fd, const char *parent_path
liblve.set_perm_dir_secure.argtypes = [c_char_p, c_int, c_int, c_char_p]
liblve.set_perm_dir_secure.restype = c_int

# Sets owner and group of directory (in secure manner)
# Returns descriptor if successful
# Returns -1 if error has occured
# Returns -2 if opened path is NOT inside parent_path or cannot be determined
# accepts: const char *path, uid_t uid, gid_t gid, int fd, const char *parent_path
liblve.set_owner_dir_secure.argtypes = [c_char_p, c_int, c_int, c_int, c_char_p]
liblve.set_owner_dir_secure.restype = c_int

# Creates directory if it does not exist, sets permissions/owner otherwise
# Returns descriptor if successful
# Returns -1 if error has occured
# accepts: const char *path, mode_t perm, uid_t uid, gid_t gid, int fd, const char *parent_path
liblve.create_dir_secure.argtypes = [c_char_p, c_int, c_int, c_int, c_int, c_char_p]
liblve.create_dir_secure.restype = c_int

# Recursive directory creation function
# Returns 0 if successful
# Returns -1 if error has occured
# accepts: const char *path, mode_t perm, uid_t uid, gid_t gid, const char *parent_path
liblve.makedirs_secure.argtypes = [c_char_p, c_int, c_int, c_int, c_char_p]
liblve.makedirs_secure.restype = c_int

# Writes absolute path pointed by descriptor fd to buffer *buf
# Returns buf if successful
# Returns NULL if error has occured
get_path_from_descriptor = liblve.get_path_from_descriptor
get_path_from_descriptor.argtypes = [c_int, c_char_p]
get_path_from_descriptor.restype = c_char_p

# Returns 1 if subdir is subdirectory of dir, 0 otherwise
is_subdir = liblve.is_subdir
is_subdir.argtypes = [c_char_p, c_char_p]
is_subdir.restype = c_int


# Returns list of entries of directory pointed by descriptor
def flistdir(fd):
	# Duplicate descriptor, because closedir() closes descriptor associated with directory stream
	fd2 = os.dup(fd)

	# Open directory stream
	dirp = fdopendir(fd2)
	if not dirp:
		raise Exception("fdopendir error")

	# Reset position of directory stream
	# (so it will be possible to read content of directory multiple times
	# via other descriptors that refer to the directory)
	rewinddir(dirp)

	dirlist = []
	while True:
		entryp = readdir(dirp)
		if not entryp:
			break
		entry = entryp.contents
		dirlist.append(entry.d_name)

	rewinddir(dirp)
	closedir(dirp)
	return dirlist



def closefd(fd):
	if fd is not None:
		try:
			os.close(fd)
		except OSError:
			pass



# Sets permissions to directory (in secure manner)
# Returns descriptor if successful
# Returns None if error has occured
def set_perm_dir_secure(path, perm, parent_path, fd = None, logger = None):
	if fd == None:
		fd = -1
	fd = liblve.set_perm_dir_secure(path, perm, fd, parent_path)
	if fd > 0:
		return fd
	if logger is not None:
		logger('Error: failed to set permissions of directory ' + path, False, True)
	return None



# Sets owner and group of directory (in secure manner)
# Returns descriptor if successful
# Returns None if error has occured
def set_owner_dir_secure(path, uid, gid, parent_path, fd = None, logger = None):
	if fd == None:
		fd = -1
	fd = liblve.set_owner_dir_secure(path, uid, gid, fd, parent_path)
	if fd > 0:
		return fd
	if logger is not None:
		logger('Error: failed to set owner of directory ' + path, False, True)
	return None



# Creates directory if it does not exist, sets permissions/owner otherwise
# Returns descriptor if successful
# Returns None if error has occured
def create_dir_secure(path, perm, uid, gid, parent_path, fd = None, logger = None):
	if fd == None:
		fd = -1
	fd = liblve.create_dir_secure(path, perm, uid, gid, fd, parent_path)
	if fd > 0:
		return fd
	if logger is not None:
		logger('Error: failed to create directory ' + path, False, True)
	return None



# Recursive directory creation function
# Returns 0 if successful
# Returns -1 if error has occured
def makedirs_secure(path, perm, uid, gid, parent_path, logger = None):
	res = liblve.makedirs_secure(path, perm, uid, gid, parent_path)
	if res and logger:
		logger('Error: failed to create directory ' + path, False, True)
	return res


def set_capability(clear=False):
	"""
	Set CAP_SYS_RESOURCE capability

	:param bool clear: Set on if it's true, set off otherwise
	:return: 0 for success, -1 otherwise
	:rtype: int
	"""
	if clear:
		return liblve.disable_quota_capability()
	else:
		return liblve.enable_quota_capability()


def change_uid(uid):
	"""
	Change effective uid of current process and set CAP_SYS_RESOURCE capbality
	to prevent "Disk quota exceeded" error

	:param int euid: User ID to set it as current effective UID
	:return: 0 if capability was set successfuly, -1 otherwise
	:rtype: int
	"""
	os.setuid(uid)
	return set_capability()
