diff --git a/app/controller/Cert.php b/app/controller/Cert.php index 5e039e8..c77269c 100644 --- a/app/controller/Cert.php +++ b/app/controller/Cert.php @@ -203,6 +203,7 @@ class Cert extends BaseController $domain = $this->request->post('domain', null, 'trim'); $id = input('post.id'); $type = input('post.type', null, 'trim'); + $status = input('post.status', null, 'trim'); $offset = input('post.offset/d'); $limit = input('post.limit/d'); @@ -216,6 +217,17 @@ class Cert extends BaseController if (!empty($type)) { $select->where('B.type', $type); } + if (!isNullOrEmpty($status)) { + if ($status == '5') { + $select->where('A.status', '<', 0); + } elseif ($status == '6') { + $select->where('A.expiretime', '<', date('Y-m-d H:i:s', time() + 86400 * 7))->where('A.expiretime', '>=', date('Y-m-d H:i:s')); + } elseif ($status == '7') { + $select->where('A.expiretime', '<', date('Y-m-d H:i:s')); + } else { + $select->where('A.status', $status); + } + } $total = $select->count(); $rows = $select->fieldRaw('A.*,B.type,B.remark aremark')->order('id', 'desc')->limit($offset, $limit)->select(); @@ -541,6 +553,7 @@ class Cert extends BaseController $domain = $this->request->post('domain', null, 'trim'); $oid = input('post.oid'); $type = input('post.type', null, 'trim'); + $status = input('post.status', null, 'trim'); $remark = input('post.remark', null, 'trim'); $offset = input('post.offset/d'); $limit = input('post.limit/d'); @@ -555,6 +568,9 @@ class Cert extends BaseController if (!empty($type)) { $select->where('B.type', $type); } + if (!isNullOrEmpty($status)) { + $select->where('A.status', $status); + } if (!empty($remark)) { $select->where('A.remark', $remark); } diff --git a/app/lib/DeployHelper.php b/app/lib/DeployHelper.php index 409dc1e..5de81c1 100644 --- a/app/lib/DeployHelper.php +++ b/app/lib/DeployHelper.php @@ -59,7 +59,7 @@ class DeployHelper ], ], 'kangle' => [ - 'name' => 'Kangle', + 'name' => 'Kangle用户', 'class' => 1, 'icon' => 'host.png', 'note' => '以上登录信息为Easypanel用户面板的,非管理员面板。如选网站密码认证类型,则用户面板登录不能开启验证码。', @@ -131,6 +131,72 @@ class DeployHelper ], ], ], + 'kangleadmin' => [ + 'name' => 'Kangle管理员', + 'class' => 1, + 'icon' => 'host.png', + 'note' => '以上登录地址需填写Easypanel管理员面板地址,非用户面板。', + 'inputs' => [ + 'url' => [ + 'name' => '面板地址', + 'type' => 'input', + 'placeholder' => 'Easypanel管理员面板地址', + 'note' => '填写规则如:http://192.168.1.100:3312 ,不要带其他后缀', + 'required' => true, + ], + 'path' => [ + 'name' => '管理员面板路径', + 'type' => 'input', + 'placeholder' => '留空默认为/admin', + ], + 'username' => [ + 'name' => '管理员用户名', + 'type' => 'input', + 'placeholder' => '', + 'required' => true, + ], + 'skey' => [ + 'name' => '面板安全码', + 'type' => 'input', + 'placeholder' => '管理员面板->服务器设置->面板通信安全码', + 'required' => true, + ], + 'proxy' => [ + 'name' => '使用代理服务器', + 'type' => 'radio', + 'options' => [ + '0' => '否', + '1' => '是', + ], + 'value' => '0' + ], + ], + 'taskinputs' => [ + 'name' => [ + 'name' => '网站用户名', + 'type' => 'input', + 'placeholder' => '', + 'required' => true, + ], + 'type' => [ + 'name' => '部署类型', + 'type' => 'radio', + 'options' => [ + '0' => '网站SSL证书', + '1' => '单域名SSL证书(仅CDN支持)', + ], + 'value' => '0', + 'required' => true, + ], + 'domains' => [ + 'name' => 'CDN域名列表', + 'type' => 'textarea', + 'placeholder' => '填写要部署证书的域名,每行一个', + 'show' => 'type==1', + 'required' => true, + ], + ], + ], 'safeline' => [ 'name' => '雷池WAF', 'class' => 1, @@ -400,6 +466,61 @@ class DeployHelper ], ], ], + 'synology' => [ + 'name' => '群辉面板', + 'class' => 1, + 'icon' => 'synology.png', + 'note' => null, + 'tasknote' => '', + 'inputs' => [ + 'url' => [ + 'name' => '面板地址', + 'type' => 'input', + 'placeholder' => '群辉面板地址', + 'note' => '填写规则如:http://192.168.1.100:5000 ,不要带其他后缀', + 'required' => true, + ], + 'username' => [ + 'name' => '登录账号', + 'type' => 'input', + 'placeholder' => '必须是处于管理员用户组,不能开启双重认证', + 'required' => true, + ], + 'password' => [ + 'name' => '登录密码', + 'type' => 'input', + 'placeholder' => '', + 'required' => true, + ], + 'version' => [ + 'name' => '群辉版本', + 'type' => 'radio', + 'options' => [ + '0' => '7.x', + '1' => '6.x', + ], + 'value' => '0', + 'required' => true, + ], + 'proxy' => [ + 'name' => '使用代理服务器', + 'type' => 'radio', + 'options' => [ + '0' => '否', + '1' => '是', + ], + 'value' => '0' + ], + ], + 'taskinputs' => [ + 'desc' => [ + 'name' => '群晖证书描述', + 'type' => 'input', + 'placeholder' => '', + 'note' => '根据证书描述匹配替换对应证书,留空则根据证书通用名匹配', + ], + ], + ], 'aliyun' => [ 'name' => '阿里云', 'class' => 2, diff --git a/app/lib/deploy/kangleadmin.php b/app/lib/deploy/kangleadmin.php new file mode 100644 index 0000000..35d223b --- /dev/null +++ b/app/lib/deploy/kangleadmin.php @@ -0,0 +1,173 @@ +url = rtrim($config['url'], '/'); + if (empty($config['path'])) $config['path'] = '/admin'; + $this->path = rtrim($config['path'], '/'); + $this->username = $config['username']; + $this->skey = $config['skey']; + $this->proxy = $config['proxy'] == 1; + } + + public function check() + { + if (empty($this->url) || empty($this->username) || empty($this->skey)) throw new Exception('必填参数不能为空'); + $this->login(); + } + + public function deploy($fullchain, $privatekey, $config, &$info) + { + if (empty($config['name'])) throw new Exception('网站用户名不能为空'); + $this->login(); + $this->log('登录成功 cookie:' . $this->cookie); + $this->loginVhost($config['name']); + + if ($config['type'] == '1' && !empty($config['domains'])) { + $domains = explode("\n", $config['domains']); + $success = 0; + $errmsg = null; + foreach ($domains as $domain) { + $domain = trim($domain); + if (empty($domain)) continue; + try { + $this->deployDomain($domain, $fullchain, $privatekey); + $this->log("域名 {$domain} 证书部署成功"); + $success++; + } catch (Exception $e) { + $errmsg = $e->getMessage(); + $this->log("域名 {$domain} 证书部署失败:" . $errmsg); + } + } + if ($success == 0) { + throw new Exception($errmsg ? $errmsg : '要部署的域名不存在'); + } + } else { + $this->deployAccount($fullchain, $privatekey); + $this->log("账号级SSL证书部署成功"); + } + } + + private function deployDomain($domain, $fullchain, $privatekey) + { + $path = '/vhost/?c=ssl&a=domainSsl'; + $post = [ + 'domain' => $domain, + 'certificate' => $fullchain, + 'certificate_key' => $privatekey, + ]; + $response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy); + if (strpos($response['body'], '成功')) { + return true; + } elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) { + throw new Exception(htmlspecialchars($match[1])); + } elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) { + throw new Exception(htmlspecialchars($response['body'])); + } else { + throw new Exception('原因未知(httpCode=' . $response['code'] . ')'); + } + } + + private function deployAccount($fullchain, $privatekey) + { + $path = '/vhost/?c=ssl&a=ssl'; + $post = [ + 'certificate' => $fullchain, + 'certificate_key' => $privatekey, + ]; + $response = curl_client($this->url . $path, http_build_query($post), null, $this->cookie, null, $this->proxy); + if (strpos($response['body'], '成功')) { + return true; + } elseif (preg_match('/alert\(\'(.*?)\'\)/i', $response['body'], $match)) { + throw new Exception(htmlspecialchars($match[1])); + } elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) { + throw new Exception(htmlspecialchars($response['body'])); + } else { + throw new Exception('原因未知(httpCode=' . $response['code'] . ')'); + } + } + + private function login() + { + $url = $this->url . $this->path . '/index.php?c=sso&a=hello&url=' . urlencode($this->url . $this->path . '/index.php?'); + $response = curl_client($url, null, null, null, null, $this->proxy); + if ($response['code'] == 302 && !empty($response['redirect_url'])) { + $cookie = ''; + if (preg_match_all('/Set-Cookie: (.*);/iU', $response['header'], $matchs)) { + foreach ($matchs[1] as $val) { + $arr = explode('=', $val); + if ($arr[1] == '' || $arr[1] == 'deleted') continue; + $cookie .= $val . '; '; + } + $query = parse_url($response['redirect_url'], PHP_URL_QUERY); + parse_str($query, $params); + if (isset($params['r'])) { + $sess_key = $params['r']; + $this->login2($cookie, $sess_key); + $this->cookie = $cookie; + return true; + } else { + throw new Exception('获取SSO凭据失败,sess_key获取失败'); + } + } else { + throw new Exception('获取SSO凭据失败,获取cookie失败'); + } + } elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) { + throw new Exception('获取SSO凭据失败 (' . htmlspecialchars($response['body']) . ')'); + } else { + throw new Exception('获取SSO凭据失败 (httpCode=' . $response['code'] . ')'); + } + } + + private function login2($cookie, $sess_key) + { + $s = md5($sess_key . $this->username . $sess_key . $this->skey); + $url = $this->url . $this->path . '/index.php?c=sso&a=login&name=' . $this->username . '&r=' . $sess_key . '&s=' . $s; + $response = curl_client($url, null, null, $cookie, null, $this->proxy); + if ($response['code'] == 302) { + return true; + } elseif (strlen($response['body']) > 3 && strlen($response['body']) < 50) { + throw new Exception('SSO登录失败 (' . htmlspecialchars($response['body']) . ')'); + } else { + throw new Exception('SSO登录失败 (httpCode=' . $response['code'] . ')'); + } + } + + private function loginVhost($name) + { + $url = $this->url . $this->path . '/index.php?c=vhost&a=impLogin&name=' . $name; + $response = curl_client($url, null, null, $this->cookie, null, $this->proxy); + if ($response['code'] == 302) { + curl_client($this->url . '/vhost/', null, null, $this->cookie, null, $this->proxy); + } else { + throw new Exception('用户面板登录失败 (httpCode=' . $response['code'] . ')'); + } + } + + public function setLogger($func) + { + $this->logger = $func; + } + + private function log($txt) + { + if ($this->logger) { + call_user_func($this->logger, $txt); + } + } +} diff --git a/app/lib/deploy/synology.php b/app/lib/deploy/synology.php new file mode 100644 index 0000000..ae7fd39 --- /dev/null +++ b/app/lib/deploy/synology.php @@ -0,0 +1,147 @@ +url = rtrim($config['url'], '/'); + $this->username = $config['username']; + $this->password = $config['password']; + $this->version = $config['version']; + $this->proxy = $config['proxy'] == 1; + } + + public function check() + { + if (empty($this->url) || empty($this->username) || empty($this->password)) throw new Exception('必填内容不能为空'); + $this->login(); + } + + private function login() + { + $url = $this->url . '/webapi/' . ($this->version == '1' ? 'auth.cgi' : 'entry.cgi'); + $params = [ + 'api' => 'SYNO.API.Auth', + 'version' => 6, + 'method' => 'login', + 'account' => $this->username, + 'passwd' => $this->password, + 'format' => 'sid', + 'enable_syno_token' => 'yes', + ]; + $response = curl_client($url, http_build_query($params), null, null, null, $this->proxy); + $result = json_decode($response['body'], true); + if (isset($result['success']) && $result['success']) { + $this->token = $result['data']; + } elseif(isset($result['error'])) { + throw new Exception('登录失败:' . $result['error']); + } else { + throw new Exception('请求失败(httpCode=' . $response['code'] . ')'); + } + } + + public function deploy($fullchain, $privatekey, $config, &$info) + { + $certInfo = openssl_x509_parse($fullchain, true); + if (!$certInfo) throw new Exception('证书解析失败'); + + $url = $this->url . '/webapi/entry.cgi'; + $params = [ + 'api' => 'SYNO.Core.Certificate.CRT', + 'version' => 1, + 'method' => 'list', + '_sid' => $this->token['sid'], + 'SynoToken' => $this->token['synotoken'], + ]; + $response = curl_client($url, http_build_query($params), null, null, null, $this->proxy); + $result = json_decode($response['body'], true); + if (isset($result['success']) && $result['success']) { + $this->log('获取证书列表成功'); + } elseif(isset($result['error'])) { + throw new Exception('获取证书列表失败:' . $result['error']); + } else { + throw new Exception('获取证书列表失败(httpCode=' . $response['code'] . ')'); + } + + $id = null; + foreach ($result['data']['certificates'] as $certificate) { + if ($certificate['subject']['common_name'] == $certInfo['subject']['CN'] || $certificate['desc'] == $config['desc']) { + $id = $certificate['id']; + break; + } + } + if ($id) { + $this->import($fullchain, $privatekey, $config, $id); + } else { + $this->import($fullchain, $privatekey, $config); + } + } + + private function import($fullchain, $privatekey, $config, $id = null) + { + $url = $this->url . '/webapi/entry.cgi'; + $params = [ + 'api' => 'SYNO.Core.Certificate', + 'version' => 1, + 'method' => 'import', + '_sid' => $this->token['sid'], + 'SynoToken' => $this->token['synotoken'], + ]; + $privatekey_file = tempnam(sys_get_temp_dir(), 'privatekey'); + file_put_contents($privatekey_file, $privatekey); + $fullchain_file = tempnam(sys_get_temp_dir(), 'fullchain'); + file_put_contents($fullchain_file, $fullchain); + $post = [ + 'key' => new \CURLFile($privatekey_file), + 'cert' => new \CURLFile($fullchain_file), + 'id' => $id, + 'desc' => $config['desc'], + ]; + $response = curl_client($url . '?' . http_build_query($params), $post, null, null, null, $this->proxy); + unlink($privatekey_file); + unlink($fullchain_file); + $result = json_decode($response['body'], true); + if ($id) { + if (isset($result['success']) && $result['success']) { + $this->log('证书ID:'.$id.'更新成功!'); + } elseif(isset($result['error'])) { + throw new Exception('证书ID:'.$id.'更新失败:' . $result['error']); + } else { + throw new Exception('证书ID:'.$id.'更新失败(httpCode=' . $response['code'] . ')'); + } + } else { + if (isset($result['success']) && $result['success']) { + $this->log('证书上传成功!'); + } elseif(isset($result['error'])) { + throw new Exception('证书上传失败:' . $result['error']); + } else { + throw new Exception('证书上传失败(httpCode=' . $response['code'] . ')'); + } + } + } + + public function setLogger($func) + { + $this->logger = $func; + } + + private function log($txt) + { + if ($this->logger) { + call_user_func($this->logger, $txt); + } + } +} diff --git a/app/view/cert/certorder.html b/app/view/cert/certorder.html index 44251e7..9e7bfde 100644 --- a/app/view/cert/certorder.html +++ b/app/view/cert/certorder.html @@ -27,6 +27,9 @@ pre.pre-log{height: 330px;overflow-y: auto;width: 100%;background-color: rgba(51 {/foreach} +