 /****************************************************************************

   mod_dav_pgsql (DASL) for apache 2.X
   CopyRight(C) 2005 SolutionBox Inc. All Rights reserved.
   CopyRight(C) 2006 Netomi Inc. All Rights reserved.
   Author : elenoa lazyfake (elenoa@solutionbox.co.kr)

   $Id: share.c,v 1.8 2007/02/22 08:22:13 elenoa Exp $

   Redistribution and use in source and binary forms, with or with out
   modification, are not permitted in outside of SolutionBox Inc.

 ****************************************************************************/
#include <unistd.h>
#include <ctype.h>
#include <math.h>

#include <httpd.h>
#include <http_config.h>
#include <http_protocol.h>
#include <http_log.h>
#include <http_core.h>      /* for ap_construct_url */
#include <http_request.h>

#include <apr_strings.h>
#include <apr_signal.h>

#include "dav_repos.h"
#include "dbms.h"
#include "util.h"
#include "share_common.h"
#include "share_account.h"

/* for user cache */
static struct shm_mgnt ucah_mgnt = {
	NULL, NULL, SHM_UCAH, SHM_UCAH_LOCK, 0, 0
};

struct account_cache *g_ac;
static int maxcache = 0 ;

int dav_initialize_shared_account_cache(dav_repos_server_conf *dsc, server_rec *s, apr_pool_t *p)
{
	if (dsc->user_cache <= 0) {
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "OPENDAV: initialize_shared_memory: "
			"MaxUserCache <= 0 (%d)", dsc->user_cache);
		return !OK;
	}

	maxcache = dsc->user_cache;

	/* user cache : calculate shared memory size from user_cache value */
	ucah_mgnt.size = (sizeof(struct account_cache) * dsc->user_cache);
	g_ac = (struct account_cache *)dav_shared_get_new_memory(&ucah_mgnt, s, p, false);
	if (!g_ac) return !OK;

	return OK;
}

void dav_cleanup_shared_account_cache()
{
	if (ucah_mgnt.shm) {
		apr_shm_detach(ucah_mgnt.shm);
	}
	if (ucah_mgnt.lock) {
		apr_global_mutex_destroy(ucah_mgnt.lock);
	}
}

struct account_cache * dav_shared_ac_get_by_vol(const char * volume_name)
{
#ifndef __OPENDAV_SHARED_NOT_USE
	int i;

	if (!g_ac) 
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_ac_get_by_vol:: Account cache NULL. [%s]", volume_name);
		return NULL;
	}
		

#define AC_TIMEOUT	300

	apr_global_mutex_lock(ucah_mgnt.lock);

	for (i = 0; i < maxcache; i++)
	{
		if ((g_ac + i)->id[0] == '\0')
			break;

		if (!strcmp((g_ac + i)->svc_id, volume_name))
		{
			struct account_cache * retval = (g_ac + i);	

			// 2015.7.15 dadamin
			// version 3.5부터 timeout 대신 fimnd 의해서 설정된 flag 값 참조
			// 0 : 정상 , 1 : 삭제
			if ((g_ac + i)->nFlag)
			{
				ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_ac_get_by_vol:: The [%s] service is deleted.", (g_ac + i)->svc_id);
				retval = NULL;
			}				

			apr_global_mutex_unlock(ucah_mgnt.lock);
			return retval;
		}
	}
	apr_global_mutex_unlock(ucah_mgnt.lock);

	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_ac_get_by_vol:: The [%s] service not found.", volume_name);
#endif

	return NULL;
}

// NEW 2012-04-20 노경민
// dav_shared_ac_get_by_vol() 함수 상의 TIMEOUT 체크 기능으로 인해
// 무인증 관련 서비스의 대소문자 구분/무시 여부 확인 처리시...
// Cache 값 변경이 발생되지 않아... NULL 값이 반환되는 문제 발생..
// 따라서 이를 해결하기 위해 TIMEOUT 비교가 없는 함수를 추가함.
struct account_cache * dav_shared_ac_get_by_vol_not_timeout(const char * volume_name)
{
#ifndef __OPENDAV_SHARED_NOT_USE
	int i;

	if (!g_ac)
		return NULL;

	apr_global_mutex_lock(ucah_mgnt.lock);

	for (i = 0; i < maxcache; i++)
	{
		if ((g_ac + i)->id[0] == '\0')
			break;

		// 2015.7.15 dadamin
		// version 3.5부터 timeout 대신 fimnd 의해서 설정된 flag 값 참조
		// 0 : 정상 , 1 : 삭제
		if ((g_ac + i)->nFlag)
		{
			ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_ac_get_by_vol_not_timeout:: The [%s] service is deleted.", (g_ac + i)->svc_id);
			continue;
		}

		if (!strcmp((g_ac + i)->svc_id, volume_name))
		{
			struct account_cache * retval = (g_ac + i);

			apr_global_mutex_unlock(ucah_mgnt.lock);
			return retval;
		}
	}
	apr_global_mutex_unlock(ucah_mgnt.lock);

#endif

	return NULL;
}

// 2017-03-08 CHG huibong
// - #28349 서비스 해지 처리된 경우 불필요한 로그가 인증시마다 발생, 이를 해결하기 위해 Flow 수정 처리.
//
struct account_cache *dav_shared_ac_get_by_tranid(char *seq)
{
#ifndef __OPENDAV_SHARED_NOT_USE
	int i;

	if (!g_ac)
		return NULL;

	apr_global_mutex_lock(ucah_mgnt.lock);

	// 2015.7.15 dadamin
	// version 3.5부터 timeout 대신 fimnd 의해서 설정된 flag 값 참조
	// 0 : 정상 , 1 : 삭제

	// 인증 처리 부분이므로 검색 속도는 최대한 빠르게 한다.
	for (i = 0; i < maxcache; i++)
	{
		if ((g_ac + i)->id[0] == '\0')
			break;

		// 삭제된 서비스는 검색 조건에서 제거
		if((g_ac + i)->nFlag)
		{
			continue;
		}

		// 서비스 sequece 비교 
		if(!strcmp((g_ac + i)->tran_id, seq))
		{
			struct account_cache * retval = (g_ac + i);
			apr_global_mutex_unlock(ucah_mgnt.lock);

			return retval;
		}
	}

	// 삭제된 서비스 요청을 로깅 처리.
	//  - 요청량이 많지 않을 것이므로.. 응답이 늦어도 상관 없음.
	//  - 로깅 처리 후 NULL 반환
	//for (i = 0; i < maxcache; i++)
	//{
	//	if ((g_ac + i)->id[0] == '\0')
	//		break;
	//
	//	// 삭제된 서비스
	//	if((g_ac + i)->nFlag)
	//	{
	//		// 삭제 또는 모르는 요청인 경우.. 전달받은 seq 값이 숫자로 변환되지 않고 요청된 그대로 들어오므로...
	//		// cache 된 정보와 seq 비교는 할 수 없다.
	//		// 따라서 그냥 해지된 서비스는 모두 로깅 처리한다.
	//		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_ac_get_by_tranid:: service [%s] is deleted.", (g_ac + i)->svc_id);
	//
	//		continue;
	//	}
	//}
	
	apr_global_mutex_unlock(ucah_mgnt.lock);
#endif

	return NULL;
}


// NEW 2012-03-20 노경민
// 각 서비스별 uri 에 대한 대소문자 구분, 무시 여부를 반환한다.
// 해당 반환값은 RCDB t_sms_sp_svc_product 테이블 uri_ignore_case 필드의 설정값을 확인하여 다음과 같이 반환처리한다.
//  0 : 대소문자 구분 ( 양방향 Default )
//  1 : 대소문자 무시
// -1 : 오류 발생.
int dav_shared_is_uri_ignore_case(const dav_resource * resource)
{
	struct account_cache * ac = NULL;
	dav_resource_private *info = NULL;
	char *tranid;

	if (resource == NULL || resource->info == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_is_uri_ignore_case: dav_resource NULL.");
		return -1;
	}

	info = resource->info;
	tranid = apr_psprintf(resource->pool, "%" APR_INT64_T_FMT "", info->user_id);

	ac = dav_shared_ac_get_by_tranid(tranid);

	// 해당 서비스 정보를 공유 메모리 상에서 확인하지 못한 경우
	if (ac == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_is_uri_ignore_case: tranid[%s] uri ignore info not found.", tranid);
		return -1;
	}

	return ac->is_ignorecase;
}

int dav_shared_is_uri_ignore_case_by_tranid(char *tranid)
{
	struct account_cache * ac = NULL;
	ac = dav_shared_ac_get_by_tranid(tranid);

	// 해당 서비스 정보를 공유 메모리 상에서 확인하지 못한 경우
	if (ac == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_is_uri_ignore_case_by_tranid: tranid[%s] uri ignore info not found.", tranid);
		return -1;
	}

	return ac->is_ignorecase;
}

int dav_shared_is_uri_ignore_case_by_svcname(const char * svcname)
{
	struct account_cache * ac = NULL;
	ac = dav_shared_ac_get_by_vol_not_timeout(svcname);

	// 해당 서비스 정보를 공유 메모리 상에서 확인하지 못한 경우
	if (ac == NULL)
	{
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "dav_shared_is_uri_ignore_case_by_svcname: svcname[%s] uri ignore info not found.", svcname);
		return -1;
	}

	return ac->is_ignorecase;
}

static int dav_shared_ac_perform_find(char *haystack, char *__needle, int iscase)
{
	char *needle = __needle;
	int i, h = strlen(haystack), n = strlen(needle);


	do {
		//		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: Referer: find: needle %s/%s", needle, __needle);

		__needle = needle;

		if (h < n) goto next_time;

		for (i = 0; i < (h - n + 1); i++) {
			if (tolower(*(haystack + i)) == tolower(*needle)) {
#if 0
				if (iscase == OK) {
#endif
					if (!strncasecmp((haystack + i), needle, n)) {
						goto match_n_next;
					}
#if 0
				}
				else {
					if (!strncmp((haystack + i), needle, n)) {
						goto match_n_next;
					}
				}
#endif

				continue;

			match_n_next:
				if (i != 0 && *(haystack + i - 1) != '|')
					continue;

				if (*(haystack + i + n) != '|' && *(haystack + i + n) != '\0')
					continue;

				return OK;
			}
		}

		//ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: Referer: find: done?");

	next_time:
		needle = strchr(__needle, '.');
		//		ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: Referer: find: done? needle %x __needle %x", needle, __needle);

		if (needle) {
			needle++;
			if (*needle == '\0') break;
			n = strlen(needle);
		}
	} while (needle);

	//	ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: Referer: find: ALL done?");

	return !OK;
}

#define IS_PERMIT_EMPTY_REFERER(ac)	(strlen(ac->mode) > 4 && ac->mode[4] == '1')
int dav_shared_ac_check_referer(struct account_cache *ac, request_rec *r)
{
	const char *referer = apr_table_get(r->headers_in, "Referer");
	char *pszCopy, *pszUriCopy;
	char *p, *p2, *p3, *q;

	if ((!referer) || (NULL == referer) || (0 == strlen(referer)))
	{

		if (IS_PERMIT_EMPTY_REFERER(ac))
		{
			if (ac->permitted_referer[0] != '\0')
			{
				ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: empty referer checking: referer is NULL, not permited.");
				return !OK;
			}
		}

		return OK;
	}

	if (ac->permitted_referer[0] == '\0')
		return OK;

	pszCopy = apr_pstrdup(r->pool, referer);
	if (!pszCopy)
		return OK;

	if ((p = strstr(pszCopy, "://")))
		p += 3;
	else
		p = pszCopy;

	p2 = strchr(p, '/');
	p3 = strchr(p, ':');

	if (p2 == NULL)
	{
		if (p3 == NULL)
		{
			if (IS_PERMIT_EMPTY_REFERER(ac))
			{
				if (ac->permitted_referer[0] != '\0')
					;
				else
					return OK;
			}
			else
				return OK;
		}
		else
		{
			q = p3;
			*q = '\0';
		}
	}
	else
	{
		if (p3 == NULL)
			q = p2;
		else if (p2 < p3)
			q = p2;
		else
			q = p3;

		*q = '\0';
	}


	pszUriCopy = apr_pstrdup(r->pool, r->uri);
	q = pszUriCopy;

	if ((p2 = strrchr(q, '.')))
		q = p2 + 1;
	else
		q = NULL;

	if (ac->permitted_extend[0] != '\0')
	{
		if (!q)
			return OK;

		if (dav_shared_ac_perform_find(ac->permitted_extend, q, !OK) == !OK)
			return OK;
	}

	if (ac->permitted_referer[0] != '\0')
	{
		if (dav_shared_ac_perform_find(ac->permitted_referer, p, OK) == !OK)
		{
			ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "OPENDAV: referer: uri %s, extension %s, permitted %s !!!!!"
				, p, (ac->permitted_extend[0] != '\0' ? ac->permitted_extend : "NONE"), ac->permitted_referer);

			return !OK;
		}
	}

	return OK;
}
