<?php
// 기본 경로 정의
if(!defined('ABSPATH')){define('ABSPATH',__DIR__.'/');}


// 세션  보안 강화
if(session_status()===PHP_SESSION_NONE){
session_start([
'cookie_lifetime'=>0,
'cookie_httponly'=>true,
'cookie_secure'=>true,
'use_strict_mode'=>true,
'use_only_cookies'=>true,
'cookie_samesite'=>'Lax',
]);
}


// PDF_VIEW HASH 암호화
define('FAX_VIEW_SECRET','코드입력');


// Meta 암호화
define('META_KEY','코드입력');
function loadMeta($file){
if(!is_file($file)){return[];}
$raw=file_get_contents($file);
if($raw===false||$raw===''){return[];}
$data=base64_decode($raw,true);
if($data===false){return[];}
$iv_len=openssl_cipher_iv_length('aes-256-gcm');
if(strlen($data)<=($iv_len+16)){return[];}
$iv=substr($data,0,$iv_len);
$tag=substr($data,$iv_len,16);
$cipher=substr($data,$iv_len+16);
$json=openssl_decrypt($cipher,'aes-256-gcm',META_KEY,OPENSSL_RAW_DATA,$iv,$tag);
if($json===false){return[];}
$decoded=json_decode($json,true);
return is_array($decoded)?$decoded:[];
}
function saveMeta($file,$data){$json=json_encode($data,JSON_UNESCAPED_UNICODE);
$iv_len=openssl_cipher_iv_length('aes-256-gcm');
$iv=random_bytes($iv_len);
$tag='';
$cipher=openssl_encrypt($json,'aes-256-gcm',META_KEY,OPENSSL_RAW_DATA,$iv,$tag);
return file_put_contents($file,base64_encode($iv.$tag.$cipher),LOCK_EX);
}


// 외부 설정 로드
$settingsFile=ABSPATH.'data/users/settings.meta';
$dynConf=loadMeta($settingsFile);
function getCfg($section,$key,$default){global $dynConf;
if($section==='users'||$section==='permissions')return $dynConf[$section]??$default;
return $dynConf[$section][$key]??$default;}


// 로그인 보안 설정
if(!defined('MAX_LOGIN_ATTEMPTS'))define('MAX_LOGIN_ATTEMPTS',(int)getCfg('general','MAX_LOGIN_ATTEMPTS',3));// 로그인 시도 횟수 설정
if(!defined('BLOCK_TIME_SECONDS'))define('BLOCK_TIME_SECONDS',(int)getCfg('general','BLOCK_TIME_SECONDS',1200));// 로그인 시도 횟수 초과시 차단 시간(적정값 1200초)


// 애플리케이션 저작권 및 버전 정보
if(!defined('APP_NAME'))define('APP_NAME',getCfg('general','APP_NAME','NSFAX'));
if(!defined('APP_SHORT_NAME'))define('APP_SHORT_NAME',getCfg('general','APP_SHORT_NAME','NSFAX'));
define('APP_COPYRIGHT','© 2006-' . date('Y').'');
define('APP_ORGAUTHOR',' <a href=https://sourceforge.net/projects/faxy/ target=_blank>Faxy</a>');
define('APP_MODIFIER','Modified by <a href=https://jnstory.net target=_blank>JoNamsik</a>');
define('APP_VERSION_BASE','Base Version 0.2.2');
define('APP_VERSION_MODIFIED','Modified Version 0.1.8 (260507)');
define('DATA_HTACCESS',"Require all denied");


// 고정 상수 (VAPID 설정)
define('PUSH_PUBLIC_KEY','코드입력');
define('PUSH_PRIVATE_KEY','코드입력');
define('PUSH_SUBJECT','mailto:이메일@yourdomain.com');


// 고정 상수 (보안 설정)
if(empty($_SESSION['csrf'])){$_SESSION['csrf']=bin2hex(random_bytes(32));}
header("X-Frame-Options:SAMEORIGIN");
header("X-Content-Type-Options:nosniff");
header("Content-Security-Policy:default-src 'self';script-src 'self' 'unsafe-inline';img-src 'self' data:;style-src 'self' 'unsafe-inline';");
header("Strict-Transport-Security:max-age=31536000;includeSubDomains");


// 사용자 계정 파일 경로
// 초기 접속 계정 ID, 비밀번호 모두 admin이며, 반드시 접속 후 계정 생성 진행하세요.
$usersFile=ABSPATH.'data/users/users.meta';
$USERS=loadMeta($usersFile);
if(empty($USERS)){
$USERS=['admin'=>['pass'=>password_hash('admin',PASSWORD_BCRYPT,['cost'=>12]),'role'=>'admin']];
saveMeta($usersFile,$USERS);
}


// 경로 설정
$applicationBaseUri="https://yourdomain.com";// 애플리케이션 서버 절대 URL/도메인/IP (끝에 "/" 제외)
$applicationBaseDir="/";// 애플리케이션 기본 URL (앞뒤 "/" 포함하며, 루트에 있을 경우 "/" 입력)
$applicationSpoolDir="/var/spool/hylafax/recvq/";// 수신 팩스의 스풀 디렉토리 (앞뒤 "/" 포함)
$applicationArchivePath="/var/www/nsfax/data/inbox/";// 수신 팩스함의 서버 절대 경로 (앞뒤 "/" 포함)
$applicationOutboxPath="/var/www/nsfax/data/outbox/";// 보낸 팩스함의 서버 절대 경로 (앞뒤 "/" 포함)


// 팩스함 및 연락처 설정
// 1.수신 팩스함
$inboxsortMode=getCfg('general','inboxsortMode','folder');// 파일 우선(file), 폴더 우선(folder)
$inboxfoldersortMode=getCfg('general','inboxfoldersortMode','off');// 내림차순(Z→A)(on), 오름차순(A→Z)(off)
$inboxfilesortMode=getCfg('general','inboxfilesortMode','on');// 최신순(on), 오래된순(off)
// 2.발신 팩스함
$outboxsortMode=getCfg('general','outboxsortMode','folder');// 파일 우선(file), 폴더 우선(folder)
$outboxfoldersortMode=getCfg('general','outboxfoldersortMode','off');// 내림차순(Z→A)(on), 오름차순(A→Z)(off)
$outboxfilesortMode=getCfg('general','outboxfilesortMode','on');// 최신순(on), 오래된순(off)
// 3.연락처
$contactSortMode=getCfg('general','contactSortMode','public');// 공용(public), 전체 혼합 가나다순(off)
$contactNameSortMode=getCfg('general','contactNameSortMode','off');// 내림차순(Z→A)(on), 오름차순(A→Z)(off)


// 대시보드 및 성능 설정
$scan_limit=(int)getCfg('general','scan_limit',50);// 성능 개선을 위한 최대 파일 검사 개수
$subfolder_level=(int)getCfg('general','subfolder_level',1);// 검색할 서브 폴더 단계
$recent_limit=(int)getCfg('general','recent_limit',5);// 표시할 최근 수발신 팩스 수 
$PER_PAGE_DESKTOP=(int)getCfg('general','PER_PAGE_DESKTOP',15);// PC에서 보여질 팩스 수 (기본값 15)
$PER_PAGE_MOBILE=(int)getCfg('general','PER_PAGE_MOBILE',9);// 모바일에서 보여질 팩스 수 (기본값 9)


// 팩스 송신 설정
$fax_retry=(int)getCfg('general','fax_retry',1);// 팩스 재발신 횟수 지정(-t)
$fax_error_timer=(int)getCfg('general','fax_error_timer',600);// 일정 초 이후 오류로 전환되도록 설정(적정값 600초)


// 퍼미션 설정
$permissions=getCfg('permissions',null,[
'faxalarm'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxsettings'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxdashboard'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxcontacts'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxsend'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxmemo'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxschedule'=>['admin'=>'on','user'=>'off','view'=>'off'],
'faxpriority'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxmenu'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxsearch'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxdelete'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxmemo'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxaddfolder'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxmove'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxrename'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxresend'=>['admin'=>'on','user'=>'off','view'=>'off'],
'outboxdownload'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxmenu'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxsearch'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxdelete'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxmemo'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxaddfolder'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxmove'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxrename'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxunread'=>['admin'=>'on','user'=>'off','view'=>'off'],
'inboxdownload'=>['admin'=>'on','user'=>'off','view'=>'off'],
]);


//------------------------------------------------------------------
// 아래 라인은 정확히 무엇을 하는지 모른다면 수정하지 마세요
//------------------------------------------------------------------
// 1. 언어 및 플러그인 경로
define('WP_LANG_DIR',ABSPATH."include/lang");
define('WP_PLUGIN_DIR',ABSPATH."include/plugin");


// 2. 자동 로그인 & 토큰 정리 & 디렉토리 보호 로직
// 2-1. 자동 로그인
if(!isset($_SESSION['user'])&&isset($_COOKIE['REMEMBER_TOKEN'])){
$rawToken=$_COOKIE['REMEMBER_TOKEN'];
$hashedToken=hash('sha256',$rawToken);
$tokenFile=ABSPATH.'data/remember/'.$hashedToken;
if(is_file($tokenFile)){
$data=json_decode(@file_get_contents($tokenFile),true);
if(is_array($data)&&isset($data['user'],$USERS[$data['user']],$data['created'])&&time()-(int)$data['created']<=86400*30){
$_SESSION['user']=$data['user'];
$_SESSION['csrf']=bin2hex(random_bytes(32));
session_regenerate_id(true);
}}}
// 2-2. 자동 로그인 토큰 정리
function cleanup_remember_tokens(string $dir,int $maxAge):void{
if(!is_dir($dir))return;
$now=time();$files=@scandir($dir);if($files===false)return;
foreach($files as $file){
if($file==='.'||$file==='..'||$file==='.htaccess')continue;
$path=$dir.'/'.$file;if(!is_file($path))continue;
$data=json_decode(@file_get_contents($path),true);
if(!is_array($data)||empty($data['created'])){@unlink($path);continue;}
if($now-(int)$data['created']>$maxAge){@unlink($path);}
}}
cleanup_remember_tokens(ABSPATH.'data/remember',86400*30);
// 2-3. 데이터 디렉토리 보호(.htaccess 생성 접근 차단)
$dataDir=ABSPATH.'data';
if(!is_dir($dataDir)){@mkdir($dataDir,0700,true);}
function protect_data_tree_subdirs(string $dir):void{
$items=@scandir($dir);if($items===false)return;
foreach($items as $item){
if($item==='.'||$item==='..')continue;
$path=$dir.'/'.$item;if(!is_dir($path))continue;
$htaccess=$path.'/.htaccess';
if(!is_file($htaccess)){@file_put_contents($htaccess,DATA_HTACCESS.PHP_EOL,LOCK_EX);}
protect_data_tree_subdirs($path);
}}
protect_data_tree_subdirs(ABSPATH.'data');


// en_EN이 기본 언어이며, /lang 디렉토리 내 파일 이름(확장자 제외)을 사용 가능
// po 편집 후 아래 명령어를 사용해 mo 파일을 생성해야 합니다.
// msgfmt /var/www/nsfax/include/lang/ko_KR.po -o /var/www/nsfax/include/lang/ko_KR.mo
$uid=$_SESSION['user']??null;
$uLang=null;
if($uid){
$uFile=ABSPATH.'data/users/'.preg_replace('/[^a-zA-Z0-9_-]/','',$uid).'.meta';
if(file_exists($uFile)){$uData=loadMeta($uFile);$uLang=$uData['prefs']['WPLANG']??null;}}
if(!defined('WPLANG'))define('WPLANG',$uLang??getCfg('general','WPLANG','en_EN'));


// 3. 통합 i18n 라이브러리 로드
$core_library=ABSPATH."include/plugin/i18n_core.min.php";
if(file_exists($core_library)){require_once$core_library;}
if(function_exists('load_textdomain')){$mofile=WP_LANG_DIR."/".WPLANG.".mo";
if(file_exists($mofile)&&is_readable($mofile)){load_textdomain('default',$mofile);}}


// 4. 권한 체크 함수
function is_admin(){
global $USERS;
$uid=$_SESSION['user']??null;
return $uid&&isset($USERS[$uid])&&$USERS[$uid]['role']==='admin';
}
function has_permission($key){
global $permissions,$USERS;
$uid=$_SESSION['user']??null;
if(!$uid||!isset($USERS[$uid]))return false;
$role=$USERS[$uid]['role'];
return isset($permissions[$key][$role])&&$permissions[$key][$role]==='on';
}


// 5. htmlspecialchars 단축어
// 5-1. 일반적인 웹 파일
// 적용예시 : echo ns($dir);
function ns($data){if($data===null)return'';return htmlspecialchars($data,ENT_QUOTES,'UTF-8');}
// 5-2. 번역문에 대한 함수
// 적용예시 : ?php _nsl('Fax Dashboard');?
function _nsl($text,$domain='default'){echo ns(__($text,$domain));}


// 6. 쉘 명령어 실행 권한 확인
function safe_shell($cmd,$args=[]){$allow=['sendfax','gs','faxstat'];$base=basename($cmd);
if(!in_array($base,$allow))return false;
$escapedArgs=array_map('escapeshellarg',$args);
return shell_exec($cmd.' '.implode(' ',$escapedArgs));}