mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
Fixed known bugs Panel Feature Modification Points to note after updating the panel: 1. The default port of the panel is changed to 7800 (Port 8888 has been flooded) 2. Panel entry error will prompt 404 3. After the panel is bound to the domain name, if the domain access is not used,it will return 401 4. After the panel is set to authorize IP access, other IP access panels will return 401 5. The website welcome page is changed to the nginx welcome page 6. Change the stop page to Nginx 404 page
850 lines
34 KiB
Python
850 lines
34 KiB
Python
#coding: utf-8
|
|
#-------------------------------------------------------------------
|
|
# 宝塔Linux面板
|
|
#-------------------------------------------------------------------
|
|
# Copyright (c) 2015-2016 宝塔软件(http:#bt.cn) All rights reserved.
|
|
#-------------------------------------------------------------------
|
|
# Author: hwliang <hwl@bt.cn>
|
|
#-------------------------------------------------------------------
|
|
|
|
#------------------------------
|
|
# SSL接口
|
|
#------------------------------
|
|
import public,os,sys,binascii,urllib,json,time,datetime,re
|
|
from Crypto import Random
|
|
from Crypto.PublicKey import RSA
|
|
from Crypto.Cipher import PKCS1_v1_5 as PKCS1_cipher
|
|
import base64
|
|
try:
|
|
from BTPanel import cache,session
|
|
except:
|
|
pass
|
|
class panelSSL:
|
|
__APIURL = 'https://brandnew.aapanel.com/api/user'
|
|
__APIURL2 = 'http://www.bt.cn/api/Cert'
|
|
__UPATH = 'data/userInfo.json'
|
|
__PUBKEY = 'data/public.key'
|
|
__userInfo = None
|
|
__PDATA = None
|
|
_check_url = None
|
|
#构造方法
|
|
def __init__(self):
|
|
if os.path.exists(self.__UPATH):
|
|
my_tmp = public.readFile(self.__UPATH)
|
|
if my_tmp:
|
|
try:
|
|
self.__userInfo = json.loads(my_tmp)
|
|
except:
|
|
self.__userInfo = {}
|
|
else:
|
|
self.__userInfo = {}
|
|
|
|
def en_code_rsa(self, data):
|
|
pk = public.readFile(self.__PUBKEY)
|
|
if not pk:
|
|
return False
|
|
pub_k = RSA.importKey(pk)
|
|
cipher = PKCS1_cipher.new(pub_k)
|
|
rsa_text = base64.b64encode(cipher.encrypt(bytes(data.encode("utf8"))))
|
|
return str(rsa_text,encoding='utf-8')
|
|
|
|
# 获取Token
|
|
def GetToken(self, get):
|
|
rtmp = ""
|
|
data = {}
|
|
data['identification'] = self.en_code_rsa(get.username)
|
|
data['password'] = self.en_code_rsa(get.password)
|
|
data['from_panel'] = self.en_code_rsa('1')
|
|
try:
|
|
rtmp = public.httpPost(self.__APIURL + '/login', data)
|
|
result = json.loads(rtmp)
|
|
if result['success']:
|
|
bind = 'data/bind.pl'
|
|
if os.path.exists(bind): os.remove(bind)
|
|
userinfo = result['res']['user_data']
|
|
userinfo['token'] = result['res']['access_token']
|
|
public.writeFile(self.__UPATH, json.dumps(userinfo))
|
|
session['focre_cloud'] = True
|
|
return public.returnMsg(True,'Bind successfully')
|
|
else:
|
|
return public.returnMsg(False,'Invalid username or email or password! please check and try again!')
|
|
except Exception as ex:
|
|
bind = 'data/bind.pl'
|
|
if os.path.exists(bind): os.remove(bind)
|
|
return public.returnMsg(False, '%s<br>%s' % (public.GetMsg("CONNECT_ERR"), str(rtmp)))
|
|
|
|
#删除Token
|
|
def DelToken(self,get):
|
|
uinfo = public.readFile(self.__UPATH)
|
|
try:
|
|
uinfo = json.loads(uinfo)
|
|
public.writeFile(self.__UPATH,json.dumps({'server_id':uinfo['server_id']}))
|
|
except:
|
|
public.ExecShell("rm -f " + self.__UPATH)
|
|
session['focre_cloud'] = True
|
|
|
|
return public.returnMsg(True,"SSL_BTUSER_UN")
|
|
|
|
#获取用户信息
|
|
def GetUserInfo(self,get):
|
|
result = {}
|
|
try:
|
|
if self.__userInfo:
|
|
userTmp = {}
|
|
userTmp['username'] = self.__userInfo['email'][0:3]+'****'+self.__userInfo['email'][-4:]
|
|
result['status'] = True
|
|
result['msg'] = public.getMsg('SSL_GET_SUCCESS')
|
|
result['data'] = userTmp
|
|
else:
|
|
userTmp = {}
|
|
userTmp['username'] = public.getMsg('SSL_NOT_BTUSER')
|
|
result['status'] = False
|
|
result['msg'] = public.getMsg('SSL_NOT_BTUSER')
|
|
result['data'] = userTmp
|
|
except:
|
|
userTmp = {}
|
|
userTmp['username'] = public.getMsg('SSL_NOT_BTUSER')
|
|
result['status'] = False
|
|
result['msg'] = public.getMsg('SSL_NOT_BTUSER')
|
|
result['data'] = userTmp
|
|
return result
|
|
|
|
#获取产品列表
|
|
def get_product_list(self,get):
|
|
result = self.request('get_product_list')
|
|
return result
|
|
|
|
#获取商业证书订单列表
|
|
def get_order_list(self,get):
|
|
result = self.request('get_order_list')
|
|
return result
|
|
|
|
#获指定商业证书订单
|
|
def get_order_find(self,get):
|
|
self.__PDATA['data']['oid'] = get.oid
|
|
result = self.request('get_order_find')
|
|
return result
|
|
|
|
#下载证书
|
|
def download_cert(self,get):
|
|
self.__PDATA['data']['oid'] = get.oid
|
|
result = self.request('download_cert')
|
|
return result
|
|
|
|
#部署指定商业证书
|
|
def set_cert(self,get):
|
|
siteName = get.siteName
|
|
certInfo = self.get_order_find(get)
|
|
path = '/www/server/panel/vhost/cert/' + siteName
|
|
if not os.path.exists(path):
|
|
public.ExecShell('mkdir -p ' + path)
|
|
csrpath = path+"/fullchain.pem"
|
|
keypath = path+"/privkey.pem"
|
|
pidpath = path+"/certOrderId"
|
|
|
|
other_file = path + '/partnerOrderId'
|
|
if os.path.exists(other_file): os.remove(other_file)
|
|
other_file = path + '/README'
|
|
if os.path.exists(other_file): os.remove(other_file)
|
|
|
|
public.writeFile(keypath,certInfo['privateKey'])
|
|
public.writeFile(csrpath,certInfo['certificate']+"\n"+certInfo['caCertificate'])
|
|
public.writeFile(pidpath,get.oid)
|
|
import panelSite
|
|
panelSite.panelSite().SetSSLConf(get)
|
|
public.serviceReload()
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
|
|
#生成商业证书支付订单
|
|
def apply_order_pay(self,args):
|
|
self.__PDATA['data'] = json.loads(args.pdata)
|
|
result = self.request('apply_cert_order')
|
|
return result
|
|
|
|
#检查商业证书支付状态
|
|
def get_pay_status(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
result = self.request('get_pay_status')
|
|
return result
|
|
|
|
#提交商业证书订单到CA
|
|
def apply_order(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
result = self.request('apply_cert')
|
|
if result['status'] == True:
|
|
self.__PDATA['data'] = {}
|
|
result['verify_info'] = self.get_verify_info(args)
|
|
return result
|
|
|
|
#获取商业证书验证信息
|
|
def get_verify_info(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
verify_info = self.request('get_verify_info')
|
|
is_file_verify = 'fileName' in verify_info
|
|
verify_info['paths'] = []
|
|
verify_info['hosts'] = []
|
|
for domain in verify_info['domains']:
|
|
if is_file_verify:
|
|
siteRunPath = self.get_domain_run_path(domain)
|
|
if not siteRunPath:
|
|
if domain[:4] == 'www.': domain = domain[:4]
|
|
verify_info['paths'].append(verify_info['path'].replace('example.com',domain))
|
|
continue
|
|
verify_path = siteRunPath + '/.well-known/pki-validation'
|
|
if not os.path.exists(verify_path):
|
|
os.makedirs(verify_path)
|
|
verify_file = verify_path + '/' + verify_info['fileName']
|
|
if os.path.exists(verify_file): continue
|
|
public.writeFile(verify_file,verify_info['content'])
|
|
else:
|
|
if domain[:4] == 'www.': domain = domain[:4]
|
|
verify_info['hosts'].append(verify_info['host'] + '.' + domain)
|
|
return verify_info
|
|
|
|
#处理验证信息
|
|
def set_verify_info(self,args):
|
|
verify_info = self.get_verify_info(args)
|
|
is_file_verify = 'fileName' in verify_info
|
|
verify_info['paths'] = []
|
|
verify_info['hosts'] = []
|
|
for domain in verify_info['domains']:
|
|
if domain[:2] == '*.': domain = domain[2:]
|
|
if is_file_verify:
|
|
siteRunPath = self.get_domain_run_path(domain)
|
|
if not siteRunPath:
|
|
if domain[:4] == 'www.': domain = domain[4:]
|
|
verify_info['paths'].append(verify_info['path'].replace('example.com',domain))
|
|
continue
|
|
verify_path = siteRunPath + '/.well-known/pki-validation'
|
|
if not os.path.exists(verify_path):
|
|
os.makedirs(verify_path)
|
|
verify_file = verify_path + '/' + verify_info['fileName']
|
|
if os.path.exists(verify_file): continue
|
|
public.writeFile(verify_file,verify_info['content'])
|
|
else:
|
|
if domain[:4] == 'www.': domain = domain[4:]
|
|
verify_info['hosts'].append(verify_info['host'] + '.' + domain)
|
|
return verify_info
|
|
|
|
|
|
#获取指定域名的PATH
|
|
def get_domain_run_path(self,domain):
|
|
pid = public.M('domain').where('name=?',(domain,)).getField('pid')
|
|
if not pid: return False
|
|
return self.get_site_run_path(pid)
|
|
|
|
|
|
def get_site_run_path(self,pid):
|
|
'''
|
|
@name 获取网站运行目录
|
|
@author hwliang<2020-08-05>
|
|
@param pid(int) 网站标识
|
|
@return string
|
|
'''
|
|
siteInfo = public.M('sites').where('id=?',(pid,)).find()
|
|
siteName = siteInfo['name']
|
|
sitePath = siteInfo['path']
|
|
webserver_type = public.get_webserver()
|
|
setupPath = '/www/server'
|
|
path = None
|
|
if webserver_type == 'nginx':
|
|
filename = setupPath + '/panel/vhost/nginx/' + siteName + '.conf'
|
|
if os.path.exists(filename):
|
|
conf = public.readFile(filename)
|
|
rep = r'\s*root\s+(.+);'
|
|
tmp1 = re.search(rep,conf)
|
|
if tmp1: path = tmp1.groups()[0]
|
|
|
|
elif webserver_type == 'apache':
|
|
filename = setupPath + '/panel/vhost/apache/' + siteName + '.conf'
|
|
if os.path.exists(filename):
|
|
conf = public.readFile(filename)
|
|
rep = r'\s*DocumentRoot\s*"(.+)"\s*\n'
|
|
tmp1 = re.search(rep,conf)
|
|
if tmp1: path = tmp1.groups()[0]
|
|
else:
|
|
filename = setupPath + '/panel/vhost/openlitespeed/' + siteName + '.conf'
|
|
if os.path.exists(filename):
|
|
conf = public.readFile(filename)
|
|
rep = r"vhRoot\s*(.*)"
|
|
path = re.search(rep,conf)
|
|
if not path:
|
|
path = None
|
|
else:
|
|
path = path.groups()[0]
|
|
|
|
if not path:
|
|
path = sitePath
|
|
return path
|
|
|
|
#验证URL是否匹配
|
|
def check_url_txt(self,args):
|
|
url = args.url
|
|
content = args.content
|
|
|
|
import http_requests
|
|
res = http_requests.get(url,s_type='curl',timeout=6,verify=False)
|
|
result = res.text
|
|
if not result: return 0
|
|
|
|
if result.find('11001') != -1 or result.find('curl: (6)') != -1: return -1
|
|
if result.find('curl: (7)') != -1 or res.status_code in [403,401]: return -5
|
|
if result.find('Not Found') != -1 or result.find('not found') != -1 or res.status_code in [404]:return -2
|
|
if result.find('timed out') != -1:return -3
|
|
if result.find('301') != -1 or result.find('302') != -1 or result.find('Redirecting...') != -1 or res.status_code in [301,302]:return -4
|
|
if result == content:return 1
|
|
return 0
|
|
|
|
#更换验证方式
|
|
def again_verify(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
self.__PDATA['data']['dcvMethod'] = args.dcvMethod
|
|
result = self.request('again_verify')
|
|
return result
|
|
|
|
#获取商业证书验证结果
|
|
def get_verify_result(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
verify_info = self.request('get_verify_result')
|
|
if verify_info['status'] in ['COMPLETE',False]: return verify_info
|
|
is_file_verify = 'CNAME_CSR_HASH' != verify_info['data']['dcvList'][0]['dcvMethod']
|
|
verify_info['paths'] = []
|
|
verify_info['hosts'] = []
|
|
if verify_info['data']['application']['status'] == 'ongoing':
|
|
return public.returnMsg(False,'订单出现问题,CA正在人工验证,若24小时内依然出现此提示,请联系宝塔')
|
|
for dinfo in verify_info['data']['dcvList']:
|
|
is_https = dinfo['dcvMethod'] == 'HTTPS_CSR_HASH'
|
|
if is_https:
|
|
is_https = 's'
|
|
else:
|
|
is_https = ''
|
|
domain = dinfo['domainName']
|
|
if domain[:2] == '*.': domain = domain[2:]
|
|
dinfo['domainName'] = domain
|
|
if is_file_verify:
|
|
siteRunPath = self.get_domain_run_path(domain)
|
|
if domain[:4] == 'www.': domain = domain[4:]
|
|
|
|
status = 0
|
|
url = 'http'+ is_https +'://'+ domain +'/.well-known/pki-validation/' + verify_info['data']['DCVfileName']
|
|
get = public.dict_obj()
|
|
get.url = url
|
|
get.content = verify_info['data']['DCVfileContent']
|
|
status = self.check_url_txt(get)
|
|
|
|
verify_info['paths'].append({'url':url,'status':status})
|
|
if not siteRunPath: continue
|
|
|
|
verify_path = siteRunPath + '/.well-known/pki-validation'
|
|
if not os.path.exists(verify_path):
|
|
os.makedirs(verify_path)
|
|
verify_file = verify_path + '/' + verify_info['data']['DCVfileName']
|
|
if os.path.exists(verify_file): continue
|
|
public.writeFile(verify_file,verify_info['data']['DCVfileContent'])
|
|
else:
|
|
if domain[:4] == 'www.': domain = domain[4:]
|
|
verify_info['hosts'].append(verify_info['data']['DCVdnsHost'] + '.' + domain)
|
|
|
|
return verify_info
|
|
|
|
#取消订单
|
|
def cancel_cert_order(self,args):
|
|
self.__PDATA['data']['oid'] = args.oid
|
|
result = self.request('cancel_cert_order')
|
|
return result
|
|
|
|
#发送请求
|
|
def request(self,dname):
|
|
self.__PDATA['data'] = json.dumps(self.__PDATA['data'])
|
|
result= public.returnMsg(False,'The request failed, please try again later!')
|
|
try:
|
|
result = public.httpPost(self.__APIURL2 + '/' + dname,self.__PDATA)
|
|
result = json.loads(result)
|
|
except:
|
|
pass
|
|
return result
|
|
#获取订单列表
|
|
def GetOrderList(self,get):
|
|
if hasattr(get,'siteName'):
|
|
path = '/etc/letsencrypt/live/'+ get.siteName + '/partnerOrderId'
|
|
if os.path.exists(path):
|
|
self.__PDATA['data']['partnerOrderId'] = public.readFile(path)
|
|
else:
|
|
path = '/www/server/panel/vhost/cert/' + get.siteName + '/partnerOrderId'
|
|
if os.path.exists(path):
|
|
self.__PDATA['data']['partnerOrderId'] = public.readFile(path)
|
|
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
rs = public.httpPost(self.__APIURL + '/GetSSLList',self.__PDATA)
|
|
try:
|
|
result = json.loads(rs)
|
|
except: return public.returnMsg(False,'SSL_ORDER_GET_FAILED')
|
|
|
|
result['data'] = self.En_Code(result['data'])
|
|
for i in range(len(result['data'])):
|
|
result['data'][i]['endtime'] = self.add_months(result['data'][i]['createTime'],result['data'][i]['validityPeriod'])
|
|
return result
|
|
|
|
#计算日期增加(月)
|
|
def add_months(self,dt,months):
|
|
import calendar
|
|
dt = datetime.datetime.fromtimestamp(dt/1000)
|
|
month = dt.month - 1 + months
|
|
year = dt.year + month // 12
|
|
month = month % 12 + 1
|
|
|
|
day = min(dt.day,calendar.monthrange(year,month)[1])
|
|
return (time.mktime(dt.replace(year=year, month=month, day=day).timetuple()) + 86400) * 1000
|
|
|
|
|
|
#申请证书
|
|
def GetDVSSL(self,get):
|
|
get.id = public.M('domain').where('name=?',(get.domain,)).getField('pid')
|
|
if hasattr(get,'siteName'):
|
|
get.path = public.M('sites').where('id=?',(get.id,)).getField('path')
|
|
else:
|
|
get.siteName = public.M('sites').where('id=?',(get.id,)).getField('name')
|
|
|
|
#当申请二级域名为www时,检测主域名是否绑定到同一网站
|
|
if get.domain[:4] == 'www.':
|
|
if not public.M('domain').where('name=? AND pid=?',(get.domain[4:],get.id)).count():
|
|
return public.returnMsg(False,"Apply for [%s] certificate to verify [%s] Please bind [%s] and resolve to the site!" % (get.domain,get.domain[4:],get.domain[4:]))
|
|
|
|
#检测是否开启强制HTTPS
|
|
if not self.CheckForceHTTPS(get.siteName):
|
|
return public.returnMsg(False,'SSL_ORDER_HTTPS_ERR')
|
|
|
|
#获取真实网站运行目录
|
|
runPath = self.GetRunPath(get)
|
|
if runPath != False and runPath != '/': get.path += runPath
|
|
|
|
|
|
#提前模拟测试验证文件值是否正确
|
|
authfile = get.path + '/.well-known/pki-validation/fileauth.txt'
|
|
if not self.CheckDomain(get):
|
|
if not os.path.exists(authfile):
|
|
return public.returnMsg(False,'CANT_CREATE',(authfile,))
|
|
else:
|
|
msg = '''{err_msg}<br><a class="btlink" href="{c_url}" target="_blank">{c_url}</a> <br><br>
|
|
<p></b>{err_msg1}</b></p>
|
|
{err_msg2}<br>
|
|
{err_msg3}<br>
|
|
{err_msg4}'''.format(c_url = self._check_url,
|
|
err_msg=public.getMsg('SSL_ERR_MSG'),
|
|
err_msg1=public.getMsg('SSL_ERR_MSG1'),
|
|
err_msg2=public.getMsg('SSL_ERR_MSG2'),
|
|
err_msg3=public.getMsg('SSL_ERR_MSG3'),
|
|
err_msg4=public.getMsg('SSL_ERR_MSG4'))
|
|
return public.returnMsg(False,msg)
|
|
|
|
action = 'GetDVSSL'
|
|
if hasattr(get,'partnerOrderId'):
|
|
self.__PDATA['data']['partnerOrderId'] = get.partnerOrderId
|
|
action = 'ReDVSSL'
|
|
|
|
self.__PDATA['data']['domain'] = get.domain
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
result = public.httpPost(self.__APIURL + '/' + action,self.__PDATA)
|
|
try:
|
|
result = json.loads(result)
|
|
except: return result
|
|
result['data'] = self.En_Code(result['data'])
|
|
|
|
try:
|
|
if 'authValue' in result['data'].keys():
|
|
public.writeFile(authfile,result['data']['authValue'])
|
|
except:
|
|
try:
|
|
public.writeFile(authfile,result['data']['authValue'])
|
|
except:
|
|
return result
|
|
|
|
return result
|
|
|
|
#检测是否强制HTTPS
|
|
def CheckForceHTTPS(self,siteName):
|
|
conf_file = '/www/server/panel/vhost/nginx/{}.conf'.format(siteName)
|
|
if not os.path.exists(conf_file):
|
|
return True
|
|
|
|
conf_body = public.readFile(conf_file)
|
|
if not conf_body: return True
|
|
if conf_body.find('HTTP_TO_HTTPS_START') != -1:
|
|
return False
|
|
return True
|
|
|
|
#获取运行目录
|
|
def GetRunPath(self,get):
|
|
if hasattr(get,'siteName'):
|
|
get.id = public.M('sites').where('name=?',(get.siteName,)).getField('id')
|
|
else:
|
|
get.id = public.M('sites').where('path=?',(get.path,)).getField('id')
|
|
if not get.id: return False
|
|
import panelSite
|
|
result = panelSite.panelSite().GetSiteRunPath(get)
|
|
return result['runPath']
|
|
|
|
|
|
#检查域名是否解析
|
|
def CheckDomain(self,get):
|
|
try:
|
|
#创建目录
|
|
spath = get.path + '/.well-known/pki-validation'
|
|
if not os.path.exists(spath): public.ExecShell("mkdir -p '" + spath + "'")
|
|
|
|
#生成并写入检测内容
|
|
epass = public.GetRandomString(32)
|
|
public.writeFile(spath + '/fileauth.txt',epass)
|
|
|
|
#检测目标域名访问结果
|
|
if get.domain[:4] == 'www.': #申请二级域名为www时检测主域名
|
|
get.domain = get.domain[4:]
|
|
|
|
import http_requests
|
|
self._check_url = 'http://127.0.0.1/.well-known/pki-validation/fileauth.txt'
|
|
result = http_requests.get(self._check_url,s_type='curl',timeout=6,headers={"host":get.domain}).text
|
|
self.__test = result
|
|
if result == epass: return True
|
|
self._check_url = self._check_url.replace('127.0.0.1', get.domain)
|
|
return False
|
|
except:
|
|
self._check_url = self._check_url.replace('127.0.0.1', get.domain)
|
|
return False
|
|
|
|
#确认域名
|
|
def Completed(self,get):
|
|
self.__PDATA['data']['partnerOrderId'] = get.partnerOrderId
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
if hasattr(get,'siteName'):
|
|
get.path = public.M('sites').where('name=?',(get.siteName,)).getField('path')
|
|
runPath = self.GetRunPath(get)
|
|
if runPath != False and runPath != '/': get.path += runPath
|
|
tmp = public.httpPost(self.__APIURL + '/SyncOrder',self.__PDATA)
|
|
try:
|
|
sslInfo = json.loads(tmp)
|
|
except:
|
|
return public.returnMsg(False,tmp)
|
|
|
|
sslInfo['data'] = self.En_Code(sslInfo['data'])
|
|
try:
|
|
spath = get.path + '/.well-known/pki-validation'
|
|
if not os.path.exists(spath): public.ExecShell("mkdir -p '" + spath + "'")
|
|
public.writeFile(spath + '/fileauth.txt',sslInfo['data']['authValue'])
|
|
except:
|
|
return public.returnMsg(False,'SSL_CHECK_WRITE_ERR')
|
|
try:
|
|
result = json.loads(public.httpPost(self.__APIURL + '/Completed',self.__PDATA))
|
|
if 'data' in result:
|
|
result['data'] = self.En_Code(result['data'])
|
|
except:
|
|
result = public.returnMsg(True,'CHECKING')
|
|
n = 0;
|
|
my_ok = False
|
|
while True:
|
|
if n > 5: break
|
|
time.sleep(5)
|
|
rRet = json.loads(public.httpPost(self.__APIURL + '/SyncOrder',self.__PDATA))
|
|
n +=1
|
|
rRet['data'] = self.En_Code(rRet['data'])
|
|
try:
|
|
if rRet['data']['stateCode'] == 'COMPLETED':
|
|
my_ok = True
|
|
break
|
|
except: return public.get_error_info()
|
|
if not my_ok: return result
|
|
return rRet
|
|
|
|
#同步指定订单
|
|
def SyncOrder(self,get):
|
|
self.__PDATA['data']['partnerOrderId'] = get.partnerOrderId
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
result = json.loads(public.httpPost(self.__APIURL + '/SyncOrder',self.__PDATA))
|
|
result['data'] = self.En_Code(result['data'])
|
|
return result
|
|
|
|
#获取证书
|
|
def GetSSLInfo(self,get):
|
|
self.__PDATA['data']['partnerOrderId'] = get.partnerOrderId
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
time.sleep(3)
|
|
result = json.loads(public.httpPost(self.__APIURL + '/GetSSLInfo',self.__PDATA))
|
|
result['data'] = self.En_Code(result['data'])
|
|
if not 'privateKey' in result['data']: return result
|
|
|
|
#写配置到站点
|
|
if hasattr(get,'siteName'):
|
|
try:
|
|
siteName = get.siteName
|
|
path = '/www/server/panel/vhost/cert/' + siteName
|
|
if not os.path.exists(path):
|
|
public.ExecShell('mkdir -p ' + path)
|
|
csrpath = path+"/fullchain.pem"
|
|
keypath = path+"/privkey.pem"
|
|
pidpath = path+"/partnerOrderId"
|
|
#清理旧的证书链
|
|
public.ExecShell('rm -f ' + keypath)
|
|
public.ExecShell('rm -f ' + csrpath)
|
|
public.ExecShell('rm -rf ' + path + '-00*')
|
|
public.ExecShell('rm -rf /etc/letsencrypt/archive/' + get.siteName)
|
|
public.ExecShell('rm -rf /etc/letsencrypt/archive/' + get.siteName + '-00*')
|
|
public.ExecShell('rm -f /etc/letsencrypt/renewal/'+ get.siteName + '.conf')
|
|
public.ExecShell('rm -f /etc/letsencrypt/renewal/'+ get.siteName + '-00*.conf')
|
|
public.ExecShell('rm -f ' + path + '/README')
|
|
public.ExecShell('rm -f ' + path + '/certOrderId')
|
|
|
|
public.writeFile(keypath,result['data']['privateKey'])
|
|
public.writeFile(csrpath,result['data']['cert']+result['data']['certCa'])
|
|
public.writeFile(pidpath,get.partnerOrderId)
|
|
import panelSite
|
|
panelSite.panelSite().SetSSLConf(get)
|
|
public.serviceReload()
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
except:
|
|
return public.returnMsg(False,'SET_ERROR')
|
|
result['data'] = self.En_Code(result['data'])
|
|
return result
|
|
|
|
#部署证书夹证书
|
|
def SetCertToSite(self,get):
|
|
try:
|
|
result = self.GetCert(get)
|
|
if not 'privkey' in result: return result
|
|
siteName = get.siteName
|
|
path = '/www/server/panel/vhost/cert/' + siteName
|
|
if not os.path.exists(path):
|
|
public.ExecShell('mkdir -p ' + path)
|
|
csrpath = path+"/fullchain.pem"
|
|
keypath = path+"/privkey.pem"
|
|
|
|
#清理旧的证书链
|
|
public.ExecShell('rm -f ' + keypath)
|
|
public.ExecShell('rm -f ' + csrpath)
|
|
public.ExecShell('rm -rf ' + path + '-00*')
|
|
public.ExecShell('rm -rf /etc/letsencrypt/archive/' + get.siteName)
|
|
public.ExecShell('rm -rf /etc/letsencrypt/archive/' + get.siteName + '-00*')
|
|
public.ExecShell('rm -f /etc/letsencrypt/renewal/'+ get.siteName + '.conf')
|
|
public.ExecShell('rm -f /etc/letsencrypt/renewal/'+ get.siteName + '-00*.conf')
|
|
public.ExecShell('rm -f ' + path + '/README')
|
|
if os.path.exists(path + '/certOrderId'): os.remove(path + '/certOrderId')
|
|
|
|
public.writeFile(keypath,result['privkey'])
|
|
public.writeFile(csrpath,result['fullchain'])
|
|
import panelSite
|
|
panelSite.panelSite().SetSSLConf(get)
|
|
public.serviceReload()
|
|
return public.returnMsg(True,'SET_SUCCESS')
|
|
except Exception as ex:
|
|
return public.returnMsg(False,'SET_ERROR,' + public.get_error_info())
|
|
|
|
#获取证书列表
|
|
def GetCertList(self,get):
|
|
try:
|
|
vpath = '/www/server/panel/vhost/ssl'
|
|
if not os.path.exists(vpath): public.ExecShell("mkdir -p " + vpath)
|
|
data = []
|
|
for d in os.listdir(vpath):
|
|
mpath = vpath + '/' + d + '/info.json'
|
|
if not os.path.exists(mpath): continue
|
|
tmp = public.readFile(mpath)
|
|
if not tmp: continue
|
|
tmp1 = json.loads(tmp)
|
|
data.append(tmp1)
|
|
return data
|
|
except:
|
|
return []
|
|
|
|
#删除证书
|
|
def RemoveCert(self,get):
|
|
try:
|
|
vpath = '/www/server/panel/vhost/ssl/' + get.certName.replace("*.",'')
|
|
if not os.path.exists(vpath): return public.returnMsg(False,'CRET_NOT_EXIST')
|
|
public.ExecShell("rm -rf " + vpath)
|
|
return public.returnMsg(True,'CRET_DEL')
|
|
except:
|
|
return public.returnMsg(False,'CRET_DEL_FAIL')
|
|
|
|
#保存证书
|
|
def SaveCert(self,get):
|
|
try:
|
|
certInfo = self.GetCertName(get)
|
|
if not certInfo: return public.returnMsg(False,'CRET_RESOLVE_FAIL')
|
|
vpath = '/www/server/panel/vhost/ssl/' + certInfo['subject']
|
|
vpath=vpath.replace("*.",'')
|
|
if not os.path.exists(vpath):
|
|
public.ExecShell("mkdir -p " + vpath)
|
|
public.writeFile(vpath + '/privkey.pem',public.readFile(get.keyPath))
|
|
public.writeFile(vpath + '/fullchain.pem',public.readFile(get.certPath))
|
|
public.writeFile(vpath + '/info.json',json.dumps(certInfo))
|
|
return public.returnMsg(True,'CRET_SAVE_SUSSESS')
|
|
except:
|
|
return public.returnMsg(False,'CRET_SAVE_FAIL')
|
|
|
|
#读取证书
|
|
def GetCert(self,get):
|
|
vpath = os.path.join('/www/server/panel/vhost/ssl' , get.certName.replace("*.",''))
|
|
if not os.path.exists(vpath): return public.returnMsg(False,'CRET_NOT_EXIST')
|
|
data = {}
|
|
data['privkey'] = public.readFile(vpath + '/privkey.pem')
|
|
data['fullchain'] = public.readFile(vpath + '/fullchain.pem')
|
|
return data;
|
|
|
|
#获取证书名称
|
|
def GetCertName(self,get):
|
|
return self.get_cert_init(get.certPath)
|
|
# try:
|
|
# openssl = '/usr/local/openssl/bin/openssl'
|
|
# if not os.path.exists(openssl): openssl = 'openssl'
|
|
# result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -subject -enddate -startdate -issuer")
|
|
# tmp = result[0].split("\n")
|
|
# data = {}
|
|
# data['subject'] = tmp[0].split('=')[-1]
|
|
# data['notAfter'] = self.strfToTime(tmp[1].split('=')[1])
|
|
# data['notBefore'] = self.strfToTime(tmp[2].split('=')[1])
|
|
# if tmp[3].find('O=') == -1:
|
|
# data['issuer'] = tmp[3].split('CN=')[-1]
|
|
# else:
|
|
# data['issuer'] = tmp[3].split('O=')[-1].split(',')[0]
|
|
# if data['issuer'].find('/') != -1: data['issuer'] = data['issuer'].split('/')[0]
|
|
# result = public.ExecShell(openssl + " x509 -in "+get.certPath+" -noout -text|grep DNS")
|
|
# data['dns'] = result[0].replace('DNS:','').replace(' ','').strip().split(',')
|
|
# return data
|
|
# except:
|
|
# print(public.get_error_info())
|
|
# return None
|
|
|
|
|
|
# 获取指定证书基本信息
|
|
def get_cert_init(self, pem_file):
|
|
if not os.path.exists(pem_file):
|
|
return None
|
|
try:
|
|
import OpenSSL
|
|
result = {}
|
|
x509 = OpenSSL.crypto.load_certificate(
|
|
OpenSSL.crypto.FILETYPE_PEM, public.readFile(pem_file))
|
|
# 取产品名称
|
|
issuer = x509.get_issuer()
|
|
result['issuer'] = ''
|
|
if hasattr(issuer, 'CN'):
|
|
result['issuer'] = issuer.CN
|
|
if not result['issuer']:
|
|
is_key = [b'0', '0']
|
|
issue_comp = issuer.get_components()
|
|
if len(issue_comp) == 1:
|
|
is_key = [b'CN', 'CN']
|
|
for iss in issue_comp:
|
|
if iss[0] in is_key:
|
|
result['issuer'] = iss[1].decode()
|
|
break
|
|
if not result['issuer']:
|
|
if hasattr(issuer, 'O'):
|
|
result['issuer'] = issuer.O
|
|
|
|
# 取到期时间
|
|
result['notAfter'] = self.strf_date(
|
|
bytes.decode(x509.get_notAfter())[:-1])
|
|
# 取申请时间
|
|
result['notBefore'] = self.strf_date(
|
|
bytes.decode(x509.get_notBefore())[:-1])
|
|
# 取可选名称
|
|
result['dns'] = []
|
|
for i in range(x509.get_extension_count()):
|
|
s_name = x509.get_extension(i)
|
|
if s_name.get_short_name() in [b'subjectAltName', 'subjectAltName']:
|
|
s_dns = str(s_name).split(',')
|
|
for d in s_dns:
|
|
result['dns'].append(d.split(':')[1])
|
|
subject = x509.get_subject().get_components()
|
|
# 取主要认证名称
|
|
if len(subject) == 1:
|
|
result['subject'] = subject[0][1].decode()
|
|
else:
|
|
if not result['dns']:
|
|
for sub in subject:
|
|
if sub[0] == b'CN':
|
|
result['subject'] = sub[1].decode()
|
|
break
|
|
# result['dns'].append(result['subject'])
|
|
if 'subject' in result:
|
|
result['dns'].append(result['subject'])
|
|
|
|
else:
|
|
result['subject'] = result['dns'][0]
|
|
return result
|
|
except:
|
|
return None
|
|
|
|
|
|
# 转换时间
|
|
def strf_date(self, sdate):
|
|
return time.strftime('%Y-%m-%d', time.strptime(sdate, '%Y%m%d%H%M%S'))
|
|
|
|
#转换时间
|
|
def strfToTime(self,sdate):
|
|
import time
|
|
return time.strftime('%Y-%m-%d',time.strptime(sdate,'%b %d %H:%M:%S %Y %Z'))
|
|
|
|
|
|
#获取产品列表
|
|
def GetSSLProduct(self,get):
|
|
self.__PDATA['data'] = self.De_Code(self.__PDATA['data'])
|
|
result = json.loads(public.httpPost(self.__APIURL + '/GetSSLProduct',self.__PDATA))
|
|
result['data'] = self.En_Code(result['data'])
|
|
return result
|
|
|
|
#加密数据
|
|
def De_Code(self,data):
|
|
if sys.version_info[0] == 2:
|
|
import urllib
|
|
pdata = urllib.urlencode(data)
|
|
return binascii.hexlify(pdata)
|
|
else:
|
|
import urllib.parse
|
|
pdata = urllib.parse.urlencode(data)
|
|
if type(pdata) == str: pdata = pdata.encode('utf-8')
|
|
return binascii.hexlify(pdata).decode()
|
|
|
|
#解密数据
|
|
def En_Code(self,data):
|
|
if sys.version_info[0] == 2:
|
|
import urllib
|
|
result = urllib.unquote(binascii.unhexlify(data))
|
|
else:
|
|
import urllib.parse
|
|
if type(data) == str: data = data.encode('utf-8')
|
|
tmp = binascii.unhexlify(data)
|
|
if type(tmp) != str: tmp = tmp.decode('utf-8')
|
|
result = urllib.parse.unquote(tmp)
|
|
|
|
if type(result) != str: result = result.decode('utf-8')
|
|
return json.loads(result)
|
|
|
|
# 手动一键续签
|
|
def renew_lets_ssl(self, get):
|
|
if not os.path.exists('vhost/cert/crontab.json'):
|
|
return public.returnMsg(False,'SSL_RENEW_ERR')
|
|
|
|
old_list = json.loads(public.ReadFile("vhost/cert/crontab.json"))
|
|
cron_list = old_list
|
|
if hasattr(get, 'siteName'):
|
|
if not get.siteName in old_list:
|
|
return public.returnMsg(False,'WEBSITE_SSL_RENEW_ERR')
|
|
cron_list = {}
|
|
cron_list[get.siteName] = old_list[get.siteName]
|
|
|
|
import panelLets
|
|
lets = panelLets.panelLets()
|
|
|
|
result = {}
|
|
result['status'] = True
|
|
result['sucess_list'] = []
|
|
result['err_list'] = []
|
|
for siteName in cron_list:
|
|
data = cron_list[siteName]
|
|
ret = lets.renew_lest_cert(data)
|
|
if ret['status']:
|
|
result['sucess_list'].append(siteName)
|
|
else:
|
|
result['err_list'].append({"siteName":siteName,"msg":ret['msg']})
|
|
return result
|